mirror of
https://github.com/FairwindsOps/polaris.git
synced 2026-05-08 18:26:43 +00:00
Refactor validation
This commit is contained in:
11
main.go
11
main.go
@@ -107,11 +107,12 @@ func main() {
|
||||
} else if *audit {
|
||||
auditData := runAndReportAudit(c, *auditPath, *auditOutputFile, *auditOutputURL, *auditOutputFormat)
|
||||
|
||||
if *setExitCode && auditData.ClusterSummary.Results.Totals.Errors > 0 {
|
||||
logrus.Infof("%d errors found in audit", auditData.ClusterSummary.Results.Totals.Errors)
|
||||
numErrors := auditData.GetSummary().Errors
|
||||
if *setExitCode && numErrors > 0 {
|
||||
logrus.Infof("%d errors found in audit", numErrors)
|
||||
os.Exit(3)
|
||||
} else if *minScore != 0 && auditData.ClusterSummary.Score < uint(*minScore) {
|
||||
logrus.Infof("Audit score of %d is less than the provided minimum of %d", auditData.ClusterSummary.Score, *minScore)
|
||||
} else if *minScore != 0 && auditData.GetSummary().GetScore() < uint(*minScore) {
|
||||
logrus.Infof("Audit score of %d is less than the provided minimum of %d", auditData.GetSummary().GetScore(), *minScore)
|
||||
os.Exit(4)
|
||||
}
|
||||
}
|
||||
@@ -228,7 +229,7 @@ func runAndReportAudit(c conf.Configuration, auditPath string, outputFile string
|
||||
|
||||
var outputBytes []byte
|
||||
if outputFormat == "score" {
|
||||
outputBytes = []byte(fmt.Sprintf("%d\n", auditData.ClusterSummary.Score))
|
||||
outputBytes = []byte(fmt.Sprintf("%d\n", auditData.GetSummary().GetScore()))
|
||||
} else if outputFormat == "yaml" {
|
||||
jsonBytes, err := json.Marshal(auditData)
|
||||
if err == nil {
|
||||
|
||||
@@ -90,15 +90,14 @@ type templateData struct {
|
||||
// GetBaseTemplate puts together the dashboard template. Individual pieces can be overridden before rendering.
|
||||
func GetBaseTemplate(name string) (*template.Template, error) {
|
||||
tmpl := template.New(name).Funcs(template.FuncMap{
|
||||
"getWarningWidth": getWarningWidth,
|
||||
"getSuccessWidth": getSuccessWidth,
|
||||
"getWeatherIcon": getWeatherIcon,
|
||||
"getWeatherText": getWeatherText,
|
||||
"getGrade": getGrade,
|
||||
"getIcon": getIcon,
|
||||
"getCategoryLink": getCategoryLink,
|
||||
"getCategoryInfo": getCategoryInfo,
|
||||
"getAllControllerResults": getAllControllerResults,
|
||||
"getWarningWidth": getWarningWidth,
|
||||
"getSuccessWidth": getSuccessWidth,
|
||||
"getWeatherIcon": getWeatherIcon,
|
||||
"getWeatherText": getWeatherText,
|
||||
"getGrade": getGrade,
|
||||
"getIcon": getIcon,
|
||||
"getCategoryLink": getCategoryLink,
|
||||
"getCategoryInfo": getCategoryInfo,
|
||||
})
|
||||
|
||||
templateFileNames := []string{
|
||||
|
||||
@@ -21,10 +21,6 @@ import (
|
||||
"github.com/fairwindsops/polaris/pkg/validator"
|
||||
)
|
||||
|
||||
func getAllControllerResults(nr validator.NamespaceResult) []validator.ControllerResult {
|
||||
return nr.GetAllControllerResults()
|
||||
}
|
||||
|
||||
func getWarningWidth(counts validator.CountSummary, fullWidth int) uint {
|
||||
return uint(float64(counts.Successes+counts.Warnings) / float64(counts.Successes+counts.Warnings+counts.Errors) * float64(fullWidth))
|
||||
}
|
||||
|
||||
@@ -19,40 +19,8 @@ import (
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
)
|
||||
|
||||
// ContainerValidation tracks validation failures associated with a Container.
|
||||
type ContainerValidation struct {
|
||||
*ResourceValidation
|
||||
Container *corev1.Container
|
||||
IsInitContainer bool
|
||||
parentPodSpec corev1.PodSpec
|
||||
}
|
||||
|
||||
// ValidateContainer validates that each pod conforms to the Polaris config, returns a ResourceResult.
|
||||
// FIXME When validating a container, there are some things in a container spec
|
||||
// that can be affected by the podSpec. This means we need a copy of the
|
||||
// relevant podSpec in order to check certain aspects of a containerSpec.
|
||||
// Perhaps there is a more ideal solution instead of attaching a parent
|
||||
// podSpec to every container Validation struct...
|
||||
func ValidateContainer(container *corev1.Container, parentPodResult *PodResult, conf *config.Configuration, controllerName string, controllerType config.SupportedController, isInit bool) ContainerResult {
|
||||
cv := ContainerValidation{
|
||||
Container: container,
|
||||
ResourceValidation: &ResourceValidation{},
|
||||
IsInitContainer: isInit,
|
||||
}
|
||||
|
||||
// Support initializing
|
||||
// FIXME This is a product of pulling in the podSpec, ideally we'd never
|
||||
// expect this be nil but our tests have conditions in which the
|
||||
// parent podResult isn't initialized in this ContainerValidation
|
||||
// struct.
|
||||
if parentPodResult == nil {
|
||||
// initialize a blank pod spec
|
||||
cv.parentPodSpec = corev1.PodSpec{}
|
||||
} else {
|
||||
cv.parentPodSpec = parentPodResult.podSpec
|
||||
}
|
||||
|
||||
err := applyContainerSchemaChecks(conf, controllerName, controllerType, &cv)
|
||||
func ValidateContainer(conf *config.Configuration, basePod *corev1.PodSpec, container *corev1.Container, controllerName string, controllerType config.SupportedController, isInit bool) ContainerResult {
|
||||
results, err := applyContainerSchemaChecks(conf, basePod, container, controllerName, controllerType, isInit)
|
||||
// FIXME: don't panic
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@@ -60,9 +28,17 @@ func ValidateContainer(container *corev1.Container, parentPodResult *PodResult,
|
||||
|
||||
cRes := ContainerResult{
|
||||
Name: container.Name,
|
||||
Messages: cv.messages(),
|
||||
Summary: cv.summary(),
|
||||
Messages: results,
|
||||
}
|
||||
|
||||
return cRes
|
||||
}
|
||||
|
||||
func ValidateContainers(conf *config.Configuration, basePod *corev1.PodSpec, containers []corev1.Container, controllerName string, controllerType config.SupportedController, isInit bool) []ContainerResult {
|
||||
results := []ContainerResult{}
|
||||
for _, container := range containers {
|
||||
cRes := ValidateContainer(conf, basePod, &container, controllerName, controllerType, isInit)
|
||||
results = append(results, cRes)
|
||||
}
|
||||
return results
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -21,13 +21,12 @@ import (
|
||||
"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 {
|
||||
func ValidateController(conf *conf.Configuration, controller controller.Interface) ControllerResult {
|
||||
controllerType := controller.GetType()
|
||||
pod := controller.GetPodSpec()
|
||||
podResult := ValidatePod(conf, pod, controller.GetName(), controllerType)
|
||||
@@ -41,24 +40,21 @@ func ValidateController(conf conf.Configuration, controller controller.Interface
|
||||
|
||||
// ValidateControllers validates that each deployment conforms to the Polaris config,
|
||||
// builds a list of ResourceResults organized by namespace.
|
||||
func ValidateControllers(config conf.Configuration, kubeResources *kube.ResourceProvider, nsResults *NamespacedResults) {
|
||||
func ValidateControllers(config *conf.Configuration, kubeResources *kube.ResourceProvider) []ControllerResult {
|
||||
var controllersToAudit []controller.Interface
|
||||
for _, supportedControllers := range config.ControllersToScan {
|
||||
loadedControllers, _ := controllers.LoadControllersByType(supportedControllers, kubeResources)
|
||||
controllersToAudit = append(controllersToAudit, loadedControllers...)
|
||||
}
|
||||
|
||||
results := []ControllerResult{}
|
||||
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)
|
||||
if err := nsResult.AddResult(controller.GetType(), controllerResult); err != nil {
|
||||
logrus.Errorf("Internal Error: Failed to add a grouped result: %s", err)
|
||||
}
|
||||
results = append(results, ValidateController(config, controller))
|
||||
}
|
||||
return results
|
||||
}
|
||||
|
||||
func hasExemptionAnnotation(ctrl controller.Interface) bool {
|
||||
|
||||
@@ -35,30 +35,22 @@ func TestValidateController(t *testing.T) {
|
||||
},
|
||||
}
|
||||
deployment := controller.NewDeploymentController(test.MockDeploy())
|
||||
expectedSum := ResultSummary{
|
||||
Totals: CountSummary{
|
||||
Successes: uint(2),
|
||||
Warnings: uint(0),
|
||||
Errors: uint(0),
|
||||
},
|
||||
ByCategory: make(map[string]*CountSummary),
|
||||
}
|
||||
expectedSum.ByCategory["Security"] = &CountSummary{
|
||||
expectedSum := CountSummary{
|
||||
Successes: uint(2),
|
||||
Warnings: uint(0),
|
||||
Errors: uint(0),
|
||||
}
|
||||
|
||||
expectedMessages := []*ResultMessage{
|
||||
{ID: "hostIPCSet", Message: "Host IPC is not configured", Type: "success", Category: "Security"},
|
||||
{ID: "hostPIDSet", Message: "Host PID is not configured", Type: "success", Category: "Security"},
|
||||
expectedMessages := ResultSet{
|
||||
"hostIPCSet": {ID: "hostIPCSet", Message: "Host IPC is not configured", Type: "success", Severity: "error", Category: "Security"},
|
||||
"hostPIDSet": {ID: "hostPIDSet", Message: "Host PID is not configured", Type: "success", Severity: "error", Category: "Security"},
|
||||
}
|
||||
|
||||
actualResult := ValidateController(c, deployment)
|
||||
actualResult := ValidateController(&c, deployment)
|
||||
|
||||
assert.Equal(t, "Deployments", actualResult.Type)
|
||||
assert.Equal(t, 1, len(actualResult.PodResult.ContainerResults), "should be equal")
|
||||
assert.EqualValues(t, &expectedSum, actualResult.PodResult.Summary)
|
||||
assert.EqualValues(t, expectedSum, actualResult.GetSummary())
|
||||
assert.EqualValues(t, expectedMessages, actualResult.PodResult.Messages)
|
||||
}
|
||||
|
||||
@@ -80,60 +72,46 @@ func TestSkipHealthChecks(t *testing.T) {
|
||||
deploymentBase := test.MockDeploy()
|
||||
deploymentBase.Spec.Template.Spec.InitContainers = []corev1.Container{test.MockContainer("test")}
|
||||
deployment := controller.NewDeploymentController(deploymentBase)
|
||||
expectedSum := ResultSummary{
|
||||
Totals: CountSummary{
|
||||
Successes: uint(0),
|
||||
Warnings: uint(1),
|
||||
Errors: uint(1),
|
||||
},
|
||||
ByCategory: make(map[string]*CountSummary),
|
||||
}
|
||||
expectedSum.ByCategory["Health Checks"] = &CountSummary{
|
||||
expectedSum := CountSummary{
|
||||
Successes: uint(0),
|
||||
Warnings: uint(1),
|
||||
Errors: uint(1),
|
||||
}
|
||||
expectedMessages := []*ResultMessage{
|
||||
{ID: "readinessProbeMissing", Message: "Readiness probe should be configured", Type: "error", Category: "Health Checks"},
|
||||
{ID: "livenessProbeMissing", Message: "Liveness probe should be configured", Type: "warning", Category: "Health Checks"},
|
||||
expectedMessages := ResultSet{
|
||||
"readinessProbeMissing": {ID: "readinessProbeMissing", Message: "Readiness probe should be configured", Type: "failure", Severity: "error", Category: "Health Checks"},
|
||||
"livenessProbeMissing": {ID: "livenessProbeMissing", Message: "Liveness probe should be configured", Type: "failure", Severity: "warning", Category: "Health Checks"},
|
||||
}
|
||||
actualResult := ValidateController(c, deployment)
|
||||
actualResult := ValidateController(&c, deployment)
|
||||
assert.Equal(t, "Deployments", actualResult.Type)
|
||||
assert.Equal(t, 2, len(actualResult.PodResult.ContainerResults), "should be equal")
|
||||
assert.EqualValues(t, &expectedSum, actualResult.PodResult.Summary)
|
||||
assert.EqualValues(t, []*ResultMessage{}, actualResult.PodResult.ContainerResults[0].Messages)
|
||||
assert.EqualValues(t, expectedSum, actualResult.GetSummary())
|
||||
assert.EqualValues(t, ResultSet{}, actualResult.PodResult.ContainerResults[0].Messages)
|
||||
assert.EqualValues(t, expectedMessages, actualResult.PodResult.ContainerResults[1].Messages)
|
||||
|
||||
job := controller.NewJobController(test.MockJob())
|
||||
expectedSum = ResultSummary{
|
||||
Totals: CountSummary{
|
||||
Successes: uint(0),
|
||||
Warnings: uint(0),
|
||||
Errors: uint(0),
|
||||
},
|
||||
ByCategory: make(map[string]*CountSummary),
|
||||
expectedSum = CountSummary{
|
||||
Successes: uint(0),
|
||||
Warnings: uint(0),
|
||||
Errors: uint(0),
|
||||
}
|
||||
expectedMessages = []*ResultMessage{}
|
||||
actualResult = ValidateController(c, job)
|
||||
expectedMessages = ResultSet{}
|
||||
actualResult = ValidateController(&c, job)
|
||||
assert.Equal(t, "Jobs", actualResult.Type)
|
||||
assert.Equal(t, 1, len(actualResult.PodResult.ContainerResults), "should be equal")
|
||||
assert.EqualValues(t, &expectedSum, actualResult.PodResult.Summary)
|
||||
assert.EqualValues(t, expectedSum, actualResult.GetSummary())
|
||||
assert.EqualValues(t, expectedMessages, actualResult.PodResult.ContainerResults[0].Messages)
|
||||
|
||||
cronjob := controller.NewCronJobController(test.MockCronJob())
|
||||
expectedSum = ResultSummary{
|
||||
Totals: CountSummary{
|
||||
Successes: uint(0),
|
||||
Warnings: uint(0),
|
||||
Errors: uint(0),
|
||||
},
|
||||
ByCategory: make(map[string]*CountSummary),
|
||||
expectedSum = CountSummary{
|
||||
Successes: uint(0),
|
||||
Warnings: uint(0),
|
||||
Errors: uint(0),
|
||||
}
|
||||
expectedMessages = []*ResultMessage{}
|
||||
actualResult = ValidateController(c, cronjob)
|
||||
expectedMessages = ResultSet{}
|
||||
actualResult = ValidateController(&c, cronjob)
|
||||
assert.Equal(t, "CronJobs", actualResult.Type)
|
||||
assert.Equal(t, 1, len(actualResult.PodResult.ContainerResults), "should be equal")
|
||||
assert.EqualValues(t, &expectedSum, actualResult.PodResult.Summary)
|
||||
assert.EqualValues(t, expectedSum, actualResult.GetSummary())
|
||||
assert.EqualValues(t, expectedMessages, actualResult.PodResult.ContainerResults[0].Messages)
|
||||
}
|
||||
|
||||
@@ -151,29 +129,19 @@ func TestControllerExemptions(t *testing.T) {
|
||||
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{
|
||||
expectedSum := 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)
|
||||
actualResults := ValidateControllers(&c, resources)
|
||||
assert.Equal(t, 1, len(actualResults))
|
||||
assert.Equal(t, "Deployments", actualResults[0].Type)
|
||||
assert.EqualValues(t, expectedSum, actualResults[0].GetSummary())
|
||||
|
||||
resources.Deployments[0].ObjectMeta.Annotations = map[string]string{
|
||||
exemptionAnnotationKey: "true",
|
||||
}
|
||||
nsResults = NamespacedResults{}
|
||||
ValidateControllers(c, resources, &nsResults)
|
||||
assert.Equal(t, (*NamespaceResult)(nil), nsResults[""])
|
||||
actualResults = ValidateControllers(&c, resources)
|
||||
assert.Equal(t, 0, len(actualResults))
|
||||
}
|
||||
|
||||
@@ -1,36 +1,36 @@
|
||||
package validator
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
conf "github.com/fairwindsops/polaris/pkg/config"
|
||||
"github.com/fairwindsops/polaris/pkg/kube"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
apiMachineryYAML "k8s.io/apimachinery/pkg/util/yaml"
|
||||
)
|
||||
|
||||
// RunAudit runs a full Polaris audit and returns an AuditData object
|
||||
func RunAudit(config conf.Configuration, kubeResources *kube.ResourceProvider) (AuditData, error) {
|
||||
nsResults := NamespacedResults{}
|
||||
ValidateControllers(config, kubeResources, &nsResults)
|
||||
|
||||
clusterResults := ResultSummary{}
|
||||
|
||||
// Aggregate all summary counts to get a clusterwide count.
|
||||
for _, result := range nsResults.GetAllControllerResults() {
|
||||
clusterResults.appendResults(*result.PodResult.Summary)
|
||||
}
|
||||
|
||||
displayName := config.DisplayName
|
||||
if displayName == "" {
|
||||
displayName = kubeResources.SourceName
|
||||
}
|
||||
|
||||
results := ValidateControllers(&config, kubeResources)
|
||||
|
||||
auditData := AuditData{
|
||||
PolarisOutputVersion: PolarisOutputVersion,
|
||||
AuditTime: kubeResources.CreationTime.Format(time.RFC3339),
|
||||
SourceType: kubeResources.SourceType,
|
||||
SourceName: kubeResources.SourceName,
|
||||
DisplayName: displayName,
|
||||
ClusterSummary: ClusterSummary{
|
||||
ClusterInfo: ClusterInfo{
|
||||
Version: kubeResources.ServerVersion,
|
||||
Nodes: len(kubeResources.Nodes),
|
||||
Pods: len(kubeResources.Pods),
|
||||
@@ -41,10 +41,39 @@ func RunAudit(config conf.Configuration, kubeResources *kube.ResourceProvider) (
|
||||
Jobs: len(kubeResources.Jobs),
|
||||
CronJobs: len(kubeResources.CronJobs),
|
||||
ReplicationControllers: len(kubeResources.ReplicationControllers),
|
||||
Results: clusterResults,
|
||||
Score: clusterResults.Totals.GetScore(),
|
||||
},
|
||||
NamespacedResults: nsResults,
|
||||
Results: results,
|
||||
}
|
||||
return auditData, nil
|
||||
}
|
||||
|
||||
// ReadAuditFromFile reads the data from a past audit stored in a JSON or YAML file.
|
||||
func ReadAuditFromFile(fileName string) AuditData {
|
||||
auditData := AuditData{}
|
||||
oldFileBytes, err := ioutil.ReadFile(fileName)
|
||||
if err != nil {
|
||||
logrus.Errorf("Unable to read contents of loaded file: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
auditData, err = ParseAudit(oldFileBytes)
|
||||
if err != nil {
|
||||
logrus.Errorf("Error parsing file contents into auditData: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
return auditData
|
||||
}
|
||||
|
||||
// ParseAudit decodes either a YAML or JSON file and returns AuditData.
|
||||
func ParseAudit(oldFileBytes []byte) (AuditData, error) {
|
||||
reader := bytes.NewReader(oldFileBytes)
|
||||
conf := AuditData{}
|
||||
d := apiMachineryYAML.NewYAMLOrJSONDecoder(reader, 4096)
|
||||
for {
|
||||
if err := d.Decode(&conf); err != nil {
|
||||
if err == io.EOF {
|
||||
return conf, nil
|
||||
}
|
||||
return conf, fmt.Errorf("Decoding config failed: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,16 +30,7 @@ func TestGetTemplateData(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
// TODO: split out the logic for calculating summaries into another set of tests
|
||||
sum := ResultSummary{
|
||||
Totals: CountSummary{
|
||||
Successes: uint(0),
|
||||
Warnings: uint(4),
|
||||
Errors: uint(4),
|
||||
},
|
||||
ByCategory: CategorySummary{},
|
||||
}
|
||||
sum.ByCategory["Health Checks"] = &CountSummary{
|
||||
sum := CountSummary{
|
||||
Successes: uint(0),
|
||||
Warnings: uint(4),
|
||||
Errors: uint(4),
|
||||
@@ -48,17 +39,33 @@ func TestGetTemplateData(t *testing.T) {
|
||||
actualAudit, err := RunAudit(c, resources)
|
||||
assert.Equal(t, err, nil, "error should be nil")
|
||||
|
||||
assert.EqualValues(t, sum, actualAudit.ClusterSummary.Results)
|
||||
assert.EqualValues(t, sum, actualAudit.GetSummary())
|
||||
assert.Equal(t, actualAudit.SourceType, "Cluster", "should be from a cluster")
|
||||
assert.Equal(t, actualAudit.SourceName, "test", "should be from a cluster")
|
||||
|
||||
assert.Equal(t, 1, len(actualAudit.NamespacedResults["test"].DeploymentResults), "should be equal")
|
||||
assert.Equal(t, 1, len(actualAudit.NamespacedResults["test"].DeploymentResults), "should be equal")
|
||||
assert.Equal(t, 1, len(actualAudit.NamespacedResults["test"].DeploymentResults[0].PodResult.ContainerResults), "should be equal")
|
||||
assert.Equal(t, 2, len(actualAudit.NamespacedResults["test"].DeploymentResults[0].PodResult.ContainerResults[0].Messages), "should be equal")
|
||||
assert.Equal(t, 6, len(actualAudit.Results))
|
||||
|
||||
assert.Equal(t, 1, len(actualAudit.NamespacedResults["test"].StatefulSetResults), "should be equal")
|
||||
assert.Equal(t, 1, len(actualAudit.NamespacedResults["test"].StatefulSetResults), "should be equal")
|
||||
assert.Equal(t, 1, len(actualAudit.NamespacedResults["test"].StatefulSetResults[0].PodResult.ContainerResults), "should be equal")
|
||||
assert.Equal(t, 2, len(actualAudit.NamespacedResults["test"].StatefulSetResults[0].PodResult.ContainerResults[0].Messages), "should be equal")
|
||||
assert.Equal(t, "Deployments", actualAudit.Results[0].Type)
|
||||
assert.Equal(t, 1, len(actualAudit.Results[0].PodResult.ContainerResults))
|
||||
assert.Equal(t, 2, len(actualAudit.Results[0].PodResult.ContainerResults[0].Messages))
|
||||
|
||||
assert.Equal(t, "StatefulSets", actualAudit.Results[1].Type)
|
||||
assert.Equal(t, 1, len(actualAudit.Results[1].PodResult.ContainerResults))
|
||||
assert.Equal(t, 2, len(actualAudit.Results[1].PodResult.ContainerResults[0].Messages))
|
||||
|
||||
assert.Equal(t, "DaemonSets", actualAudit.Results[2].Type)
|
||||
assert.Equal(t, 1, len(actualAudit.Results[2].PodResult.ContainerResults))
|
||||
assert.Equal(t, 2, len(actualAudit.Results[2].PodResult.ContainerResults[0].Messages))
|
||||
|
||||
assert.Equal(t, "Jobs", actualAudit.Results[3].Type)
|
||||
assert.Equal(t, 1, len(actualAudit.Results[3].PodResult.ContainerResults))
|
||||
assert.Equal(t, 0, len(actualAudit.Results[3].PodResult.ContainerResults[0].Messages))
|
||||
|
||||
assert.Equal(t, "CronJobs", actualAudit.Results[4].Type)
|
||||
assert.Equal(t, 1, len(actualAudit.Results[4].PodResult.ContainerResults))
|
||||
assert.Equal(t, 0, len(actualAudit.Results[4].PodResult.ContainerResults[0].Messages))
|
||||
|
||||
assert.Equal(t, "ReplicationController", actualAudit.Results[5].Type)
|
||||
assert.Equal(t, 1, len(actualAudit.Results[5].PodResult.ContainerResults))
|
||||
assert.Equal(t, 2, len(actualAudit.Results[5].PodResult.ContainerResults[0].Messages))
|
||||
}
|
||||
|
||||
183
pkg/validator/output.go
Normal file
183
pkg/validator/output.go
Normal file
@@ -0,0 +1,183 @@
|
||||
// Copyright 2019 FairwindsOps Inc
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package validator
|
||||
|
||||
import (
|
||||
"github.com/fairwindsops/polaris/pkg/config"
|
||||
)
|
||||
|
||||
const (
|
||||
// PolarisOutputVersion is the version of the current output structure
|
||||
PolarisOutputVersion = "1.0"
|
||||
)
|
||||
|
||||
// AuditData contains all the data from a full Polaris audit
|
||||
type AuditData struct {
|
||||
PolarisOutputVersion string
|
||||
AuditTime string
|
||||
SourceType string
|
||||
SourceName string
|
||||
DisplayName string
|
||||
ClusterInfo ClusterInfo
|
||||
Results []ControllerResult
|
||||
}
|
||||
|
||||
// ClusterInfo contains Polaris results as well as some high-level stats
|
||||
type ClusterInfo struct {
|
||||
Version string
|
||||
Nodes int
|
||||
Pods int
|
||||
Namespaces int
|
||||
Deployments int
|
||||
StatefulSets int
|
||||
DaemonSets int
|
||||
Jobs int
|
||||
CronJobs int
|
||||
ReplicationControllers int
|
||||
}
|
||||
|
||||
// MessageType represents the type of Message
|
||||
type MessageType string
|
||||
|
||||
const (
|
||||
// MessageTypeSuccess indicates a validation success
|
||||
MessageTypeSuccess MessageType = "success"
|
||||
|
||||
// MessageTypeWarning indicates a validation failure
|
||||
MessageTypeFailure MessageType = "failure"
|
||||
)
|
||||
|
||||
// ResultMessage is the result of a given check
|
||||
type ResultMessage struct {
|
||||
ID string
|
||||
Message string
|
||||
Type MessageType
|
||||
Severity config.Severity
|
||||
Category string
|
||||
}
|
||||
|
||||
// ResultSet contiains the results for a set of checks
|
||||
type ResultSet map[string]ResultMessage
|
||||
|
||||
// ControllerResult provides results for a controller
|
||||
type ControllerResult struct {
|
||||
Name string
|
||||
Type string
|
||||
Messages ResultSet
|
||||
PodResult PodResult
|
||||
}
|
||||
|
||||
// PodResult provides a list of validation messages for each pod.
|
||||
type PodResult struct {
|
||||
Name string
|
||||
Messages ResultSet
|
||||
ContainerResults []ContainerResult
|
||||
}
|
||||
|
||||
// ContainerResult provides a list of validation messages for each container.
|
||||
type ContainerResult struct {
|
||||
Name string
|
||||
Messages ResultSet
|
||||
}
|
||||
|
||||
// CountSummary provides a high level overview of success, warnings, and errors.
|
||||
type CountSummary struct {
|
||||
Successes uint
|
||||
Warnings uint
|
||||
Errors uint
|
||||
}
|
||||
|
||||
// GetScore returns an overall score in [0, 100] for the CountSummary
|
||||
func (cs CountSummary) GetScore() uint {
|
||||
total := (cs.Successes * 2) + cs.Warnings + (cs.Errors * 2)
|
||||
return uint((float64(cs.Successes*2) / float64(total)) * 100)
|
||||
}
|
||||
|
||||
func (cs *CountSummary) AddSummary(other CountSummary) {
|
||||
cs.Successes += other.Successes
|
||||
cs.Warnings += other.Warnings
|
||||
cs.Errors += other.Errors
|
||||
}
|
||||
|
||||
// CategorySummary provides a map from category name to a CountSummary
|
||||
type CategorySummary map[string]*CountSummary
|
||||
|
||||
func (rs ResultSet) GetSummary() CountSummary {
|
||||
cs := CountSummary{}
|
||||
for _, result := range rs {
|
||||
if result.Type == MessageTypeFailure {
|
||||
if result.Severity == config.SeverityWarning {
|
||||
cs.Warnings += 1
|
||||
} else {
|
||||
cs.Errors += 1
|
||||
}
|
||||
} else {
|
||||
cs.Successes += 1
|
||||
}
|
||||
}
|
||||
return cs
|
||||
}
|
||||
|
||||
func (p PodResult) GetSummary() CountSummary {
|
||||
summary := p.Messages.GetSummary()
|
||||
for _, containerResult := range p.ContainerResults {
|
||||
summary.AddSummary(containerResult.Messages.GetSummary())
|
||||
}
|
||||
return summary
|
||||
}
|
||||
|
||||
func (c ControllerResult) GetSummary() CountSummary {
|
||||
summary := c.Messages.GetSummary()
|
||||
summary.AddSummary(c.PodResult.GetSummary())
|
||||
return summary
|
||||
}
|
||||
|
||||
func (a AuditData) GetSummary() CountSummary {
|
||||
summary := CountSummary{}
|
||||
for _, ctrlResult := range a.Results {
|
||||
summary.AddSummary(ctrlResult.GetSummary())
|
||||
}
|
||||
return summary
|
||||
}
|
||||
|
||||
func (rs ResultSet) GetSuccesses() []ResultMessage {
|
||||
successes := []ResultMessage{}
|
||||
for _, msg := range rs {
|
||||
if msg.Type == MessageTypeSuccess {
|
||||
successes = append(successes, msg)
|
||||
}
|
||||
}
|
||||
return successes
|
||||
}
|
||||
|
||||
func (rs ResultSet) GetWarnings() []ResultMessage {
|
||||
warnings := []ResultMessage{}
|
||||
for _, msg := range rs {
|
||||
if msg.Type == MessageTypeFailure && msg.Severity == config.SeverityWarning {
|
||||
warnings = append(warnings, msg)
|
||||
}
|
||||
}
|
||||
return warnings
|
||||
}
|
||||
|
||||
func (rs ResultSet) GetErrors() []ResultMessage {
|
||||
errors := []ResultMessage{}
|
||||
for _, msg := range rs {
|
||||
if msg.Type == MessageTypeFailure && msg.Severity == config.SeverityError {
|
||||
errors = append(errors, msg)
|
||||
}
|
||||
}
|
||||
return errors
|
||||
}
|
||||
@@ -19,45 +19,27 @@ import (
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
)
|
||||
|
||||
// PodValidation tracks validation failures associated with a Pod.
|
||||
type PodValidation struct {
|
||||
*ResourceValidation
|
||||
Pod *corev1.PodSpec
|
||||
}
|
||||
|
||||
// ValidatePod validates that each pod conforms to the Polaris config, returns a ResourceResult.
|
||||
func ValidatePod(conf config.Configuration, pod *corev1.PodSpec, controllerName string, controllerType config.SupportedController) PodResult {
|
||||
pv := PodValidation{
|
||||
Pod: pod,
|
||||
ResourceValidation: &ResourceValidation{},
|
||||
}
|
||||
|
||||
err := applyPodSchemaChecks(&conf, pod, controllerName, controllerType, &pv)
|
||||
func ValidatePod(conf *config.Configuration, pod *corev1.PodSpec, controllerName string, controllerType config.SupportedController) PodResult {
|
||||
podResults, err := applyPodSchemaChecks(conf, pod, controllerName, controllerType)
|
||||
// FIXME: don't panic
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
pRes := PodResult{
|
||||
Messages: pv.messages(),
|
||||
Messages: podResults,
|
||||
ContainerResults: []ContainerResult{},
|
||||
Summary: pv.summary(),
|
||||
podSpec: *pod,
|
||||
}
|
||||
|
||||
pv.validateContainers(pod.InitContainers, &pRes, &conf, controllerName, controllerType, true)
|
||||
pv.validateContainers(pod.Containers, &pRes, &conf, controllerName, controllerType, false)
|
||||
podCopy := *pod
|
||||
podCopy.InitContainers = []corev1.Container{}
|
||||
podCopy.Containers = []corev1.Container{}
|
||||
|
||||
for _, cRes := range pRes.ContainerResults {
|
||||
pRes.Summary.appendResults(*cRes.Summary)
|
||||
}
|
||||
containerResults := ValidateContainers(conf, &podCopy, pod.InitContainers, controllerName, controllerType, true)
|
||||
pRes.ContainerResults = append(pRes.ContainerResults, containerResults...)
|
||||
containerResults = ValidateContainers(conf, &podCopy, pod.Containers, controllerName, controllerType, false)
|
||||
pRes.ContainerResults = append(pRes.ContainerResults, containerResults...)
|
||||
|
||||
return pRes
|
||||
}
|
||||
|
||||
func (pv *PodValidation) validateContainers(containers []corev1.Container, pRes *PodResult, conf *config.Configuration, controllerName string, controllerType config.SupportedController, isInit bool) {
|
||||
for _, container := range containers {
|
||||
cRes := ValidateContainer(&container, pRes, conf, controllerName, controllerType, isInit)
|
||||
pRes.ContainerResults = append(pRes.ContainerResults, cRes)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,35 +36,22 @@ func TestValidatePod(t *testing.T) {
|
||||
k8s = test.SetupAddControllers(k8s, "test")
|
||||
pod := test.MockPod()
|
||||
|
||||
expectedSum := ResultSummary{
|
||||
Totals: CountSummary{
|
||||
Successes: uint(4),
|
||||
Warnings: uint(0),
|
||||
Errors: uint(0),
|
||||
},
|
||||
ByCategory: make(map[string]*CountSummary),
|
||||
}
|
||||
expectedSum.ByCategory["Networking"] = &CountSummary{
|
||||
Successes: uint(2),
|
||||
Warnings: uint(0),
|
||||
Errors: uint(0),
|
||||
}
|
||||
expectedSum.ByCategory["Security"] = &CountSummary{
|
||||
Successes: uint(2),
|
||||
expectedSum := CountSummary{
|
||||
Successes: uint(4),
|
||||
Warnings: uint(0),
|
||||
Errors: uint(0),
|
||||
}
|
||||
|
||||
expectedMessages := []*ResultMessage{
|
||||
{ID: "hostIPCSet", Message: "Host IPC is not configured", Type: "success", Category: "Security"},
|
||||
{ID: "hostNetworkSet", Message: "Host network is not configured", Type: "success", Category: "Networking"},
|
||||
{ID: "hostPIDSet", Message: "Host PID is not configured", Type: "success", Category: "Security"},
|
||||
expectedMessages := ResultSet{
|
||||
"hostIPCSet": {ID: "hostIPCSet", Message: "Host IPC is not configured", Type: "success", Severity: "error", Category: "Security"},
|
||||
"hostNetworkSet": {ID: "hostNetworkSet", Message: "Host network is not configured", Type: "success", Severity: "warning", Category: "Networking"},
|
||||
"hostPIDSet": {ID: "hostPIDSet", Message: "Host PID is not configured", Type: "success", Severity: "error", Category: "Security"},
|
||||
}
|
||||
|
||||
actualPodResult := ValidatePod(c, &pod.Spec, "", conf.Deployments)
|
||||
actualPodResult := ValidatePod(&c, &pod.Spec, "", conf.Deployments)
|
||||
|
||||
assert.Equal(t, 1, len(actualPodResult.ContainerResults), "should be equal")
|
||||
assert.EqualValues(t, &expectedSum, actualPodResult.Summary)
|
||||
assert.EqualValues(t, expectedSum, actualPodResult.GetSummary())
|
||||
assert.EqualValues(t, expectedMessages, actualPodResult.Messages)
|
||||
}
|
||||
|
||||
@@ -83,34 +70,21 @@ func TestInvalidIPCPod(t *testing.T) {
|
||||
pod := test.MockPod()
|
||||
pod.Spec.HostIPC = true
|
||||
|
||||
expectedSum := ResultSummary{
|
||||
Totals: CountSummary{
|
||||
Successes: uint(3),
|
||||
Warnings: uint(0),
|
||||
Errors: uint(1),
|
||||
},
|
||||
ByCategory: make(map[string]*CountSummary),
|
||||
}
|
||||
expectedSum.ByCategory["Networking"] = &CountSummary{
|
||||
Successes: uint(2),
|
||||
Warnings: uint(0),
|
||||
Errors: uint(0),
|
||||
}
|
||||
expectedSum.ByCategory["Security"] = &CountSummary{
|
||||
Successes: uint(1),
|
||||
expectedSum := CountSummary{
|
||||
Successes: uint(3),
|
||||
Warnings: uint(0),
|
||||
Errors: uint(1),
|
||||
}
|
||||
expectedMessages := []*ResultMessage{
|
||||
{ID: "hostIPCSet", Message: "Host IPC should not be configured", Type: "error", Category: "Security"},
|
||||
{ID: "hostNetworkSet", Message: "Host network is not configured", Type: "success", Category: "Networking"},
|
||||
{ID: "hostPIDSet", Message: "Host PID is not configured", Type: "success", Category: "Security"},
|
||||
expectedMessages := ResultSet{
|
||||
"hostIPCSet": {ID: "hostIPCSet", Message: "Host IPC should not be configured", Type: "failure", Severity: "error", Category: "Security"},
|
||||
"hostNetworkSet": {ID: "hostNetworkSet", Message: "Host network is not configured", Type: "success", Severity: "warning", Category: "Networking"},
|
||||
"hostPIDSet": {ID: "hostPIDSet", Message: "Host PID is not configured", Type: "success", Severity: "error", Category: "Security"},
|
||||
}
|
||||
|
||||
actualPodResult := ValidatePod(c, &pod.Spec, "", conf.Deployments)
|
||||
actualPodResult := ValidatePod(&c, &pod.Spec, "", conf.Deployments)
|
||||
|
||||
assert.Equal(t, 1, len(actualPodResult.ContainerResults), "should be equal")
|
||||
assert.EqualValues(t, &expectedSum, actualPodResult.Summary)
|
||||
assert.EqualValues(t, expectedSum, actualPodResult.GetSummary())
|
||||
assert.EqualValues(t, expectedMessages, actualPodResult.Messages)
|
||||
}
|
||||
|
||||
@@ -129,36 +103,22 @@ func TestInvalidNeworkPod(t *testing.T) {
|
||||
pod := test.MockPod()
|
||||
pod.Spec.HostNetwork = true
|
||||
|
||||
expectedSum := ResultSummary{
|
||||
Totals: CountSummary{
|
||||
Successes: uint(3),
|
||||
Warnings: uint(1),
|
||||
Errors: uint(0),
|
||||
},
|
||||
ByCategory: make(map[string]*CountSummary),
|
||||
}
|
||||
expectedSum.ByCategory["Networking"] = &CountSummary{
|
||||
Successes: uint(1),
|
||||
expectedSum := CountSummary{
|
||||
Successes: uint(3),
|
||||
Warnings: uint(1),
|
||||
Errors: uint(0),
|
||||
}
|
||||
|
||||
expectedSum.ByCategory["Security"] = &CountSummary{
|
||||
Successes: uint(2),
|
||||
Warnings: uint(0),
|
||||
Errors: uint(0),
|
||||
expectedMessages := ResultSet{
|
||||
"hostNetworkSet": {ID: "hostNetworkSet", Message: "Host network should not be configured", Type: "failure", Severity: "warning", Category: "Networking"},
|
||||
"hostIPCSet": {ID: "hostIPCSet", Message: "Host IPC is not configured", Type: "success", Severity: "error", Category: "Security"},
|
||||
"hostPIDSet": {ID: "hostPIDSet", Message: "Host PID is not configured", Type: "success", Severity: "error", Category: "Security"},
|
||||
}
|
||||
|
||||
expectedMessages := []*ResultMessage{
|
||||
{ID: "hostNetworkSet", Message: "Host network should not be configured", Type: "warning", Category: "Networking"},
|
||||
{ID: "hostIPCSet", Message: "Host IPC is not configured", Type: "success", Category: "Security"},
|
||||
{ID: "hostPIDSet", Message: "Host PID is not configured", Type: "success", Category: "Security"},
|
||||
}
|
||||
|
||||
actualPodResult := ValidatePod(c, &pod.Spec, "", conf.Deployments)
|
||||
actualPodResult := ValidatePod(&c, &pod.Spec, "", conf.Deployments)
|
||||
|
||||
assert.Equal(t, 1, len(actualPodResult.ContainerResults), "should be equal")
|
||||
assert.EqualValues(t, &expectedSum, actualPodResult.Summary)
|
||||
assert.EqualValues(t, expectedSum, actualPodResult.GetSummary())
|
||||
assert.EqualValues(t, expectedMessages, actualPodResult.Messages)
|
||||
}
|
||||
|
||||
@@ -177,35 +137,22 @@ func TestInvalidPIDPod(t *testing.T) {
|
||||
pod := test.MockPod()
|
||||
pod.Spec.HostPID = true
|
||||
|
||||
expectedSum := ResultSummary{
|
||||
Totals: CountSummary{
|
||||
Successes: uint(3),
|
||||
Warnings: uint(0),
|
||||
Errors: uint(1),
|
||||
},
|
||||
ByCategory: make(map[string]*CountSummary),
|
||||
}
|
||||
expectedSum.ByCategory["Networking"] = &CountSummary{
|
||||
Successes: uint(2),
|
||||
Warnings: uint(0),
|
||||
Errors: uint(0),
|
||||
}
|
||||
expectedSum.ByCategory["Security"] = &CountSummary{
|
||||
Successes: uint(1),
|
||||
expectedSum := CountSummary{
|
||||
Successes: uint(3),
|
||||
Warnings: uint(0),
|
||||
Errors: uint(1),
|
||||
}
|
||||
|
||||
expectedMessages := []*ResultMessage{
|
||||
{ID: "hostPIDSet", Message: "Host PID should not be configured", Type: "error", Category: "Security"},
|
||||
{ID: "hostIPCSet", Message: "Host IPC is not configured", Type: "success", Category: "Security"},
|
||||
{ID: "hostNetworkSet", Message: "Host network is not configured", Type: "success", Category: "Networking"},
|
||||
expectedMessages := ResultSet{
|
||||
"hostPIDSet": {ID: "hostPIDSet", Message: "Host PID should not be configured", Type: "failure", Severity: "error", Category: "Security"},
|
||||
"hostIPCSet": {ID: "hostIPCSet", Message: "Host IPC is not configured", Type: "success", Severity: "error", Category: "Security"},
|
||||
"hostNetworkSet": {ID: "hostNetworkSet", Message: "Host network is not configured", Type: "success", Severity: "warning", Category: "Networking"},
|
||||
}
|
||||
|
||||
actualPodResult := ValidatePod(c, &pod.Spec, "", conf.Deployments)
|
||||
actualPodResult := ValidatePod(&c, &pod.Spec, "", conf.Deployments)
|
||||
|
||||
assert.Equal(t, 1, len(actualPodResult.ContainerResults), "should be equal")
|
||||
assert.EqualValues(t, &expectedSum, actualPodResult.Summary)
|
||||
assert.EqualValues(t, expectedSum, actualPodResult.GetSummary())
|
||||
assert.EqualValues(t, expectedMessages, actualPodResult.Messages)
|
||||
}
|
||||
|
||||
@@ -230,32 +177,19 @@ func TestExemption(t *testing.T) {
|
||||
pod := test.MockPod()
|
||||
pod.Spec.HostIPC = true
|
||||
|
||||
expectedSum := ResultSummary{
|
||||
Totals: CountSummary{
|
||||
Successes: uint(3),
|
||||
Warnings: uint(0),
|
||||
Errors: uint(0),
|
||||
},
|
||||
ByCategory: make(map[string]*CountSummary),
|
||||
}
|
||||
expectedSum.ByCategory["Networking"] = &CountSummary{
|
||||
Successes: uint(2),
|
||||
expectedSum := CountSummary{
|
||||
Successes: uint(3),
|
||||
Warnings: uint(0),
|
||||
Errors: uint(0),
|
||||
}
|
||||
expectedSum.ByCategory["Security"] = &CountSummary{
|
||||
Successes: uint(1),
|
||||
Warnings: uint(0),
|
||||
Errors: uint(0),
|
||||
}
|
||||
expectedMessages := []*ResultMessage{
|
||||
{ID: "hostNetworkSet", Message: "Host network is not configured", Type: "success", Category: "Networking"},
|
||||
{ID: "hostPIDSet", Message: "Host PID is not configured", Type: "success", Category: "Security"},
|
||||
expectedMessages := ResultSet{
|
||||
"hostNetworkSet": {ID: "hostNetworkSet", Message: "Host network is not configured", Type: "success", Severity: "warning", Category: "Networking"},
|
||||
"hostPIDSet": {ID: "hostPIDSet", Message: "Host PID is not configured", Type: "success", Severity: "error", Category: "Security"},
|
||||
}
|
||||
|
||||
actualPodResult := ValidatePod(c, &pod.Spec, "foo", conf.Deployments)
|
||||
actualPodResult := ValidatePod(&c, &pod.Spec, "foo", conf.Deployments)
|
||||
|
||||
assert.Equal(t, 1, len(actualPodResult.ContainerResults), "should be equal")
|
||||
assert.EqualValues(t, &expectedSum, actualPodResult.Summary)
|
||||
assert.EqualValues(t, expectedSum, actualPodResult.GetSummary())
|
||||
assert.EqualValues(t, expectedMessages, actualPodResult.Messages)
|
||||
}
|
||||
|
||||
@@ -1,109 +0,0 @@
|
||||
// Copyright 2019 FairwindsOps Inc
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package validator
|
||||
|
||||
import (
|
||||
conf "github.com/fairwindsops/polaris/pkg/config"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// ResourceValidation contains methods shared by PodValidation and ContainerValidation
|
||||
type ResourceValidation struct {
|
||||
Errors []*ResultMessage
|
||||
Warnings []*ResultMessage
|
||||
Successes []*ResultMessage
|
||||
}
|
||||
|
||||
func (rv *ResourceValidation) messages() []*ResultMessage {
|
||||
messages := []*ResultMessage{}
|
||||
messages = append(messages, rv.Errors...)
|
||||
messages = append(messages, rv.Warnings...)
|
||||
messages = append(messages, rv.Successes...)
|
||||
return messages
|
||||
}
|
||||
|
||||
func (rv *ResourceValidation) summary() *ResultSummary {
|
||||
counts := CountSummary{
|
||||
Errors: uint(len(rv.Errors)),
|
||||
Warnings: uint(len(rv.Warnings)),
|
||||
Successes: uint(len(rv.Successes)),
|
||||
}
|
||||
byCategory := CategorySummary{}
|
||||
for _, msg := range rv.messages() {
|
||||
if _, ok := byCategory[msg.Category]; !ok {
|
||||
byCategory[msg.Category] = &CountSummary{}
|
||||
}
|
||||
if msg.Type == MessageTypeError {
|
||||
byCategory[msg.Category].Errors++
|
||||
} else if msg.Type == MessageTypeWarning {
|
||||
byCategory[msg.Category].Warnings++
|
||||
} else if msg.Type == MessageTypeSuccess {
|
||||
byCategory[msg.Category].Successes++
|
||||
}
|
||||
}
|
||||
return &ResultSummary{
|
||||
Totals: counts,
|
||||
ByCategory: byCategory,
|
||||
}
|
||||
}
|
||||
|
||||
func (rv *ResourceValidation) addMessage(message ResultMessage) {
|
||||
if message.Type == MessageTypeError {
|
||||
rv.Errors = append(rv.Errors, &message)
|
||||
} else if message.Type == MessageTypeWarning {
|
||||
rv.Warnings = append(rv.Warnings, &message)
|
||||
} else if message.Type == MessageTypeSuccess {
|
||||
rv.Successes = append(rv.Successes, &message)
|
||||
} else {
|
||||
panic("Bad message type")
|
||||
}
|
||||
}
|
||||
|
||||
func (rv *ResourceValidation) addFailure(message string, severity conf.Severity, category string, id string) {
|
||||
if severity == conf.SeverityError {
|
||||
rv.addError(message, category, id)
|
||||
} else if severity == conf.SeverityWarning {
|
||||
rv.addWarning(message, category, id)
|
||||
} else {
|
||||
logrus.Errorf("Invalid severity: %s", severity)
|
||||
}
|
||||
}
|
||||
|
||||
func (rv *ResourceValidation) addError(message string, category string, id string) {
|
||||
rv.Errors = append(rv.Errors, &ResultMessage{
|
||||
ID: id,
|
||||
Message: message,
|
||||
Type: MessageTypeError,
|
||||
Category: category,
|
||||
})
|
||||
}
|
||||
|
||||
func (rv *ResourceValidation) addWarning(message string, category string, id string) {
|
||||
rv.Warnings = append(rv.Warnings, &ResultMessage{
|
||||
ID: id,
|
||||
Message: message,
|
||||
Type: MessageTypeWarning,
|
||||
Category: category,
|
||||
})
|
||||
}
|
||||
|
||||
func (rv *ResourceValidation) addSuccess(message string, category string, id string) {
|
||||
rv.Successes = append(rv.Successes, &ResultMessage{
|
||||
ID: id,
|
||||
Message: message,
|
||||
Type: MessageTypeSuccess,
|
||||
Category: category,
|
||||
})
|
||||
}
|
||||
@@ -72,72 +72,85 @@ func parseCheck(rawBytes []byte) (config.SchemaCheck, error) {
|
||||
}
|
||||
}
|
||||
|
||||
func applyPodSchemaChecks(conf *config.Configuration, pod *corev1.PodSpec, controllerName string, controllerType config.SupportedController, pv *PodValidation) error {
|
||||
func resolveCheck(conf *config.Configuration, checkID string, controllerName string, controllerType config.SupportedController, target config.TargetKind, isInitContainer bool) (*config.SchemaCheck, error) {
|
||||
check, ok := conf.CustomChecks[checkID]
|
||||
if !ok {
|
||||
check, ok = builtInChecks[checkID]
|
||||
}
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("Check %s not found", checkID)
|
||||
}
|
||||
if !conf.IsActionable(check.ID, controllerName) {
|
||||
return nil, nil
|
||||
}
|
||||
if !check.IsActionable(target, controllerType, isInitContainer) {
|
||||
return nil, nil
|
||||
}
|
||||
return &check, nil
|
||||
}
|
||||
|
||||
func makeResult(conf *config.Configuration, check *config.SchemaCheck, passes bool) ResultMessage {
|
||||
result := ResultMessage{
|
||||
ID: check.ID,
|
||||
Severity: conf.Checks[check.ID],
|
||||
Category: check.Category,
|
||||
}
|
||||
if passes {
|
||||
result.Message = check.SuccessMessage
|
||||
result.Type = MessageTypeSuccess
|
||||
} else {
|
||||
result.Message = check.FailureMessage
|
||||
result.Type = MessageTypeFailure
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func applyPodSchemaChecks(conf *config.Configuration, pod *corev1.PodSpec, controllerName string, controllerType config.SupportedController) (ResultSet, error) {
|
||||
results := ResultSet{}
|
||||
checkIDs := getSortedKeys(conf.Checks)
|
||||
for _, checkID := range checkIDs {
|
||||
check, ok := conf.CustomChecks[checkID]
|
||||
if !ok {
|
||||
check, ok = builtInChecks[checkID]
|
||||
check, err := resolveCheck(conf, checkID, controllerName, controllerType, config.TargetPod, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !ok {
|
||||
return fmt.Errorf("Check %s not found", checkID)
|
||||
}
|
||||
if !conf.IsActionable(check.ID, controllerName) {
|
||||
continue
|
||||
}
|
||||
if !check.IsActionable(config.TargetPod, controllerType, false) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if check == nil {
|
||||
continue
|
||||
}
|
||||
passes, err := check.CheckPod(pod)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if passes {
|
||||
pv.addSuccess(check.SuccessMessage, check.Category, check.ID)
|
||||
} else {
|
||||
severity := conf.Checks[checkID]
|
||||
pv.addFailure(check.FailureMessage, severity, check.Category, check.ID)
|
||||
return nil, err
|
||||
}
|
||||
results[check.ID] = makeResult(conf, check, passes)
|
||||
}
|
||||
return nil
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func applyContainerSchemaChecks(conf *config.Configuration, controllerName string, controllerType config.SupportedController, cv *ContainerValidation) error {
|
||||
func applyContainerSchemaChecks(conf *config.Configuration, basePod *corev1.PodSpec, container *corev1.Container, controllerName string, controllerType config.SupportedController, isInit bool) (ResultSet, error) {
|
||||
results := ResultSet{}
|
||||
checkIDs := getSortedKeys(conf.Checks)
|
||||
for _, checkID := range checkIDs {
|
||||
check, ok := conf.CustomChecks[checkID]
|
||||
if !ok {
|
||||
check, ok = builtInChecks[checkID]
|
||||
}
|
||||
if !ok {
|
||||
return fmt.Errorf("Check %s not found", checkID)
|
||||
}
|
||||
if !conf.IsActionable(check.ID, controllerName) {
|
||||
continue
|
||||
}
|
||||
if !check.IsActionable(config.TargetContainer, controllerType, cv.IsInitContainer) {
|
||||
check, err := resolveCheck(conf, checkID, controllerName, controllerType, config.TargetContainer, isInit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if check == nil {
|
||||
continue
|
||||
}
|
||||
var passes bool
|
||||
var err error
|
||||
if check.SchemaTarget == config.TargetPod {
|
||||
cv.parentPodSpec.Containers = []corev1.Container{*cv.Container}
|
||||
passes, err = check.CheckPod(&cv.parentPodSpec)
|
||||
cv.parentPodSpec.Containers = []corev1.Container{}
|
||||
basePod.Containers = []corev1.Container{*container}
|
||||
passes, err = check.CheckPod(basePod)
|
||||
basePod.Containers = []corev1.Container{}
|
||||
} else {
|
||||
passes, err = check.CheckContainer(cv.Container)
|
||||
passes, err = check.CheckContainer(container)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if passes {
|
||||
cv.addSuccess(check.SuccessMessage, check.Category, check.ID)
|
||||
} else {
|
||||
severity := conf.Checks[checkID]
|
||||
cv.addFailure(check.FailureMessage, severity, check.Category, check.ID)
|
||||
return nil, err
|
||||
}
|
||||
results[check.ID] = makeResult(conf, check, passes)
|
||||
}
|
||||
return nil
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func getSortedKeys(m map[string]config.Severity) []string {
|
||||
|
||||
@@ -110,55 +110,50 @@ func TestValidateResourcesPartiallyValid(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
expectedWarnings := []*ResultMessage{
|
||||
expectedWarnings := []ResultMessage{
|
||||
{
|
||||
ID: "memoryLimitsRange",
|
||||
Type: "warning",
|
||||
Type: "failure",
|
||||
Severity: "warning",
|
||||
Message: "Memory limits should be within the required range",
|
||||
Category: "Resources",
|
||||
},
|
||||
}
|
||||
|
||||
expectedErrors := []*ResultMessage{
|
||||
expectedErrors := []ResultMessage{
|
||||
{
|
||||
ID: "memoryRequestsRange",
|
||||
Type: "error",
|
||||
Type: "failure",
|
||||
Severity: "error",
|
||||
Message: "Memory requests should be within the required range",
|
||||
Category: "Resources",
|
||||
},
|
||||
}
|
||||
|
||||
expectedSuccesses := []*ResultMessage{}
|
||||
expectedSuccesses := []ResultMessage{}
|
||||
|
||||
testValidate(t, &container, &resourceConfRanges, "foo", expectedErrors, expectedWarnings, expectedSuccesses)
|
||||
}
|
||||
|
||||
func TestValidateResourcesInit(t *testing.T) {
|
||||
cvEmpty := ContainerValidation{
|
||||
Container: &corev1.Container{},
|
||||
ResourceValidation: &ResourceValidation{},
|
||||
}
|
||||
cvInit := ContainerValidation{
|
||||
Container: &corev1.Container{},
|
||||
ResourceValidation: &ResourceValidation{},
|
||||
IsInitContainer: true,
|
||||
}
|
||||
emptyContainer := &corev1.Container{}
|
||||
|
||||
parsedConf, err := conf.Parse([]byte(resourceConfRanges))
|
||||
assert.NoError(t, err, "Expected no error when parsing config")
|
||||
|
||||
err = applyContainerSchemaChecks(&parsedConf, "", conf.Deployments, &cvEmpty)
|
||||
results, err := applyContainerSchemaChecks(&parsedConf, &corev1.PodSpec{}, emptyContainer, "", conf.Deployments, false)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
assert.Len(t, cvEmpty.Errors, 1)
|
||||
assert.Len(t, cvEmpty.Warnings, 1)
|
||||
assert.Equal(t, uint(1), results.GetSummary().Errors)
|
||||
assert.Equal(t, uint(1), results.GetSummary().Warnings)
|
||||
|
||||
err = applyContainerSchemaChecks(&parsedConf, "", conf.Deployments, &cvInit)
|
||||
results, err = applyContainerSchemaChecks(&parsedConf, &corev1.PodSpec{}, emptyContainer, "", conf.Deployments, true)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
assert.Len(t, cvInit.Errors, 0)
|
||||
assert.Equal(t, uint(0), results.GetSummary().Errors)
|
||||
assert.Equal(t, uint(0), results.GetSummary().Warnings)
|
||||
}
|
||||
|
||||
func TestValidateResourcesFullyValid(t *testing.T) {
|
||||
@@ -188,51 +183,57 @@ func TestValidateResourcesFullyValid(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
expectedSuccesses := []*ResultMessage{
|
||||
expectedSuccesses := []ResultMessage{
|
||||
{
|
||||
ID: "memoryRequestsRange",
|
||||
Type: "success",
|
||||
Severity: "error",
|
||||
Message: "Memory requests are within the required range",
|
||||
Category: "Resources",
|
||||
},
|
||||
{
|
||||
ID: "memoryLimitsRange",
|
||||
Type: "success",
|
||||
Severity: "warning",
|
||||
Message: "Memory limits are within the required range",
|
||||
Category: "Resources",
|
||||
},
|
||||
}
|
||||
|
||||
testValidate(t, &container, &resourceConfRanges, "foo", []*ResultMessage{}, []*ResultMessage{}, expectedSuccesses)
|
||||
testValidate(t, &container, &resourceConfRanges, "foo", []ResultMessage{}, []ResultMessage{}, expectedSuccesses)
|
||||
|
||||
expectedSuccesses = []*ResultMessage{
|
||||
expectedSuccesses = []ResultMessage{
|
||||
{
|
||||
ID: "cpuRequestsMissing",
|
||||
Type: "success",
|
||||
Severity: "warning",
|
||||
Message: "CPU requests are set",
|
||||
Category: "Resources",
|
||||
},
|
||||
{
|
||||
ID: "memoryRequestsMissing",
|
||||
Type: "success",
|
||||
Severity: "warning",
|
||||
Message: "Memory requests are set",
|
||||
Category: "Resources",
|
||||
},
|
||||
{
|
||||
ID: "cpuLimitsMissing",
|
||||
Type: "success",
|
||||
Severity: "error",
|
||||
Message: "CPU limits are set",
|
||||
Category: "Resources",
|
||||
},
|
||||
{
|
||||
ID: "memoryLimitsMissing",
|
||||
Type: "success",
|
||||
Severity: "error",
|
||||
Message: "Memory limits are set",
|
||||
Category: "Resources",
|
||||
},
|
||||
}
|
||||
|
||||
testValidate(t, &container, &resourceConfMinimal, "foo", []*ResultMessage{}, []*ResultMessage{}, expectedSuccesses)
|
||||
testValidate(t, &container, &resourceConfMinimal, "foo", []ResultMessage{}, []ResultMessage{}, expectedSuccesses)
|
||||
}
|
||||
|
||||
func TestValidateCustomCheckExemptions(t *testing.T) {
|
||||
@@ -241,15 +242,16 @@ func TestValidateCustomCheckExemptions(t *testing.T) {
|
||||
Image: "hub.docker.com/foo",
|
||||
}
|
||||
|
||||
expectedWarnings := []*ResultMessage{}
|
||||
expectedErrors := []*ResultMessage{}
|
||||
expectedSuccesses := []*ResultMessage{}
|
||||
expectedWarnings := []ResultMessage{}
|
||||
expectedErrors := []ResultMessage{}
|
||||
expectedSuccesses := []ResultMessage{}
|
||||
testValidate(t, &container, &customCheckExemptions, "exempt", expectedErrors, expectedWarnings, expectedSuccesses)
|
||||
|
||||
expectedErrors = []*ResultMessage{
|
||||
expectedErrors = []ResultMessage{
|
||||
{
|
||||
ID: "foo",
|
||||
Type: "error",
|
||||
Type: "failure",
|
||||
Severity: "error",
|
||||
Message: "fail!",
|
||||
Category: "Security",
|
||||
},
|
||||
|
||||
@@ -1,270 +0,0 @@
|
||||
// Copyright 2019 FairwindsOps Inc
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package validator
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/fairwindsops/polaris/pkg/config"
|
||||
conf "github.com/fairwindsops/polaris/pkg/config"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
apiMachineryYAML "k8s.io/apimachinery/pkg/util/yaml"
|
||||
)
|
||||
|
||||
const (
|
||||
// PolarisOutputVersion is the version of the current output structure
|
||||
PolarisOutputVersion = "0.3"
|
||||
)
|
||||
|
||||
// AuditData contains all the data from a full Polaris audit
|
||||
type AuditData struct {
|
||||
PolarisOutputVersion string
|
||||
AuditTime string
|
||||
SourceType string
|
||||
SourceName string
|
||||
DisplayName string
|
||||
ClusterSummary ClusterSummary
|
||||
NamespacedResults NamespacedResults
|
||||
}
|
||||
|
||||
// ClusterSummary contains Polaris results as well as some high-level stats
|
||||
type ClusterSummary struct {
|
||||
Results ResultSummary
|
||||
Version string
|
||||
Nodes int
|
||||
Pods int
|
||||
Namespaces int
|
||||
Deployments int
|
||||
StatefulSets int
|
||||
DaemonSets int
|
||||
Jobs int
|
||||
CronJobs int
|
||||
ReplicationControllers int
|
||||
Score uint
|
||||
}
|
||||
|
||||
// MessageType represents the type of Message
|
||||
type MessageType string
|
||||
|
||||
const (
|
||||
// MessageTypeSuccess indicates a validation success
|
||||
MessageTypeSuccess MessageType = "success"
|
||||
|
||||
// MessageTypeWarning indicates a validation warning
|
||||
MessageTypeWarning MessageType = "warning"
|
||||
|
||||
// MessageTypeError indicates a validation error
|
||||
MessageTypeError MessageType = "error"
|
||||
)
|
||||
|
||||
// NamespaceResult groups container results by parent resource.
|
||||
type NamespaceResult struct {
|
||||
Name string
|
||||
Summary *ResultSummary
|
||||
|
||||
// TODO: This struct could use some love to reorganize it as just having "results"
|
||||
// and then having methods to return filtered results by type
|
||||
// (deploy, daemonset, etc)
|
||||
// The way this is structured right now makes it difficult to add
|
||||
// additional result types and potentially miss things in the metrics
|
||||
// summary.
|
||||
DeploymentResults []ControllerResult
|
||||
StatefulSetResults []ControllerResult
|
||||
DaemonSetResults []ControllerResult
|
||||
JobResults []ControllerResult
|
||||
CronJobResults []ControllerResult
|
||||
ReplicationControllerResults []ControllerResult
|
||||
}
|
||||
|
||||
// AddResult adds a result to the result sets by leveraging the types supported by NamespaceResult
|
||||
func (n *NamespaceResult) AddResult(resourceType config.SupportedController, result ControllerResult) error {
|
||||
// Iterate all the resource types supported in this struct
|
||||
var results *[]ControllerResult
|
||||
switch resourceType {
|
||||
case conf.Deployments:
|
||||
results = &n.DeploymentResults
|
||||
case conf.StatefulSets:
|
||||
results = &n.StatefulSetResults
|
||||
case conf.DaemonSets:
|
||||
results = &n.DaemonSetResults
|
||||
case conf.Jobs:
|
||||
results = &n.JobResults
|
||||
case conf.CronJobs:
|
||||
results = &n.CronJobResults
|
||||
case conf.ReplicationControllers:
|
||||
results = &n.ReplicationControllerResults
|
||||
default:
|
||||
return fmt.Errorf("Unknown Resource Type: (%s) Missing Implementation in NamespacedResult", resourceType)
|
||||
}
|
||||
|
||||
// Append the new result to the results pointer loaded from the supported values
|
||||
*results = append(*results, result)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetAllControllerResults grabs all the different types of controller results from the namespaced result as a single list for easier iteration
|
||||
func (n NamespaceResult) GetAllControllerResults() []ControllerResult {
|
||||
all := []ControllerResult{}
|
||||
all = append(all, n.DeploymentResults...)
|
||||
all = append(all, n.StatefulSetResults...)
|
||||
all = append(all, n.DaemonSetResults...)
|
||||
all = append(all, n.JobResults...)
|
||||
all = append(all, n.CronJobResults...)
|
||||
all = append(all, n.ReplicationControllerResults...)
|
||||
|
||||
return all
|
||||
}
|
||||
|
||||
// NamespacedResults is a mapping of namespace name to the validation results.
|
||||
type NamespacedResults map[string]*NamespaceResult
|
||||
|
||||
// GetAllControllerResults aggregates all the namespaced results in the set together
|
||||
func (nsResults NamespacedResults) GetAllControllerResults() []ControllerResult {
|
||||
all := []ControllerResult{}
|
||||
for _, nsResult := range nsResults {
|
||||
all = append(all, nsResult.GetAllControllerResults()...)
|
||||
}
|
||||
return all
|
||||
}
|
||||
|
||||
func (nsResults NamespacedResults) getNamespaceResult(nsName string) *NamespaceResult {
|
||||
nsResult := &NamespaceResult{}
|
||||
switch nsResults[nsName] {
|
||||
case nil:
|
||||
nsResult = &NamespaceResult{
|
||||
Summary: &ResultSummary{},
|
||||
DeploymentResults: []ControllerResult{},
|
||||
StatefulSetResults: []ControllerResult{},
|
||||
DaemonSetResults: []ControllerResult{},
|
||||
JobResults: []ControllerResult{},
|
||||
CronJobResults: []ControllerResult{},
|
||||
ReplicationControllerResults: []ControllerResult{},
|
||||
}
|
||||
nsResults[nsName] = nsResult
|
||||
default:
|
||||
nsResult = nsResults[nsName]
|
||||
}
|
||||
return nsResult
|
||||
}
|
||||
|
||||
// CountSummary provides a high level overview of success, warnings, and errors.
|
||||
type CountSummary struct {
|
||||
Successes uint
|
||||
Warnings uint
|
||||
Errors uint
|
||||
}
|
||||
|
||||
// GetScore returns an overall score in [0, 100] for the CountSummary
|
||||
func (cs *CountSummary) GetScore() uint {
|
||||
total := (cs.Successes * 2) + cs.Warnings + (cs.Errors * 2)
|
||||
return uint((float64(cs.Successes*2) / float64(total)) * 100)
|
||||
}
|
||||
|
||||
func (cs *CountSummary) appendCounts(toAppend CountSummary) {
|
||||
cs.Errors += toAppend.Errors
|
||||
cs.Warnings += toAppend.Warnings
|
||||
cs.Successes += toAppend.Successes
|
||||
}
|
||||
|
||||
// CategorySummary provides a map from category name to a CountSummary
|
||||
type CategorySummary map[string]*CountSummary
|
||||
|
||||
// ResultSummary provides a high level overview of success, warnings, and errors.
|
||||
type ResultSummary struct {
|
||||
Totals CountSummary
|
||||
ByCategory CategorySummary
|
||||
}
|
||||
|
||||
func (rs *ResultSummary) appendResults(toAppend ResultSummary) {
|
||||
rs.Totals.appendCounts(toAppend.Totals)
|
||||
for category, summary := range toAppend.ByCategory {
|
||||
if rs.ByCategory == nil {
|
||||
rs.ByCategory = CategorySummary{}
|
||||
}
|
||||
if _, exists := rs.ByCategory[category]; !exists {
|
||||
rs.ByCategory[category] = &CountSummary{}
|
||||
}
|
||||
rs.ByCategory[category].appendCounts(*summary)
|
||||
}
|
||||
}
|
||||
|
||||
// ControllerResult provides a wrapper around a PodResult
|
||||
type ControllerResult struct {
|
||||
Name string
|
||||
Type string
|
||||
PodResult PodResult
|
||||
}
|
||||
|
||||
// ContainerResult provides a list of validation messages for each container.
|
||||
type ContainerResult struct {
|
||||
Name string
|
||||
Messages []*ResultMessage
|
||||
Summary *ResultSummary
|
||||
}
|
||||
|
||||
// PodResult provides a list of validation messages for each pod.
|
||||
type PodResult struct {
|
||||
Name string
|
||||
Summary *ResultSummary
|
||||
Messages []*ResultMessage
|
||||
ContainerResults []ContainerResult
|
||||
podSpec corev1.PodSpec
|
||||
}
|
||||
|
||||
// ResultMessage contains a message and a type indicator (success, warning, or error).
|
||||
type ResultMessage struct {
|
||||
ID string
|
||||
Message string
|
||||
Type MessageType
|
||||
Category string
|
||||
}
|
||||
|
||||
// ReadAuditFromFile reads the data from a past audit stored in a JSON or YAML file.
|
||||
func ReadAuditFromFile(fileName string) AuditData {
|
||||
auditData := AuditData{}
|
||||
oldFileBytes, err := ioutil.ReadFile(fileName)
|
||||
if err != nil {
|
||||
logrus.Errorf("Unable to read contents of loaded file: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
auditData, err = ParseAudit(oldFileBytes)
|
||||
if err != nil {
|
||||
logrus.Errorf("Error parsing file contents into auditData: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
return auditData
|
||||
}
|
||||
|
||||
// ParseAudit decodes either a YAML or JSON file and returns AuditData.
|
||||
func ParseAudit(oldFileBytes []byte) (AuditData, error) {
|
||||
reader := bytes.NewReader(oldFileBytes)
|
||||
conf := AuditData{}
|
||||
d := apiMachineryYAML.NewYAMLOrJSONDecoder(reader, 4096)
|
||||
for {
|
||||
if err := d.Decode(&conf); err != nil {
|
||||
if err == io.EOF {
|
||||
return conf, nil
|
||||
}
|
||||
return conf, fmt.Errorf("Decoding config failed: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -94,7 +94,7 @@ func (v *Validator) Handle(ctx context.Context, req types.Request) types.Respons
|
||||
if req.AdmissionRequest.Kind.Kind == "Pod" {
|
||||
pod := corev1.Pod{}
|
||||
err = v.decoder.Decode(req, &pod)
|
||||
podResult = validator.ValidatePod(v.Config, &pod.Spec, "", config.Unsupported)
|
||||
podResult = validator.ValidatePod(&v.Config, &pod.Spec, "", config.Unsupported)
|
||||
} else {
|
||||
var controller controllers.Interface
|
||||
if yes := v.Config.CheckIfKindIsConfiguredForValidation(req.AdmissionRequest.Kind.Kind); !yes {
|
||||
@@ -138,7 +138,7 @@ func (v *Validator) Handle(ctx context.Context, req types.Request) types.Respons
|
||||
err = v.decoder.Decode(req, &replicationController)
|
||||
controller = controllers.NewReplicationControllerController(replicationController)
|
||||
}
|
||||
controllerResult := validator.ValidateController(v.Config, controller)
|
||||
controllerResult := validator.ValidateController(&v.Config, controller)
|
||||
podResult = controllerResult.PodResult
|
||||
}
|
||||
|
||||
@@ -149,11 +149,12 @@ func (v *Validator) Handle(ctx context.Context, req types.Request) types.Respons
|
||||
|
||||
allowed := true
|
||||
reason := ""
|
||||
if podResult.Summary.Totals.Errors > 0 {
|
||||
numErrors := podResult.GetSummary().Errors
|
||||
if numErrors > 0 {
|
||||
allowed = false
|
||||
reason = getFailureReason(podResult)
|
||||
}
|
||||
logrus.Infof("%d validation errors found when validating %s", podResult.Summary.Totals.Errors, podResult.Name)
|
||||
logrus.Infof("%d validation errors found when validating %s", numErrors, podResult.Name)
|
||||
return admission.ValidationResponse(allowed, reason)
|
||||
}
|
||||
|
||||
@@ -161,14 +162,14 @@ func getFailureReason(podResult validator.PodResult) string {
|
||||
reason := "\nPolaris prevented this deployment due to configuration problems:\n"
|
||||
|
||||
for _, message := range podResult.Messages {
|
||||
if message.Type == validator.MessageTypeError {
|
||||
if message.Type == validator.MessageTypeFailure && message.Severity == config.SeverityError {
|
||||
reason += fmt.Sprintf("- Pod: %s\n", message.Message)
|
||||
}
|
||||
}
|
||||
|
||||
for _, containerResult := range podResult.ContainerResults {
|
||||
for _, message := range containerResult.Messages {
|
||||
if message.Type == validator.MessageTypeError {
|
||||
if message.Type == validator.MessageTypeFailure && message.Severity == config.SeverityError {
|
||||
reason += fmt.Sprintf("- Container %s: %s\n", containerResult.Name, message.Message)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user