Skip to content

Commit

Permalink
Add exemptions to config (#204)
Browse files Browse the repository at this point in the history
* first pass at adding exemptions

* Update config.yaml

* make config_test more reliable

* add flag to disallow exemptions in dashboard

* add disallow-exemptions flag to CLI

* add comments

* fix exemptions flag

* fix alert on dashboard

* minor style changes
  • Loading branch information
Robert Brennan authored Oct 23, 2019
1 parent b172f61 commit 2b15f11
Show file tree
Hide file tree
Showing 16 changed files with 575 additions and 161 deletions.
70 changes: 70 additions & 0 deletions examples/config-full.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,73 @@ security:
warning:
ifAnyAddedBeyond:
- NONE
exemptions:
- controllerNames:
- dns-controller
- datadog-datadog
- kube-flannel-ds
- kube2iam
- aws-iam-authenticator
- datadog
- kube2iam
rules:
- hostNetworkSet
- controllerNames:
- aws-iam-authenticator
- aws-cluster-autoscaler
- kube-state-metrics
- dns-controller
- external-dns
- dnsmasq
- autoscaler
- kubernetes-dashboard
- install-cni
- kube2iam
rules:
- readinessProbeMissing
- livenessProbeMissing
- controllerNames:
- aws-iam-authenticator
- nginx-ingress-controller
- nginx-ingress-default-backend
- aws-cluster-autoscaler
- kube-state-metrics
- dns-controller
- external-dns
- kubedns
- dnsmasq
- autoscaler
- tiller
- kube2iam
rules:
- runAsRootAllowed
- controllerNames:
- aws-iam-authenticator
- nginx-ingress-controller
- nginx-ingress-default-backend
- aws-cluster-autoscaler
- kube-state-metrics
- dns-controller
- external-dns
- kubedns
- dnsmasq
- autoscaler
- tiller
- kube2iam
rules:
- notReadOnlyRootFileSystem
- controllerNames:
- cert-manager
- dns-controller
- kubedns
- dnsmasq
- autoscaler
rules:
- cpuRequestsMissing
- cpuLimitsMissing
- memoryRequestsMissing
- memoryLimitsMissing
- controllerNames:
- kube2iam
rules:
- runAsPrivileged
70 changes: 70 additions & 0 deletions examples/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,73 @@ controllers_to_scan:
- CronJobs
- Jobs
- ReplicationControllers
exemptions:
- controllerNames:
- dns-controller
- datadog-datadog
- kube-flannel-ds
- kube2iam
- aws-iam-authenticator
- datadog
- kube2iam
rules:
- hostNetworkSet
- controllerNames:
- aws-iam-authenticator
- aws-cluster-autoscaler
- kube-state-metrics
- dns-controller
- external-dns
- dnsmasq
- autoscaler
- kubernetes-dashboard
- install-cni
- kube2iam
rules:
- readinessProbeMissing
- livenessProbeMissing
- controllerNames:
- aws-iam-authenticator
- nginx-ingress-controller
- nginx-ingress-default-backend
- aws-cluster-autoscaler
- kube-state-metrics
- dns-controller
- external-dns
- kubedns
- dnsmasq
- autoscaler
- tiller
- kube2iam
rules:
- runAsRootAllowed
- controllerNames:
- aws-iam-authenticator
- nginx-ingress-controller
- nginx-ingress-default-backend
- aws-cluster-autoscaler
- kube-state-metrics
- dns-controller
- external-dns
- kubedns
- dnsmasq
- autoscaler
- tiller
- kube2iam
rules:
- notReadOnlyRootFileSystem
- controllerNames:
- cert-manager
- dns-controller
- kubedns
- dnsmasq
- autoscaler
rules:
- cpuRequestsMissing
- cpuLimitsMissing
- memoryRequestsMissing
- memoryLimitsMissing
- controllerNames:
- kube2iam
rules:
- runAsPrivileged
12 changes: 5 additions & 7 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,53 +62,51 @@ func main() {
loadAuditFile := flag.String("load-audit-file", "", "Runs the dashboard with data saved from a past audit.")
displayName := flag.String("display-name", "", "An optional identifier for the audit")
configPath := flag.String("config", "", "Location of Polaris configuration file")
disallowExemptions := flag.Bool("disallow-exemptions", false, "Location of Polaris configuration file")
logLevel := flag.String("log-level", logrus.InfoLevel.String(), "Logrus log level")
version := flag.Bool("version", false, "Prints the version of Polaris")
disableWebhookConfigInstaller := flag.Bool("disable-webhook-config-installer", false,
"disable the installer in the webhook server, so it won't install webhook configuration resources during bootstrapping")

flag.Parse()

// if version is specified anywhere, print and exit
if *version {
fmt.Printf("Polaris version %s\n", Version)
os.Exit(0)
}

// Set logging level
parsedLevel, err := logrus.ParseLevel(*logLevel)
if err != nil {
logrus.Errorf("log-level flag has invalid value %s", *logLevel)
} else {
logrus.SetLevel(parsedLevel)
}

// Parse the config file
c, err := conf.ParseFile(*configPath)
if err != nil {
logrus.Errorf("Error parsing config at %s: %v", *configPath, err)
os.Exit(1)
}

// Override display name on reports if defined in CLI flags
if *displayName != "" {
c.DisplayName = *displayName
}

// default to run as audit if no "run-mode" is defined
if *disallowExemptions {
c.DisallowExemptions = true
}

if !*dashboard && !*webhook && !*audit {
*audit = true
}

// perform the action for the desired "run-mode"
if *webhook {
startWebhookServer(c, *disableWebhookConfigInstaller, *webhookPort)
} else if *dashboard {
startDashboardServer(c, *auditPath, *loadAuditFile, *dashboardPort, *dashboardBasePath)
} else if *audit {
auditData := runAndReportAudit(c, *auditPath, *auditOutputFile, *auditOutputURL, *auditOutputFormat)

// exit code 3 if any errors in the audit else if score is under desired minimum, exit 4
if *setExitCode && auditData.ClusterSummary.Results.Totals.Errors > 0 {
logrus.Infof("%d errors found in audit", auditData.ClusterSummary.Results.Totals.Errors)
os.Exit(3)
Expand Down
22 changes: 15 additions & 7 deletions pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,21 @@ import (

// Configuration contains all of the config for the validation checks.
type Configuration struct {
DisplayName string `json:"displayName"`
Resources Resources `json:"resources"`
HealthChecks HealthChecks `json:"healthChecks"`
Images Images `json:"images"`
Networking Networking `json:"networking"`
Security Security `json:"security"`
ControllersToScan []SupportedController `json:"controllers_to_scan"`
DisplayName string `json:"displayName"`
Resources Resources `json:"resources"`
HealthChecks HealthChecks `json:"healthChecks"`
Images Images `json:"images"`
Networking Networking `json:"networking"`
Security Security `json:"security"`
ControllersToScan []SupportedController `json:"controllers_to_scan"`
Exemptions []Exemption `json:"exemptions"`
DisallowExemptions bool `json:"disallowExemptions"`
}

// Exemption represents an exemption to normal rules
type Exemption struct {
Rules []string `json:"rules"`
ControllerNames []string `json:"controllerNames"`
}

// Resources contains config for resource requests and limits.
Expand Down
2 changes: 2 additions & 0 deletions pkg/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"net/http"
"regexp"
"testing"
"time"

"github.com/stretchr/testify/assert"
"k8s.io/apimachinery/pkg/api/resource"
Expand Down Expand Up @@ -146,6 +147,7 @@ func TestConfigFromURL(t *testing.T) {
log.Fatalf("ListenAndServe(): %s", err)
}
}()
time.Sleep(time.Second)

parsedConf, err = ParseFile("http://localhost:8081/exampleURL")
assert.NoError(t, err, "Expected no error when parsing YAML from URL")
Expand Down
31 changes: 31 additions & 0 deletions pkg/config/exemptions.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package config

import (
"reflect"
)

// IsActionable determines whether a check is actionable given the current configuration
func (conf *Configuration) IsActionable(subConf interface{}, ruleName, controllerName string) bool {
ruleID := GetIDFromField(subConf, ruleName)
subConfRef := reflect.ValueOf(subConf)
severity, ok := reflect.Indirect(subConfRef).FieldByName(ruleName).Interface().(Severity)
if ok && !severity.IsActionable() {
return false
}
if conf.DisallowExemptions {
return true
}
for _, example := range conf.Exemptions {
for _, rule := range example.Rules {
if rule != ruleID {
continue
}
for _, controller := range example.ControllerNames {
if controller == controllerName {
return false
}
}
}
}
return true
}
5 changes: 3 additions & 2 deletions pkg/validator/ids.go → pkg/config/ids.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
package validator
package config

import (
"reflect"
)

func getIDFromField(config interface{}, name string) string {
// GetIDFromField returns the JSON key associated with a particular field, which serves as the check ID.
func GetIDFromField(config interface{}, name string) string {
t := reflect.TypeOf(config)
field, ok := t.FieldByName(name)
if !ok {
Expand Down
10 changes: 10 additions & 0 deletions pkg/dashboard/assets/css/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,13 @@ body {
text-decoration: underline;
}

.exemption-alert {
margin-top: 15px;
padding: 15px;
border: 1px solid #f26c21;
border-radius: 2px;
}
.exemption-alert .fa-exclamation {
margin-right: 10px;
color: #f26c21;
}
Loading

0 comments on commit 2b15f11

Please sign in to comment.