Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for annotation-based exemptions #227

Merged
merged 4 commits into from
Dec 6, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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