mirror of
https://github.com/FairwindsOps/polaris.git
synced 2026-08-23 22:26:34 +00:00
Merge pull request #64 from reactiveops/rs/config-updates
Restructuring config to match up with docs
This commit is contained in:
@@ -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.
|
||||
@@ -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
|
||||
|
||||
+2
-3
@@ -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
|
||||
|
||||
+8
-9
@@ -37,16 +37,15 @@ data:
|
||||
readinessProbeMissing: warning
|
||||
livenessProbeMissing: warning
|
||||
networking:
|
||||
hostAliasSet: error
|
||||
hostIPCSet: error
|
||||
hostNetworkSet: error
|
||||
hostPIDSet: error
|
||||
hostPortSet: error
|
||||
hostNetworkSet: warning
|
||||
hostPortSet: warning
|
||||
security:
|
||||
runAsRootAllowed: warning
|
||||
runAsPrivileged: error
|
||||
hostIPCSet: error
|
||||
hostPIDSet: error
|
||||
notReadOnlyRootFileSystem: warning
|
||||
privilegeEscalationAllowed: error
|
||||
runAsRootAllowed: warning
|
||||
runAsPrivileged: error
|
||||
capabilities:
|
||||
error:
|
||||
ifAnyAdded:
|
||||
@@ -138,7 +137,7 @@ apiVersion: extensions/v1beta1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
annotations:
|
||||
checksum/config: 'af539094a198a41b47151133fbf9c53309b0b621234185a651af15723f2c92ab'
|
||||
checksum/config: '5702aca235561630172c22b6b900f5cebd4e82fae60389df18a3537ff82e2f09'
|
||||
name: fairwinds-dashboard
|
||||
namespace: fairwinds
|
||||
labels:
|
||||
@@ -200,7 +199,7 @@ apiVersion: extensions/v1beta1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
annotations:
|
||||
checksum/config: 'af539094a198a41b47151133fbf9c53309b0b621234185a651af15723f2c92ab'
|
||||
checksum/config: '5702aca235561630172c22b6b900f5cebd4e82fae60389df18a3537ff82e2f09'
|
||||
name: fairwinds-webhook
|
||||
namespace: fairwinds
|
||||
labels:
|
||||
|
||||
@@ -11,16 +11,15 @@ config: |
|
||||
readinessProbeMissing: warning
|
||||
livenessProbeMissing: warning
|
||||
networking:
|
||||
hostAliasSet: error
|
||||
hostIPCSet: error
|
||||
hostNetworkSet: error
|
||||
hostPIDSet: error
|
||||
hostPortSet: error
|
||||
hostNetworkSet: warning
|
||||
hostPortSet: warning
|
||||
security:
|
||||
runAsRootAllowed: warning
|
||||
runAsPrivileged: error
|
||||
hostIPCSet: error
|
||||
hostPIDSet: error
|
||||
notReadOnlyRootFileSystem: warning
|
||||
privilegeEscalationAllowed: error
|
||||
runAsRootAllowed: warning
|
||||
runAsPrivileged: error
|
||||
capabilities:
|
||||
error:
|
||||
ifAnyAdded:
|
||||
|
||||
@@ -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"`
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
+9
-35
@@ -38,6 +38,7 @@ func ValidatePod(podConf conf.Configuration, pod *corev1.PodSpec) ResourceResult
|
||||
},
|
||||
}
|
||||
|
||||
pv.validateSecurity(&podConf.Security)
|
||||
pv.validateNetworking(&podConf.Networking)
|
||||
|
||||
pRes := PodResult{
|
||||
@@ -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) validateSecurity(securityConf *conf.Security) {
|
||||
category := messages.CategorySecurity
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
}
|
||||
@@ -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"},
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user