mirror of
https://github.com/FairwindsOps/polaris.git
synced 2026-08-29 22:17:16 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ff8e66af07 | ||
|
|
01cc5fa642 | ||
|
|
cd47487b0b | ||
|
|
7e7e553c0d | ||
|
|
8385fd10e5 |
@@ -3,7 +3,7 @@
|
||||
<br>
|
||||
<h3>Best Practices for Kubernetes Workload Configuration</h3>
|
||||
<a href="https://github.com/FairwindsOps/polaris">
|
||||
<img src="https://img.shields.io/static/v1.svg?label=Version&message=4.0.3&color=239922">
|
||||
<img src="https://img.shields.io/static/v1.svg?label=Version&message=4.0.4&color=239922">
|
||||
</a>
|
||||
<a href="https://goreportcard.com/report/github.com/FairwindsOps/polaris">
|
||||
<img src="https://goreportcard.com/badge/github.com/FairwindsOps/polaris">
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
---
|
||||
sidebarDepth: 0
|
||||
---
|
||||
## 4.0.5
|
||||
* Bugfix for repeated objects on the dashboard
|
||||
|
||||
## 4.0.4
|
||||
* Bugfix for validating webhook and non-pod checks
|
||||
|
||||
## 4.0.3
|
||||
* Fixed bad interaction between `--set-exit-score-below` and `--only-show-failed-tests`
|
||||
* Dependency updates
|
||||
|
||||
@@ -20,7 +20,7 @@ import (
|
||||
|
||||
const (
|
||||
// Version represents the current release version of Polaris
|
||||
Version = "4.0.3"
|
||||
Version = "4.0.5"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
@@ -13,8 +13,10 @@
|
||||
padding-right: 40px;
|
||||
box-shadow: none;
|
||||
}
|
||||
.card.insights img {
|
||||
max-width: 400px;
|
||||
.card.transparent {
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
.card.insights a {
|
||||
background-color: #20162D;
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 66 KiB |
@@ -83,10 +83,12 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="insights" class="card insights py-2">
|
||||
<img class="align-self-center" src="static/images/FW_Insights_Polaris.svg" />
|
||||
<span>Fairwinds, the company behind Polaris, now offers Fairwinds Insights, a multi-cluster, multi-user Kubernetes configuration validation and policy enforcement platform. Fairwinds Insights can help you avoid errors that lead to wasted time, compute costs, and increased risk.</span>
|
||||
<a class="col-auto py-1 px-3 mt-2 align-self-center" href="https://www.fairwinds.com/polaris-user-insights-demo?utm_source=polaris&utm_medium=polaris&utm_campaign=polaris" target="_blank">Try Insights</a>
|
||||
<div class="card transparent">
|
||||
<a class="align-self-center"
|
||||
target="_blank"
|
||||
href="https://www.fairwinds.com/polaris-user-insights-demo?utm_source=polaris&utm_medium=ad&utm_campaign=polarisad">
|
||||
<img src="static/images/FW_Insights_Polaris.svg" />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div id="categories" class="card category">
|
||||
|
||||
@@ -25,24 +25,23 @@ type GenericResource struct {
|
||||
}
|
||||
|
||||
// NewGenericResourceFromUnstructured creates a workload from an unstructured.Unstructured
|
||||
func NewGenericResourceFromUnstructured(unst *unstructured.Unstructured) (GenericResource, error) {
|
||||
func NewGenericResourceFromUnstructured(unst unstructured.Unstructured) (GenericResource, error) {
|
||||
workload := GenericResource{
|
||||
Kind: unst.GetKind(),
|
||||
Resource: *unst,
|
||||
Resource: unst,
|
||||
}
|
||||
|
||||
objMeta, err := meta.Accessor(unst)
|
||||
objMeta, err := meta.Accessor(&unst)
|
||||
if err != nil {
|
||||
return workload, err
|
||||
}
|
||||
workload.ObjectMeta = objMeta
|
||||
|
||||
b, err := json.Marshal(unst)
|
||||
b, err := json.Marshal(&unst)
|
||||
if err != nil {
|
||||
return workload, err
|
||||
}
|
||||
workload.OriginalObjectJSON = b
|
||||
|
||||
m := make(map[string]interface{})
|
||||
err = json.Unmarshal(b, &m)
|
||||
if err != nil {
|
||||
@@ -61,7 +60,6 @@ func NewGenericResourceFromUnstructured(unst *unstructured.Unstructured) (Generi
|
||||
}
|
||||
workload.PodSpec = &podSpec
|
||||
}
|
||||
|
||||
return workload, nil
|
||||
}
|
||||
|
||||
@@ -101,7 +99,7 @@ func NewGenericResourceFromBytes(contentBytes []byte) (GenericResource, error) {
|
||||
if err != nil {
|
||||
return GenericResource{}, err
|
||||
}
|
||||
return NewGenericResourceFromUnstructured(&unst)
|
||||
return NewGenericResourceFromUnstructured(unst)
|
||||
}
|
||||
|
||||
// ResolveControllerFromPod builds a new workload for a given Pod
|
||||
@@ -161,7 +159,7 @@ func resolveControllerFromPod(ctx context.Context, podResource kubeAPICoreV1.Pod
|
||||
|
||||
if lastKey != "" {
|
||||
unst := objectCache[lastKey]
|
||||
return NewGenericResourceFromUnstructured(&unst)
|
||||
return NewGenericResourceFromUnstructured(unst)
|
||||
}
|
||||
workload, err := NewGenericResourceFromPod(podResource, podResource)
|
||||
if err != nil {
|
||||
|
||||
@@ -168,7 +168,7 @@ func CreateResourceProviderFromResource(ctx context.Context, workload string) (*
|
||||
logrus.Errorf("Could not find workload %s: %v", workload, err)
|
||||
return nil, err
|
||||
}
|
||||
workloadObj, err := NewGenericResourceFromUnstructured(obj)
|
||||
workloadObj, err := NewGenericResourceFromUnstructured(*obj)
|
||||
if err != nil {
|
||||
logrus.Errorf("Could not parse workload %s: %v", workload, err)
|
||||
return nil, err
|
||||
@@ -298,7 +298,7 @@ func CreateResourceProviderFromAPI(ctx context.Context, kube kubernetes.Interfac
|
||||
return nil, err
|
||||
}
|
||||
for _, obj := range objects.Items {
|
||||
res, err := NewGenericResourceFromUnstructured(&obj)
|
||||
res, err := NewGenericResourceFromUnstructured(obj)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ func TestValidatePDB(t *testing.T) {
|
||||
},
|
||||
}
|
||||
pdb := unstructured.Unstructured{}
|
||||
res, err := kube.NewGenericResourceFromUnstructured(&pdb)
|
||||
res, err := kube.NewGenericResourceFromUnstructured(pdb)
|
||||
res.Kind = "PodDisruptionBudget"
|
||||
|
||||
actualResult, err := applyNonControllerSchemaChecks(&c, nil, res)
|
||||
@@ -70,7 +70,7 @@ func TestValidateIngress(t *testing.T) {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
res, err := kube.NewGenericResourceFromUnstructured(&unst)
|
||||
res, err := kube.NewGenericResourceFromUnstructured(unst)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
+27
-49
@@ -16,8 +16,6 @@ package webhook
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
@@ -56,37 +54,11 @@ func NewWebhook(mgr manager.Manager, validator Validator) {
|
||||
mgr.GetWebhookServer().Register(path, &webhook.Admission{Handler: &validator})
|
||||
}
|
||||
|
||||
// GetObjectFromRawRequest returns the pod object and the controller's object from the raw json bytes.
|
||||
func GetObjectFromRawRequest(raw []byte) (corev1.Pod, interface{}, error) {
|
||||
pod := corev1.Pod{}
|
||||
var originalObject interface{}
|
||||
|
||||
decoded := map[string]interface{}{}
|
||||
err := json.Unmarshal(raw, &decoded)
|
||||
if err != nil {
|
||||
return pod, originalObject, err
|
||||
}
|
||||
podMap := kube.GetPodSpec(decoded)
|
||||
if podMap == nil {
|
||||
return pod, originalObject, errors.New("Object does not contain pods")
|
||||
}
|
||||
encoded, err := json.Marshal(podMap)
|
||||
if err != nil {
|
||||
return pod, originalObject, err
|
||||
}
|
||||
err = json.Unmarshal(encoded, &pod.Spec)
|
||||
if err != nil {
|
||||
return pod, originalObject, err
|
||||
}
|
||||
originalObject = decoded
|
||||
return pod, originalObject, err
|
||||
}
|
||||
|
||||
func (v *Validator) handleInternal(req admission.Request) (*validator.PodResult, error) {
|
||||
pod := corev1.Pod{}
|
||||
var originalObject interface{}
|
||||
func (v *Validator) handleInternal(req admission.Request) (*validator.Result, error) {
|
||||
var controller kube.GenericResource
|
||||
var err error
|
||||
if req.AdmissionRequest.Kind.Kind == "Pod" {
|
||||
pod := corev1.Pod{}
|
||||
err := v.decoder.Decode(req, &pod)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -95,58 +67,64 @@ func (v *Validator) handleInternal(req admission.Request) (*validator.PodResult,
|
||||
logrus.Infof("Allowing owned pod %s/%s to pass through webhook", pod.ObjectMeta.Namespace, pod.ObjectMeta.Name)
|
||||
return nil, nil
|
||||
}
|
||||
originalObject = pod
|
||||
controller, err = kube.NewGenericResourceFromPod(pod, pod)
|
||||
} else {
|
||||
pod, originalObject, err = GetObjectFromRawRequest(req.Object.Raw)
|
||||
controller, err = kube.NewGenericResourceFromBytes(req.Object.Raw)
|
||||
}
|
||||
controller, err := kube.NewGenericResourceFromPod(pod, originalObject)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
controller.Kind = req.AdmissionRequest.Kind.Kind
|
||||
var controllerResult validator.Result
|
||||
// TODO: consider enabling multi-resource checks
|
||||
controllerResult, err = validator.ApplyAllSchemaChecks(&v.Config, nil, controller)
|
||||
controllerResult, err := validator.ApplyAllSchemaChecks(&v.Config, nil, controller)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return controllerResult.PodResult, nil
|
||||
return &controllerResult, nil
|
||||
}
|
||||
|
||||
// Handle for Validator to run validation checks.
|
||||
func (v *Validator) Handle(ctx context.Context, req admission.Request) admission.Response {
|
||||
logrus.Info("Starting request")
|
||||
podResult, err := v.handleInternal(req)
|
||||
result, err := v.handleInternal(req)
|
||||
if err != nil {
|
||||
logrus.Errorf("Error validating request: %v", err)
|
||||
return admission.Errored(http.StatusBadRequest, err)
|
||||
}
|
||||
allowed := true
|
||||
reason := ""
|
||||
if podResult != nil {
|
||||
numDangers := podResult.GetSummary().Dangers
|
||||
if result != nil {
|
||||
numDangers := result.GetSummary().Dangers
|
||||
if numDangers > 0 {
|
||||
allowed = false
|
||||
reason = getFailureReason(*podResult)
|
||||
reason = getFailureReason(*result)
|
||||
}
|
||||
logrus.Infof("%d validation errors found when validating %s", numDangers, podResult.Name)
|
||||
logrus.Infof("%d validation errors found when validating %s", numDangers, result.Name)
|
||||
}
|
||||
return admission.ValidationResponse(allowed, reason)
|
||||
}
|
||||
|
||||
func getFailureReason(podResult validator.PodResult) string {
|
||||
func getFailureReason(result validator.Result) string {
|
||||
reason := "\nPolaris prevented this deployment due to configuration problems:\n"
|
||||
|
||||
for _, message := range podResult.Results {
|
||||
for _, message := range result.Results {
|
||||
if !message.Success && message.Severity == config.SeverityDanger {
|
||||
reason += fmt.Sprintf("- Pod: %s\n", message.Message)
|
||||
reason += fmt.Sprintf("- %s: %s\n", result.Kind, message.Message)
|
||||
}
|
||||
}
|
||||
|
||||
for _, containerResult := range podResult.ContainerResults {
|
||||
for _, message := range containerResult.Results {
|
||||
podResult := result.PodResult
|
||||
if podResult != nil {
|
||||
for _, message := range podResult.Results {
|
||||
if !message.Success && message.Severity == config.SeverityDanger {
|
||||
reason += fmt.Sprintf("- Container %s: %s\n", containerResult.Name, message.Message)
|
||||
reason += fmt.Sprintf("- Pod: %s\n", message.Message)
|
||||
}
|
||||
}
|
||||
|
||||
for _, containerResult := range podResult.ContainerResults {
|
||||
for _, message := range containerResult.Results {
|
||||
if !message.Success && message.Severity == config.SeverityDanger {
|
||||
reason += fmt.Sprintf("- Container %s: %s\n", containerResult.Name, message.Message)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user