Merge remote-tracking branch 'upstream/dev'

This commit is contained in:
dwertent
2021-09-09 11:43:44 +03:00
16 changed files with 4176 additions and 12 deletions

View File

@@ -40,9 +40,9 @@ jobs:
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
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
- name: Upload Release Asset
- name: Upload Release binaries
id: upload-release-asset
uses: actions/upload-release-asset@v1
env:

View File

@@ -1,7 +1,6 @@
<img src="docs/kubescape.png" width="300" alt="logo" align="center">
[![build](https://github.com/armosec/kubescape/actions/workflows/build.yaml/badge.svg)](https://github.com/armosec/kubescape/actions/workflows/build.yaml)
[![Github All Releases](https://img.shields.io/github/downloads/armosec/kubescape/total.svg)](https://github.com/armosec/kubescape)
[![Go Report Card](https://goreportcard.com/badge/github.com/armosec/kubescape)](https://goreportcard.com/report/github.com/armosec/kubescape)
Kubescape is the first tool for testing if Kubernetes is deployed securely as defined in [Kubernetes Hardening Guidance by NSA and CISA](https://www.nsa.gov/News-Features/Feature-Stories/Article-View/Article/2716980/nsa-cisa-release-kubernetes-hardening-guidance/)
@@ -50,7 +49,7 @@ If you wish to scan all namespaces in your cluster, remove the `--exclude-namesp
kubescape scan framework nsa --exclude-namespaces kube-system,kube-public
```
* Scan local `yaml`/`json` files before deploying <img src="docs/new-feature.svg">
* Scan local `yaml`/`json` files before deploying
```
kubescape scan framework nsa *.yaml
```

View File

@@ -22,7 +22,7 @@ type ArmoAPI struct {
func NewArmoAPI() *ArmoAPI {
return &ArmoAPI{
httpClient: &http.Client{},
baseURL: "https://dashbe.auprod1.cyberarmorsoft.com",
baseURL: "https://dashbe.euprod1.cyberarmorsoft.com",
}
}
func (armoAPI *ArmoAPI) GetFramework(name string) (*opapolicy.Framework, error) {

View File

@@ -22,7 +22,7 @@ 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)
}

View File

@@ -29,6 +29,9 @@ OUTPUT=$BASE_DIR/$KUBESCAPE_EXEC
curl --progress-bar -L $DOWNLOAD_URL -o $OUTPUT
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

View File

@@ -56,12 +56,6 @@ func NewOPAProcessorHandler(processedPolicy, reportResults *chan *cautils.OPASes
}
func (opaHandler *OPAProcessorHandler) ProcessRulesListenner() {
// recover
defer func() {
if err := recover(); err != nil {
glog.Errorf("RECOVER in ProcessRulesListenner, reason: %v", err)
}
}()
for {
opaSessionObj := <-*opaHandler.processedPolicy

View File

@@ -0,0 +1,136 @@
package exceptions
import (
"github.com/armosec/kubescape/cautils/k8sinterface"
"github.com/armosec/kubescape/cautils/armotypes"
"github.com/armosec/kubescape/cautils/opapolicy"
"k8s.io/apimachinery/pkg/labels"
)
func ListRuleExceptions(exceptionPolicies []armotypes.PostureExceptionPolicy, frameworkName, controlName, ruleName string) []armotypes.PostureExceptionPolicy {
ruleExceptions := []armotypes.PostureExceptionPolicy{}
for i := range exceptionPolicies {
if ruleHasExceptions(&exceptionPolicies[i], frameworkName, controlName, ruleName) {
ruleExceptions = append(ruleExceptions, exceptionPolicies[i])
}
}
return ruleExceptions
}
func ruleHasExceptions(exceptionPolicy *armotypes.PostureExceptionPolicy, frameworkName, controlName, ruleName string) bool {
for _, posturePolicy := range exceptionPolicy.PosturePolicies {
if posturePolicy.FrameworkName == "" && posturePolicy.ControlName == "" && posturePolicy.RuleName == "" {
continue // empty policy -> ignore
}
if posturePolicy.FrameworkName != "" && posturePolicy.FrameworkName != frameworkName {
continue // policy does not match
}
if posturePolicy.ControlName != "" && posturePolicy.ControlName != controlName {
continue // policy does not match
}
if posturePolicy.RuleName != "" && posturePolicy.RuleName != ruleName {
continue // policy does not match
}
return true // policies match
}
return false
}
func AddExceptionsToRuleResponses(results []opapolicy.RuleResponse, ruleExceptions []armotypes.PostureExceptionPolicy) {
if len(ruleExceptions) == 0 {
return
}
for i := range results {
workloads := alertObjectToWorkloads(&results[i].AlertObject)
if len(workloads) == 0 {
continue
}
for w := range workloads {
if exception := getException(ruleExceptions, workloads[w]); exception != nil {
results[i].Exception = exception
}
}
results[i].RuleStatus = results[i].GetSingleResultStatus()
}
}
func alertObjectToWorkloads(obj *opapolicy.AlertObject) []k8sinterface.IWorkload {
resource := []k8sinterface.IWorkload{}
for i := range obj.K8SApiObjects {
r := k8sinterface.NewWorkloadObj(obj.K8SApiObjects[i])
if r == nil {
continue
}
resource = append(resource, r)
}
return resource
}
func getException(ruleExceptions []armotypes.PostureExceptionPolicy, workload k8sinterface.IWorkload) *armotypes.PostureExceptionPolicy {
for e := range ruleExceptions {
for _, resource := range ruleExceptions[e].Resources {
if hasException(&resource, workload) {
return &ruleExceptions[e] // TODO - return disable exception out of all exceptions
}
}
}
return nil
}
// compareMetadata - compare namespace and kind
func hasException(designator *armotypes.PortalDesignator, workload k8sinterface.IWorkload) bool {
cluster, namespace, kind, name, labels := designator.DigestPortalDesignator()
if cluster == "" && namespace == "" && kind == "" && name == "" && len(labels) == 0 {
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 namespace != "" && !compareNamespace(workload, namespace) {
return false // namespaces do not match
}
if kind != "" && !compareKind(workload, kind) {
return false // kinds do not match
}
if name != "" && !compareName(workload, name) {
return false // names do not match
}
if len(labels) > 0 && !compareLabels(workload, labels) {
return false // labels do not match
}
return true // no mismatch found -> the workload has an exception
}
func compareNamespace(workload k8sinterface.IWorkload, namespace string) bool {
if workload.GetKind() == "Namespace" {
return namespace == workload.GetName()
}
return namespace == workload.GetNamespace()
}
func compareKind(workload k8sinterface.IWorkload, kind string) bool {
return kind == workload.GetKind()
}
func compareName(workload k8sinterface.IWorkload, name string) bool {
return name == workload.GetName()
}
func compareLabels(workload k8sinterface.IWorkload, attributes map[string]string) bool {
workloadLabels := labels.Set(workload.GetLabels())
designators := labels.Set(attributes).AsSelector()
return designators.Matches(workloadLabels)
}

View File

@@ -0,0 +1,59 @@
package exceptions
import (
"testing"
"github.com/armosec/kubescape/cautils/armotypes"
)
func PostureExceptionPolicyDisableMock() *armotypes.PostureExceptionPolicy {
return &armotypes.PostureExceptionPolicy{}
}
func PostureExceptionPolicyAlertOnlyMock() *armotypes.PostureExceptionPolicy {
return &armotypes.PostureExceptionPolicy{
PortalBase: armotypes.PortalBase{
Name: "postureExceptionPolicyAlertOnlyMock",
},
PolicyType: "postureExceptionPolicy",
Actions: []armotypes.PostureExceptionPolicyActions{armotypes.AlertOnly},
Resources: []armotypes.PortalDesignator{
{
DesignatorType: armotypes.DesignatorAttributes,
Attributes: map[string]string{
armotypes.AttributeNamespace: "default",
armotypes.AttributeCluster: "unittest",
},
},
},
PosturePolicies: []armotypes.PosturePolicy{
{
FrameworkName: "MITRE",
},
},
}
}
func TestListRuleExceptions(t *testing.T) {
exceptionPolicies := []armotypes.PostureExceptionPolicy{*PostureExceptionPolicyAlertOnlyMock()}
res1 := ListRuleExceptions(exceptionPolicies, "MITRE", "", "")
if len(res1) != 1 {
t.Errorf("expecting 1 exception")
}
res2 := ListRuleExceptions(exceptionPolicies, "", "hostPath mount", "")
if len(res2) != 0 {
t.Errorf("expecting 0 exception")
}
}
// func TestGetException(t *testing.T) {
// exceptionPolicies := []armotypes.PostureExceptionPolicy{*PostureExceptionPolicyAlertOnlyMock()}
// res1 := ListRuleExceptions(exceptionPolicies, "MITRE", "", "")
// if len(res1) != 1 {
// t.Errorf("expecting 1 exception")
// }
// res2 := ListRuleExceptions(exceptionPolicies, "", "hostPath mount", "")
// if len(res2) != 0 {
// t.Errorf("expecting 0 exception")
// }
// }

View File

@@ -0,0 +1,232 @@
{
"developer_framework": {
"Writable hostPath mount": {
"baseScore": 1.0,
"improvementRatio": 1.0
},
"Compromised images in registry": {
"baseScore": 1.0,
"improvementRatio": 1.0
},
"Exposed dashboard": {
"baseScore": 1.0,
"improvementRatio": 1.0
},
"Network mapping": {
"baseScore": 1.0,
"improvementRatio": 1.0
},
"Access container service account": {
"baseScore": 1.0,
"improvementRatio": 1.0
},
"Access Kubelet API": {
"baseScore": 1.0,
"improvementRatio": 1.0
},
"Cluster-admin binding": {
"baseScore": 1.0,
"improvementRatio": 1.0
},
"Kubernetes CronJob": {
"baseScore": 1.0,
"improvementRatio": 1.0
},
"SSH server running inside container": {
"baseScore": 1.0,
"improvementRatio": 1.0
},
"Pod / container name similarity": {
"baseScore": 1.0,
"improvementRatio": 1.0
},
"Cluster internal networking": {
"baseScore": 1.0,
"improvementRatio": 1.0
},
"Access Kubernetes dashboard": {
"baseScore": 1.0,
"improvementRatio": 1.0
},
"Privileged container": {
"baseScore": 1.0,
"improvementRatio": 1.0
},
"hostPath mount": {
"baseScore": 1.0,
"improvementRatio": 1.0
},
"Instance Metadata API": {
"baseScore": 1.0,
"improvementRatio": 1.0
},
"Applications credentials in configuration files": {
"baseScore": 1.0,
"improvementRatio": 1.0
}
},
"MITRE": {
"Writable hostPath mount": {
"baseScore": 8.0,
"improvementRatio": 0.5
},
"Sidecar injection": {
"baseScore": 1.0,
"improvementRatio": 1.0
},
"Compromised images in registry": {
"baseScore": 1.0,
"improvementRatio": 1.0
},
"Access tiller endpoint": {
"baseScore": 1.0,
"improvementRatio": 1.0
},
"Data Destruction": {
"baseScore": 1.0,
"improvementRatio": 1.0
},
"Resource Hijacking": {
"baseScore": 1.0,
"improvementRatio": 1.0
},
"Access the Kubernetes API server": {
"baseScore": 1.0,
"improvementRatio": 1.0
},
"Exposed dashboard": {
"baseScore": 1.0,
"improvementRatio": 1.0
},
"Backdoor container": {
"baseScore": 1.0,
"improvementRatio": 1.0
},
"Network mapping": {
"baseScore": 1.0,
"improvementRatio": 1.0
},
"Images from private registry": {
"baseScore": 1.0,
"improvementRatio": 1.0
},
"Mount service principal": {
"baseScore": 1.0,
"improvementRatio": 1.0
},
"Access container service account": {
"baseScore": 1.0,
"improvementRatio": 1.0
},
"Malicious admission controller (validating)": {
"baseScore": 1.0,
"improvementRatio": 1.0
},
"Access Kubelet API": {
"baseScore": 1.0,
"improvementRatio": 1.0
},
"Vulnerable application": {
"baseScore": 1.0,
"improvementRatio": 1.0
},
"Application exploit (RCE)": {
"baseScore": 1.0,
"improvementRatio": 1.0
},
"Cluster-admin binding": {
"baseScore": 1.0,
"improvementRatio": 1.0
},
"Kubernetes CronJob": {
"baseScore": 1.0,
"improvementRatio": 1.0
},
"SSH server running inside container": {
"baseScore": 1.0,
"improvementRatio": 1.0
},
"List Kubernetes secrets": {
"baseScore": 1.0,
"improvementRatio": 1.0
},
"Pod / container name similarity": {
"baseScore": 1.0,
"improvementRatio": 1.0
},
"Cluster internal networking": {
"baseScore": 1.0,
"improvementRatio": 1.0
},
"Exposed sensitive interfaces": {
"baseScore": 1.0,
"improvementRatio": 1.0
},
"Bash/cmd inside container": {
"baseScore": 1.0,
"improvementRatio": 1.0
},
"Clear container logs": {
"baseScore": 1.0,
"improvementRatio": 1.0
},
"Access Kubernetes dashboard": {
"baseScore": 1.0,
"improvementRatio": 1.0
},
"New container": {
"baseScore": 1.0,
"improvementRatio": 1.0
},
"Privileged container": {
"baseScore": 1.0,
"improvementRatio": 1.0
},
"CoreDNS poisoning": {
"baseScore": 1.0,
"improvementRatio": 1.0
},
"hostPath mount": {
"baseScore": 1.0,
"improvementRatio": 1.0
},
"Instance Metadata API": {
"baseScore": 1.0,
"improvementRatio": 1.0
},
"Malicious admission controller (mutating)": {
"baseScore": 1.0,
"improvementRatio": 1.0
},
"Exec into container": {
"baseScore": 1.0,
"improvementRatio": 1.0
},
"Delete Kubernetes events": {
"baseScore": 1.0,
"improvementRatio": 1.0
},
"Applications credentials in configuration files": {
"baseScore": 1.0,
"improvementRatio": 1.0
}
},
"NSA": {
"Control plane hardening": {
"baseScore": 1.0,
"improvementRatio": 1.0
},
"Immutable container filesystem": {
"baseScore": 1.0,
"improvementRatio": 1.0
},
"Non-root containers": {
"baseScore": 1.0,
"improvementRatio": 1.0
},
"Host PID/IPC privileges": {
"baseScore": 1.0,
"improvementRatio": 1.0
}
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,22 @@
{
"pod": 1.0,
"service": 1.0,
"daemonset": 1.0,
"deployment": 1.0,
"replicaset": 1.1,
"statefulset": 1.0,
"job": 1.0,
"secret": 1.0,
"cronjob": 1.0,
"clusterrolebinding": 1.0,
"clusterrole": 1.0,
"rolebinding": 1.0,
"role": 1.0,
"networkpolicy": 1.0,
"controllerrevision": 1.0,
"namespace": 1.0,
"serviceaccount": 1.0,
"configmap": 1.0,
"node": 1.0
}

201
scapepkg/score/score.go Normal file
View File

@@ -0,0 +1,201 @@
package score
import (
"encoding/json"
"fmt"
"io/ioutil"
"strings"
appsv1 "k8s.io/api/apps/v1"
// corev1 "k8s.io/api/core/v1"
k8sinterface "github.com/armosec/kubescape/cautils/k8sinterface"
"github.com/armosec/kubescape/cautils/opapolicy"
)
type ControlScoreWeights struct {
BaseScore float32 `json:"baseScore"`
RuntimeImprovementMultiplier float32 `json:"improvementRatio"`
}
type ScoreUtil struct {
ResourceTypeScores map[string]float32
FrameworksScore map[string]map[string]ControlScoreWeights
K8SApoObj *k8sinterface.KubernetesApi
configPath string
}
var postureScore *ScoreUtil
func (su *ScoreUtil) Calculate(frameworksReports []opapolicy.FrameworkReport) error {
for i := range frameworksReports {
su.CalculateFrameworkScore(&frameworksReports[i])
}
return nil
}
func (su *ScoreUtil) CalculateFrameworkScore(framework *opapolicy.FrameworkReport) error {
for i := range framework.ControlReports {
framework.WCSScore += su.ControlScore(&framework.ControlReports[i], framework.Name)
framework.Score += framework.ControlReports[i].Score
framework.ARMOImprovement += framework.ControlReports[i].ARMOImprovement
}
if framework.WCSScore > 0 {
framework.Score = (framework.Score * 100) / framework.WCSScore
framework.ARMOImprovement = (framework.ARMOImprovement * 100) / framework.WCSScore
}
return fmt.Errorf("unable to calculate score for framework %s due to bad wcs score", framework.Name)
}
/*
daemonset: daemonsetscore*#nodes
workloads: if replicas:
replicascore*workloadkindscore*#replicas
else:
regular
*/
func (su *ScoreUtil) resourceRules(resources []map[string]interface{}) float32 {
var weight float32 = 0
for _, v := range resources {
var score float32 = 0
wl := k8sinterface.NewWorkloadObj(v)
kind := ""
if wl != nil {
kind = strings.ToLower(wl.GetKind())
replicas := wl.GetReplicas()
score = su.ResourceTypeScores[kind]
if replicas > 1 {
score *= su.ResourceTypeScores["replicaset"] * float32(replicas)
}
} else {
epsilon := float32(0.00001)
keys := make([]string, 0, len(v))
for k := range v {
keys = append(keys, k)
}
kind = keys[0]
score = su.ResourceTypeScores[kind]
if score == 0.0 || (score > -1*epsilon && score < epsilon) {
score = 1
}
}
if kind == "daemonset" {
b, err := json.Marshal(v)
if err == nil {
dmnset := appsv1.DaemonSet{}
json.Unmarshal(b, &dmnset)
score *= float32(dmnset.Status.DesiredNumberScheduled)
}
}
weight += score
}
return weight
}
func (su *ScoreUtil) externalResourceConverter(rscs map[string]interface{}) []map[string]interface{} {
resources := make([]map[string]interface{}, 0)
for atype, v := range rscs {
resources = append(resources, map[string]interface{}{atype: v})
}
return resources
}
/*
ControlScore:
@input:
ctrlReport - opapolicy.ControlReport object, must contain down the line the Input resources and the output resources
frameworkName - calculate this control according to a given framework weights
ctrl.score = baseScore * SUM_resource (resourceWeight*min(#replicas*replicaweight,1)(nodes if daemonset)
returns control score ***for the input resources***
*/
func (su *ScoreUtil) ControlScore(ctrlReport *opapolicy.ControlReport, frameworkName string) float32 {
aggregatedInputs := make([]map[string]interface{}, 0)
aggregatedResponses := make([]map[string]interface{}, 0)
for _, ruleReport := range ctrlReport.RuleReports {
status, _, _ := ruleReport.GetRuleStatus()
if status != "warning" {
for _, ruleResponse := range ruleReport.RuleResponses {
aggregatedResponses = append(aggregatedResponses, ruleResponse.AlertObject.K8SApiObjects...)
aggregatedResponses = append(aggregatedResponses, su.externalResourceConverter(ruleResponse.AlertObject.ExternalObjects)...)
}
}
aggregatedInputs = append(aggregatedInputs, ruleReport.ListInputResources...)
}
improvementRatio := float32(1)
if ctrls, isOk := su.FrameworksScore[frameworkName]; isOk {
if scoreobj, isOk2 := ctrls[ctrlReport.Name]; isOk2 {
ctrlReport.BaseScore = scoreobj.BaseScore
improvementRatio -= scoreobj.RuntimeImprovementMultiplier
}
} else {
ctrlReport.BaseScore = 1.0
}
ctrlReport.Score = ctrlReport.BaseScore * su.resourceRules(aggregatedResponses)
ctrlReport.ARMOImprovement = ctrlReport.Score * improvementRatio
return ctrlReport.BaseScore * su.resourceRules(aggregatedInputs)
}
func getPostureFrameworksScores(weightPath string) map[string]map[string]ControlScoreWeights {
if len(weightPath) != 0 {
weightPath = weightPath + "/"
}
frameworksScoreMap := make(map[string]map[string]ControlScoreWeights)
dat, err := ioutil.ReadFile(weightPath + "frameworkdict.json")
if err != nil {
return nil
}
if err := json.Unmarshal(dat, &frameworksScoreMap); err != nil {
return nil
}
return frameworksScoreMap
}
func getPostureResourceScores(weightPath string) map[string]float32 {
if len(weightPath) != 0 {
weightPath = weightPath + "/"
}
resourceScoreMap := make(map[string]float32)
dat, err := ioutil.ReadFile(weightPath + "resourcesdict.json")
if err != nil {
return nil
}
if err := json.Unmarshal(dat, &resourceScoreMap); err != nil {
return nil
}
return resourceScoreMap
}
func NewScore(k8sapiobj *k8sinterface.KubernetesApi, configPath string) *ScoreUtil {
if postureScore == nil {
postureScore = &ScoreUtil{
ResourceTypeScores: getPostureResourceScores(configPath),
FrameworksScore: getPostureFrameworksScores(configPath),
configPath: configPath,
}
}
return postureScore
}

View File

@@ -0,0 +1,77 @@
package score
import (
"encoding/json"
"io/ioutil"
"strings"
k8sinterface "github.com/armosec/kubescape/cautils/k8sinterface"
"github.com/armosec/kubescape/cautils/opapolicy"
)
func loadResourcesMock() []map[string]interface{} {
resources := make([]map[string]interface{}, 0)
dat, err := ioutil.ReadFile("resourcemocks.json")
if err != nil {
return resources
}
if err := json.Unmarshal(dat, &resources); err != nil {
return resources
}
return resources
}
func getResouceByType(desiredType string) map[string]interface{} {
rsrcs := loadResourcesMock()
if rsrcs == nil {
return nil
}
for _, v := range rsrcs {
wl := k8sinterface.NewWorkloadObj(v)
if wl != nil {
if strings.ToLower(wl.GetKind()) == desiredType {
return v
}
continue
} else {
for k := range v {
if k == desiredType {
return v
}
}
}
}
return nil
}
func loadFrameworkMock() *opapolicy.FrameworkReport {
report := &opapolicy.FrameworkReport{}
dat, err := ioutil.ReadFile("frameworkmock.json")
if err != nil {
return report
}
if err := json.Unmarshal(dat, &report); err != nil {
return report
}
return report
}
func getMITREFrameworkResultMock() []opapolicy.FrameworkReport {
l := make([]opapolicy.FrameworkReport, 0)
report := loadFrameworkMock()
resources := loadResourcesMock()
if report != nil && resources != nil {
report.ControlReports[0].RuleReports[0].ListInputResources = resources
l = append(l, *report)
}
return l
}

View File

@@ -0,0 +1,65 @@
package score
import (
"testing"
)
func TestFrameworkMock(t *testing.T) {
r := getMITREFrameworkResultMock()
su := NewScore(nil, "")
var epsilon float32 = 0.001
su.Calculate(r)
var sumweights float32 = 0.0
for _, v := range su.ResourceTypeScores {
sumweights += v
}
for _, framework := range r {
if framework.Score < 1 {
t.Errorf("framework %s invalid calculation1: %v", framework.Name, framework)
}
if framework.Score > framework.WCSScore+epsilon {
t.Errorf("framework %s invalid calculation2: %v", framework.Name, framework)
}
if framework.ARMOImprovement > framework.Score+epsilon {
t.Errorf("framework %s invalid calculation3: %v", framework.Name, framework)
}
if framework.ControlReports[0].Score*sumweights <= 0+epsilon {
t.Errorf("framework %s invalid calculation4: %v", framework.Name, framework)
}
}
//
}
func TestDaemonsetRule(t *testing.T) {
desiredType := "daemonset"
r := getResouceByType(desiredType)
if r == nil {
t.Errorf("no %v was found in the mock, should be 1", desiredType)
}
su := NewScore(nil, "")
resources := []map[string]interface{}{r}
weights := su.resourceRules(resources)
expecting := 13 * su.ResourceTypeScores[desiredType]
if weights != expecting {
t.Errorf("no %v unexpected weights were calculated expecting: %v got %v", desiredType, expecting, weights)
}
}
func TestMultipleReplicasRule(t *testing.T) {
desiredType := "deployment"
r := getResouceByType(desiredType)
if r == nil {
t.Errorf("no %v was found in the mock, should be 1", desiredType)
}
su := NewScore(nil, "")
resources := []map[string]interface{}{r}
weights := su.resourceRules(resources)
expecting := 3 * su.ResourceTypeScores[desiredType] * su.ResourceTypeScores["replicaset"]
if weights != expecting {
t.Errorf("no %v unexpected weights were calculated expecting: %v got %v", desiredType, expecting, weights)
}
}

View File

@@ -0,0 +1 @@
package score