Skip to content

Commit

Permalink
Add support for annotation-based exemptions (FairwindsOps#227)
Browse files Browse the repository at this point in the history
* add controllers_to_scan to example config-full

* add support for annotation-based exemptions

* fix lint errors

* add docs
  • Loading branch information
Robert Brennan authored Dec 6, 2019
1 parent 97457d7 commit 67ab987
Show file tree
Hide file tree
Showing 11 changed files with 118 additions and 4 deletions.
22 changes: 21 additions & 1 deletion docs/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,12 @@ be run as a local binary, which will use your kubeconfig to connect to the clust
or run against local YAML files.

## Configuration
Polaris supports a wide range of validations covering a number of Kubernetes best practices.
Here's a [sample configuration file](/examples/config-full.yaml) that includes all currently supported checks.
The [default configuration](/examples/config.yaml) contains a number of those checks.

Polaris supports a wide range of validations covering a number of Kubernetes best practices. Here's a sample configuration file that includes all currently supported checks. The [default configuration](https://github.com/fairwindsops/polaris/blob/master/examples/config.yaml) contains a number of those checks. This repository also includes a sample [full configuration file](https://github.com/fairwindsops/polaris/blob/master/examples/config-full.yaml) that enables all available checks.

### Checks
Each check can be assigned a `severity`. Only checks with a severity of `error` or `warning` will be validated. The results of these validations are visible on the dashboard. In the case of the validating webhook, only failures with a severity of `error` will result in a change being rejected.

Polaris validation checks fall into several different categories:
Expand All @@ -17,6 +20,23 @@ Polaris validation checks fall into several different categories:
- [Resources](check-documentation/resources.md)
- [Security](check-documentation/security.md)

### Exemptions
Exemptions can be added two ways: by annotating a controller, or editing the Polaris config.

To exempt a controller via annotations, use the annotation `polaris.fairwinds.com/exempt=true`, e.g.
```
kubectl annotate deployment my-deployment polaris.fairwinds.com/exempt=true
```

To exempt a controller via the config, you have to specify a list of controller names and a list of rules, e.g.
```yaml
exemptions:
- controllerNames:
- dns-controller
rules:
- hostNetworkSet
```
# Installing
There are several ways to install and use Polaris. Below outline ways to install using `kubectl`, `helm` and `local binary`.

Expand Down
7 changes: 7 additions & 0 deletions examples/config-full.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,13 @@ security:
warning:
ifAnyAddedBeyond:
- NONE
controllers_to_scan:
- Deployments
- StatefulSets
- DaemonSets
- CronJobs
- Jobs
- ReplicationControllers
exemptions:
- controllerNames:
- dns-controller
Expand Down
13 changes: 13 additions & 0 deletions pkg/validator/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,17 @@
package validator

import (
"strings"

conf "github.com/fairwindsops/polaris/pkg/config"
"github.com/fairwindsops/polaris/pkg/kube"
"github.com/fairwindsops/polaris/pkg/validator/controllers"
controller "github.com/fairwindsops/polaris/pkg/validator/controllers"
"github.com/sirupsen/logrus"
)

const exemptionAnnotationKey = "polaris.fairwinds.com/exempt"

// ValidateController validates a single controller, returns a ControllerResult.
func ValidateController(conf conf.Configuration, controller controller.Interface) ControllerResult {
controllerType := controller.GetType()
Expand All @@ -44,6 +48,9 @@ func ValidateControllers(config conf.Configuration, kubeResources *kube.Resource
}

for _, controller := range controllersToAudit {
if !config.DisallowExemptions && hasExemptionAnnotation(controller) {
continue
}
controllerResult := ValidateController(config, controller)
nsResult := nsResults.getNamespaceResult(controller.GetNamespace())
nsResult.Summary.appendResults(*controllerResult.PodResult.Summary)
Expand All @@ -52,3 +59,9 @@ func ValidateControllers(config conf.Configuration, kubeResources *kube.Resource
}
}
}

func hasExemptionAnnotation(ctrl controller.Interface) bool {
annot := ctrl.GetAnnotations()
val := annot[exemptionAnnotationKey]
return strings.ToLower(val) == "true"
}
49 changes: 46 additions & 3 deletions pkg/validator/controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,14 @@ package validator
import (
"testing"

conf "github.com/fairwindsops/polaris/pkg/config"
controller "github.com/fairwindsops/polaris/pkg/validator/controllers"
"github.com/stretchr/testify/assert"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"

conf "github.com/fairwindsops/polaris/pkg/config"
"github.com/fairwindsops/polaris/pkg/kube"
controller "github.com/fairwindsops/polaris/pkg/validator/controllers"
"github.com/fairwindsops/polaris/test"
"github.com/stretchr/testify/assert"
)

func TestValidateController(t *testing.T) {
Expand Down Expand Up @@ -134,3 +136,44 @@ func TestSkipHealthChecks(t *testing.T) {
assert.EqualValues(t, &expectedSum, actualResult.PodResult.Summary)
assert.EqualValues(t, expectedMessages, actualResult.PodResult.ContainerResults[0].Messages)
}

func TestControllerExemptions(t *testing.T) {
c := conf.Configuration{
HealthChecks: conf.HealthChecks{
ReadinessProbeMissing: conf.SeverityError,
LivenessProbeMissing: conf.SeverityWarning,
},
ControllersToScan: []conf.SupportedController{
conf.Deployments,
},
}
resources := &kube.ResourceProvider{
Deployments: []appsv1.Deployment{test.MockDeploy()},
}

expectedSum := ResultSummary{
Totals: CountSummary{
Successes: uint(0),
Warnings: uint(1),
Errors: uint(1),
},
ByCategory: make(map[string]*CountSummary),
}
expectedSum.ByCategory["Health Checks"] = &CountSummary{
Successes: uint(0),
Warnings: uint(1),
Errors: uint(1),
}
nsResults := NamespacedResults{}
ValidateControllers(c, resources, &nsResults)
actualResult := nsResults[""].DeploymentResults[0]
assert.Equal(t, "Deployments", actualResult.Type)
assert.EqualValues(t, &expectedSum, actualResult.PodResult.Summary)

resources.Deployments[0].ObjectMeta.Annotations = map[string]string{
exemptionAnnotationKey: "true",
}
nsResults = NamespacedResults{}
ValidateControllers(c, resources, &nsResults)
assert.Equal(t, (*NamespaceResult)(nil), nsResults[""])
}
5 changes: 5 additions & 0 deletions pkg/validator/controllers/cronjob.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ func (c CronJobController) GetType() config.SupportedController {
return config.CronJobs
}

// GetAnnotations returns the controller's annotations
func (c CronJobController) GetAnnotations() map[string]string {
return c.K8SResource.ObjectMeta.Annotations
}

// NewCronJobController builds a new controller interface for Deployments
func NewCronJobController(originalDeploymentResource kubeAPIBatchV1beta1.CronJob) Interface {
controller := CronJobController{}
Expand Down
5 changes: 5 additions & 0 deletions pkg/validator/controllers/daemonset.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ func (d DaemonSetController) GetPodSpec() *kubeAPICoreV1.PodSpec {
return &d.K8SResource.Spec.Template.Spec
}

// GetAnnotations returns the controller's annotations
func (d DaemonSetController) GetAnnotations() map[string]string {
return d.K8SResource.ObjectMeta.Annotations
}

// GetType returns the supportedcontroller enum type
func (d DaemonSetController) GetType() config.SupportedController {
return config.DaemonSets
Expand Down
5 changes: 5 additions & 0 deletions pkg/validator/controllers/deployment.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ func (d DeploymentController) GetPodSpec() *kubeAPICoreV1.PodSpec {
return &d.K8SResource.Spec.Template.Spec
}

// GetAnnotations returns the controller's annotations
func (d DeploymentController) GetAnnotations() map[string]string {
return d.K8SResource.ObjectMeta.Annotations
}

// GetType returns the supportedcontroller enum type
func (d DeploymentController) GetType() config.SupportedController {
return config.Deployments
Expand Down
1 change: 1 addition & 0 deletions pkg/validator/controllers/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ type Interface interface {
GetPodTemplate() *kubeAPICoreV1.PodTemplateSpec
GetPodSpec() *kubeAPICoreV1.PodSpec
GetType() config.SupportedController
GetAnnotations() map[string]string
}

// GenericController is a base implementation with some free methods for inherited structs
Expand Down
5 changes: 5 additions & 0 deletions pkg/validator/controllers/job.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ func (j JobController) GetPodSpec() *kubeAPICoreV1.PodSpec {
return &j.K8SResource.Spec.Template.Spec
}

// GetAnnotations returns the controller's annotations
func (j JobController) GetAnnotations() map[string]string {
return j.K8SResource.ObjectMeta.Annotations
}

// GetType returns the supportedcontroller enum type
func (j JobController) GetType() config.SupportedController {
return config.Jobs
Expand Down
5 changes: 5 additions & 0 deletions pkg/validator/controllers/replicationcontroller.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ func (r ReplicationControllerController) GetPodSpec() *kubeAPICoreV1.PodSpec {
return &r.K8SResource.Spec.Template.Spec
}

// GetAnnotations returns the controller's annotations
func (r ReplicationControllerController) GetAnnotations() map[string]string {
return r.K8SResource.ObjectMeta.Annotations
}

// GetType returns the supportedcontroller enum type
func (r ReplicationControllerController) GetType() config.SupportedController {
return config.ReplicationControllers
Expand Down
5 changes: 5 additions & 0 deletions pkg/validator/controllers/statefulsets.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ func (s StatefulSetController) GetPodSpec() *kubeAPICoreV1.PodSpec {
return &s.K8SResource.Spec.Template.Spec
}

// GetAnnotations returns the controller's annotations
func (s StatefulSetController) GetAnnotations() map[string]string {
return s.K8SResource.ObjectMeta.Annotations
}

// GetType returns the supportedcontroller enum type
func (s StatefulSetController) GetType() config.SupportedController {
return config.StatefulSets
Expand Down

0 comments on commit 67ab987

Please sign in to comment.