Skip to content

Commit

Permalink
Merge pull request #64 from reactiveops/rs/config-updates
Browse files Browse the repository at this point in the history
Restructuring config to match up with docs
  • Loading branch information
robscott authored Apr 22, 2019
2 parents 3ce7e12 + 674696c commit 028ad60
Show file tree
Hide file tree
Showing 9 changed files with 163 additions and 73 deletions.
20 changes: 10 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,16 +54,6 @@ Fairwinds includes experimental support for an optional validating webhook. This

Unfortunately we have not found a way to disply warnings as part of `kubectl` output unless we are rejecting a deployment altogether. That means that any checks with a severity of `warning` will still pass webhook validation, and the only evidence of that warning will either be in the Fairwinds dashboard or the Fairwinds webhook logs.

## CLI Options

* `config`: Specify a location for the Fairwinds config
* `dashboard`: Runs the webserver for Fairwinds dashboard.
* `dashboard-port`: Port for the dashboard webserver (default 8080)
* `webhook`: Runs the webhook webserver.
* `webhook-port`: Port for the webhook webserver (default 9876)
* `disable-webhook-config-installer`: disable the installer in the webhook server, so it won't install webhook configuration resources during bootstrapping
* `kubeconfig`: Paths to a kubeconfig. Only required if out-of-cluster.

## Configuration

Fairwinds 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/reactiveops/fairwinds/blob/master/config.yaml) contains a number of those checks. This repository also includes a sample [full configuration file](https://github.com/reactiveops/fairwinds/blob/master/config-full.yaml) that enables all available checks.
Expand All @@ -78,5 +68,15 @@ Fairwinds validation checks fall into several different categories:
- [Resources](docs/resources.md)
- [Security](docs/security.md)

## CLI Options

* `config`: Specify a location for the Fairwinds config
* `dashboard`: Runs the webserver for Fairwinds dashboard.
* `dashboard-port`: Port for the dashboard webserver (default 8080)
* `webhook`: Runs the webhook webserver.
* `webhook-port`: Port for the webhook webserver (default 9876)
* `disable-webhook-config-installer`: disable the installer in the webhook server, so it won't install webhook configuration resources during bootstrapping
* `kubeconfig`: Paths to a kubeconfig. Only required if out-of-cluster.

## License
Apache License 2.0
5 changes: 2 additions & 3 deletions config-full.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,11 @@ healthChecks:
readinessProbeMissing: warning
livenessProbeMissing: warning
networking:
hostAliasSet: error
hostIPCSet: error
hostNetworkSet: error
hostPIDSet: error
hostPortSet: error
security:
hostIPCSet: error
hostPIDSet: error
runAsRootAllowed: warning
runAsPrivileged: error
notReadOnlyRootFileSystem: warning
Expand Down
15 changes: 7 additions & 8 deletions deploy/all.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -37,16 +37,15 @@ data:
readinessProbeMissing: warning
livenessProbeMissing: warning
networking:
hostAliasSet: error
hostNetworkSet: warning
hostPortSet: warning
security:
hostIPCSet: error
hostNetworkSet: error
hostPIDSet: error
hostPortSet: error
security:
runAsRootAllowed: warning
runAsPrivileged: error
notReadOnlyRootFileSystem: warning
privilegeEscalationAllowed: error
runAsRootAllowed: warning
runAsPrivileged: error
capabilities:
error:
ifAnyAdded:
Expand Down Expand Up @@ -138,7 +137,7 @@ apiVersion: extensions/v1beta1
kind: Deployment
metadata:
annotations:
checksum/config: 'af539094a198a41b47151133fbf9c53309b0b621234185a651af15723f2c92ab'
checksum/config: '5702aca235561630172c22b6b900f5cebd4e82fae60389df18a3537ff82e2f09'
name: fairwinds-dashboard
namespace: fairwinds
labels:
Expand Down Expand Up @@ -200,7 +199,7 @@ apiVersion: extensions/v1beta1
kind: Deployment
metadata:
annotations:
checksum/config: 'af539094a198a41b47151133fbf9c53309b0b621234185a651af15723f2c92ab'
checksum/config: '5702aca235561630172c22b6b900f5cebd4e82fae60389df18a3537ff82e2f09'
name: fairwinds-webhook
namespace: fairwinds
labels:
Expand Down
11 changes: 5 additions & 6 deletions deploy/helm/fairwinds/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,15 @@ config: |
readinessProbeMissing: warning
livenessProbeMissing: warning
networking:
hostAliasSet: error
hostNetworkSet: warning
hostPortSet: warning
security:
hostIPCSet: error
hostNetworkSet: error
hostPIDSet: error
hostPortSet: error
security:
runAsRootAllowed: warning
runAsPrivileged: error
notReadOnlyRootFileSystem: warning
privilegeEscalationAllowed: error
runAsRootAllowed: warning
runAsPrivileged: error
capabilities:
error:
ifAnyAdded:
Expand Down
5 changes: 2 additions & 3 deletions pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,15 +80,14 @@ type ErrorWarningLists struct {

// Networking contains the config for networking validations.
type Networking struct {
HostAliasSet Severity `json:"hostAliasSet"`
HostIPCSet Severity `json:"hostIPCSet"`
HostNetworkSet Severity `json:"hostNetworkSet"`
HostPIDSet Severity `json:"hostPIDSet"`
HostPortSet Severity `json:"hostPortSet"`
}

// Security contains the config for security validations.
type Security struct {
HostIPCSet Severity `json:"hostIPCSet"`
HostPIDSet Severity `json:"hostPIDSet"`
RunAsRootAllowed Severity `json:"runAsRootAllowed"`
RunAsPrivileged Severity `json:"RunAsPrivileged"`
NotReadOnlyRootFileSystem Severity `json:"notReadOnlyRootFileSystem"`
Expand Down
2 changes: 1 addition & 1 deletion pkg/validator/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ func (cv *ContainerValidation) validateNetworking(networkConf *conf.Networking)
}

if hostPortSet {
cv.addFailure(messages.HostPortFailure, networkConf.HostAliasSet, category)
cv.addFailure(messages.HostPortFailure, networkConf.HostPortSet, category)
} else {
cv.addSuccess(messages.HostPortSuccess, category)
}
Expand Down
120 changes: 120 additions & 0 deletions pkg/validator/container_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,126 @@ func TestValidateImage(t *testing.T) {
}
}

func TestValidateNetworking(t *testing.T) {
// Test setup.
emptyConf := conf.Networking{}
standardConf := conf.Networking{
HostPortSet: conf.SeverityWarning,
}
strongConf := conf.Networking{
HostPortSet: conf.SeverityError,
}

emptyCV := ContainerValidation{
Container: &corev1.Container{Name: ""},
ResourceValidation: &ResourceValidation{
Summary: &ResultSummary{},
},
}

badCV := ContainerValidation{
Container: &corev1.Container{
Ports: []corev1.ContainerPort{{
ContainerPort: 3000,
HostPort: 443,
}},
},
ResourceValidation: &ResourceValidation{
Summary: &ResultSummary{},
},
}

goodCV := ContainerValidation{
Container: &corev1.Container{
Ports: []corev1.ContainerPort{{
ContainerPort: 3000,
}},
},
ResourceValidation: &ResourceValidation{
Summary: &ResultSummary{},
},
}

var testCases = []struct {
name string
networkConf conf.Networking
cv ContainerValidation
expectedMessages []*ResultMessage
}{
{
name: "empty ports + empty validation config",
networkConf: emptyConf,
cv: emptyCV,
expectedMessages: []*ResultMessage{},
},
{
name: "empty ports + standard validation config",
networkConf: standardConf,
cv: emptyCV,
expectedMessages: []*ResultMessage{{
Message: "Host port is not configured",
Type: "success",
Category: "Networking",
}},
},
{
name: "empty ports + strong validation config",
networkConf: standardConf,
cv: emptyCV,
expectedMessages: []*ResultMessage{{
Message: "Host port is not configured",
Type: "success",
Category: "Networking",
}},
},
{
name: "host ports + empty validation config",
networkConf: emptyConf,
cv: badCV,
expectedMessages: []*ResultMessage{},
},
{
name: "host ports + standard validation config",
networkConf: standardConf,
cv: badCV,
expectedMessages: []*ResultMessage{{
Message: "Host port should not be configured",
Type: "warning",
Category: "Networking",
}},
},
{
name: "no host ports + standard validation config",
networkConf: standardConf,
cv: goodCV,
expectedMessages: []*ResultMessage{{
Message: "Host port is not configured",
Type: "success",
Category: "Networking",
}},
},
{
name: "host ports + strong validation config",
networkConf: strongConf,
cv: badCV,
expectedMessages: []*ResultMessage{{
Message: "Host port should not be configured",
Type: "error",
Category: "Networking",
}},
},
}

for _, tt := range testCases {
t.Run(tt.name, func(t *testing.T) {
tt.cv = resetCV(tt.cv)
tt.cv.validateNetworking(&tt.networkConf)
assert.Len(t, tt.cv.messages(), len(tt.expectedMessages))
assert.ElementsMatch(t, tt.cv.messages(), tt.expectedMessages)
})
}
}

func TestValidateSecurity(t *testing.T) {
trueVar := true
falseVar := false
Expand Down
44 changes: 9 additions & 35 deletions pkg/validator/pod.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ func ValidatePod(podConf conf.Configuration, pod *corev1.PodSpec) ResourceResult
},
}

pv.validateSecurity(&podConf.Security)
pv.validateNetworking(&podConf.Networking)

pRes := PodResult{
Expand Down Expand Up @@ -70,56 +71,29 @@ func (pv *PodValidation) validateContainers(containers []corev1.Container, pRes
}
}

func (pv *PodValidation) validateNetworking(networkConf *conf.Networking) {
pv.validateHostAlias(networkConf)
pv.validateHostIPC(networkConf)
pv.validateHostPID(networkConf)
pv.validateHostNetwork(networkConf)
}

func (pv *PodValidation) validateHostAlias(networkConf *conf.Networking) {
category := messages.CategoryNetworking
if networkConf.HostAliasSet.IsActionable() {
hostAliasSet := false
for _, alias := range pv.Pod.HostAliases {
if alias.IP != "" && len(alias.Hostnames) == 0 {
hostAliasSet = true
break
}
}
func (pv *PodValidation) validateSecurity(securityConf *conf.Security) {
category := messages.CategorySecurity

if hostAliasSet {
pv.addFailure(messages.HostAliasFailure, networkConf.HostAliasSet, category)
} else {
pv.addSuccess(messages.HostAliasSuccess, category)
}
}
}

func (pv *PodValidation) validateHostIPC(networkConf *conf.Networking) {
category := messages.CategoryNetworking
if networkConf.HostIPCSet.IsActionable() {
if securityConf.HostIPCSet.IsActionable() {
if pv.Pod.HostIPC {
pv.addFailure(messages.HostIPCFailure, networkConf.HostIPCSet, category)
pv.addFailure(messages.HostIPCFailure, securityConf.HostIPCSet, category)
} else {
pv.addSuccess(messages.HostIPCSuccess, category)
}
}
}

func (pv *PodValidation) validateHostPID(networkConf *conf.Networking) {
category := messages.CategoryNetworking
if networkConf.HostPIDSet.IsActionable() {
if securityConf.HostPIDSet.IsActionable() {
if pv.Pod.HostPID {
pv.addFailure(messages.HostPIDFailure, networkConf.HostPIDSet, category)
pv.addFailure(messages.HostPIDFailure, securityConf.HostPIDSet, category)
} else {
pv.addSuccess(messages.HostPIDSuccess, category)
}
}
}

func (pv *PodValidation) validateHostNetwork(networkConf *conf.Networking) {
func (pv *PodValidation) validateNetworking(networkConf *conf.Networking) {
category := messages.CategoryNetworking

if networkConf.HostNetworkSet.IsActionable() {
if pv.Pod.HostNetwork {
pv.addFailure(messages.HostNetworkFailure, networkConf.HostNetworkSet, category)
Expand Down
14 changes: 7 additions & 7 deletions pkg/validator/pod_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,12 @@ import (

func TestValidatePod(t *testing.T) {
c := conf.Configuration{
Security: conf.Security{
HostIPCSet: conf.SeverityError,
HostPIDSet: conf.SeverityError,
},
Networking: conf.Networking{
HostAliasSet: conf.SeverityError,
HostIPCSet: conf.SeverityError,
HostNetworkSet: conf.SeverityWarning,
HostPIDSet: conf.SeverityError,
HostPortSet: conf.SeverityError,
},
}
Expand All @@ -38,15 +39,14 @@ func TestValidatePod(t *testing.T) {
pod := test.MockPod()

expectedSum := ResultSummary{
Successes: uint(9),
Successes: uint(8),
Warnings: uint(0),
Errors: uint(0),
}

expectedMessages := []*ResultMessage{
{Message: "Host alias is not configured", Type: "success", Category: "Networking"},
{Message: "Host IPC is not configured", Type: "success", Category: "Networking"},
{Message: "Host PID is not configured", Type: "success", Category: "Networking"},
{Message: "Host IPC is not configured", Type: "success", Category: "Security"},
{Message: "Host PID is not configured", Type: "success", Category: "Security"},
{Message: "Host network is not configured", Type: "success", Category: "Networking"},
}

Expand Down

0 comments on commit 028ad60

Please sign in to comment.