Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Add support for ingress objects #469

Merged
merged 16 commits into from
Jan 14, 2021
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions checks/tlsSettingsMissing.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
successMessage: Ingress has TLS configured
failureMessage: Ingress does not have TLS configured
category: Security
target: Ingress
schema:
'$schema': http://json-schema.org/draft-07/schema
type: object
required:
- spec
properties:
spec:
type: object
required:
- tls
properties:
tls:
type: array
not:
const: null
1 change: 1 addition & 0 deletions examples/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ checks:
insecureCapabilities: warning
hostNetworkSet: warning
hostPortSet: warning
tlsSettingsMissing: warning

exemptions:
- namespace: kube-system
Expand Down
2 changes: 2 additions & 0 deletions pkg/config/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ const (
TargetPod TargetKind = "Pod"
// TargetController points to the controller's spec
TargetController TargetKind = "Controller"
// TargetIngress points to the ingress spec
TargetIngress TargetKind = "Ingress"
)

// SchemaCheck is a Polaris check that runs using JSON Schema
Expand Down
2 changes: 1 addition & 1 deletion pkg/dashboard/dashboard.go
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ func getConfigForQuery(base config.Configuration, query url.Values) config.Confi
}

func stripUnselectedNamespaces(data *validator.AuditData, selectedNamespaces []string) {
newResults := []validator.ControllerResult{}
newResults := []validator.Result{}
for _, res := range data.Results {
if stringInSlice(res.Namespace, selectedNamespaces) {
newResults = append(newResults, res)
Expand Down
40 changes: 21 additions & 19 deletions pkg/dashboard/templates/dashboard.gohtml
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@
<strong>{{ .Name }}</strong></div>

<div class="result-messages expandable-content">
<h4>Controller Spec:
<h4>Spec:</h4>
{{ if eq 0 (len .Results.GetSortedResults) }}
<i>no checks applied</i>
{{ end }}
Expand All @@ -171,24 +171,26 @@
</ul>
</div>

<div class="result-messages expandable-content">
<h4>Pod Spec:
{{ if eq 0 (len .PodResult.Results.GetSortedResults) }}
<i>no checks applied</i>
{{ end }}
</h4>
<ul class="message-list">
{{ range $message := .PodResult.Results.GetSortedResults }}
<li class="{{ getResultClass . }}">
<i class="message-icon {{ getIcon $message }}"></i>
<span class="message">{{ .Message }}</span>
<a class="more-info" href="{{ getCategoryLink .Category }}" target="_blank">
<i class="far fa-question-circle"></i>
</a>
</li>
{{ end }}
</ul>
</div>
{{ if .PodResult }}
<div class="result-messages expandable-content">
<h4>Pod Spec:
{{ if eq 0 (len .PodResult.Results.GetSortedResults) }}
<i>no checks applied</i>
{{ end }}
</h4>
<ul class="message-list">
{{ range $message := .PodResult.Results.GetSortedResults }}
<li class="{{ getResultClass . }}">
<i class="message-icon {{ getIcon $message }}"></i>
<span class="message">{{ .Message }}</span>
<a class="more-info" href="{{ getCategoryLink .Category }}" target="_blank">
<i class="far fa-question-circle"></i>
</a>
</li>
{{ end }}
</ul>
</div>
{{ end }}

{{ range .PodResult.ContainerResults }}
<div class="result-messages expandable-content">
Expand Down
12 changes: 12 additions & 0 deletions pkg/kube/resources.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (

"github.com/sirupsen/logrus"
corev1 "k8s.io/api/core/v1"
v1beta1 "k8s.io/api/extensions/v1beta1"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
Expand All @@ -34,6 +35,7 @@ type ResourceProvider struct {
Nodes []corev1.Node
Namespaces []corev1.Namespace
Controllers []GenericWorkload
Ingresses []v1beta1.Ingress
}

type k8sResource struct {
Expand Down Expand Up @@ -198,6 +200,11 @@ func CreateResourceProviderFromAPI(ctx context.Context, kube kubernetes.Interfac
logrus.Errorf("Error fetching Pods: %v", err)
return nil, err
}
ingressList, err := kube.ExtensionsV1beta1().Ingresses("").List(ctx, listOpts)
if err != nil {
logrus.Errorf("Error fetching Ingresses: %v", err)
return nil, err
}

resources, err := restmapper.GetAPIGroupResources(kube.Discovery())
if err != nil {
Expand All @@ -222,6 +229,7 @@ func CreateResourceProviderFromAPI(ctx context.Context, kube kubernetes.Interfac
Nodes: nodes.Items,
Namespaces: namespaces.Items,
Controllers: controllers,
Ingresses: ingressList.Items,
}
return &api, nil
}
Expand Down Expand Up @@ -319,6 +327,10 @@ func addResourceFromString(contents string, resources *ResourceProvider) error {
return err
}
resources.Controllers = append(resources.Controllers, workload)
} else if resource.Kind == "Ingress" {
ingress := v1beta1.Ingress{}
err = decoder.Decode(&ingress)
resources.Ingresses = append(resources.Ingresses, ingress)
} else {
newController, err := GetWorkloadFromBytes(contentBytes)
if err != nil || newController == nil {
Expand Down
1 change: 1 addition & 0 deletions pkg/kube/resources_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ func TestGetResourceFromAPI(t *testing.T) {
assert.IsType(t, time.Now(), resources.CreationTime, "Creation time should be set")

assert.Equal(t, 0, len(resources.Nodes), "Should not have any nodes")
assert.Equal(t, 0, len(resources.Ingresses), "Should not have any ingresses")
assert.Equal(t, 5, len(resources.Controllers), "Should have 5 controllers")

expectedNames := map[string]bool{
Expand Down
16 changes: 8 additions & 8 deletions pkg/validator/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,36 +25,36 @@ import (

const exemptionAnnotationKey = "polaris.fairwinds.com/exempt"

// ValidateController validates a single controller, returns a ControllerResult.
func ValidateController(conf *conf.Configuration, controller kube.GenericWorkload) (ControllerResult, error) {
// ValidateController validates a single controller, returns a Result.
func ValidateController(conf *conf.Configuration, controller kube.GenericWorkload) (Result, error) {
podResult, err := ValidatePod(conf, controller)
if err != nil {
return ControllerResult{}, err
return Result{}, err
}

var controllerResult ResultSet
controllerResult, err = applyControllerSchemaChecks(conf, controller)
if err != nil {
return ControllerResult{}, err
return Result{}, err
}

result := ControllerResult{
result := Result{
Kind: controller.Kind,
Name: controller.ObjectMeta.GetName(),
Namespace: controller.ObjectMeta.GetNamespace(),
Results: controllerResult,
PodResult: podResult,
PodResult: &podResult,
}

return result, nil
}

// 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) ([]ControllerResult, error) {
func ValidateControllers(config *conf.Configuration, kubeResources *kube.ResourceProvider) ([]Result, error) {
controllersToAudit := kubeResources.Controllers

results := []ControllerResult{}
results := []Result{}
for _, controller := range controllersToAudit {
if !config.DisallowExemptions && hasExemptionAnnotation(controller) {
continue
Expand Down
6 changes: 3 additions & 3 deletions pkg/validator/controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ func TestValidateController(t *testing.T) {
"hostPIDSet": {ID: "hostPIDSet", Message: "Host PID is not configured", Success: true, Severity: "danger", Category: "Security"},
}

var actualResult ControllerResult
var actualResult Result
actualResult, err = ValidateController(&c, deployment)
if err != nil {
panic(err)
Expand Down Expand Up @@ -137,7 +137,7 @@ func TestSkipHealthChecks(t *testing.T) {
"readinessProbeMissing": {ID: "readinessProbeMissing", Message: "Readiness probe should be configured", Success: false, Severity: "danger", Category: "Reliability"},
"livenessProbeMissing": {ID: "livenessProbeMissing", Message: "Liveness probe should be configured", Success: false, Severity: "warning", Category: "Reliability"},
}
var actualResult ControllerResult
var actualResult Result
actualResult, err = ValidateController(&c, deployment)
if err != nil {
panic(err)
Expand Down Expand Up @@ -205,7 +205,7 @@ func TestControllerExemptions(t *testing.T) {
Warnings: uint(1),
Dangers: uint(1),
}
var actualResults []ControllerResult
var actualResults []Result
actualResults, err = ValidateControllers(&c, resources)
if err != nil {
panic(err)
Expand Down
9 changes: 8 additions & 1 deletion pkg/validator/fullaudit.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,13 @@ func RunAudit(config conf.Configuration, kubeResources *kube.ResourceProvider) (
if err != nil {
return AuditData{}, err
}
controllerCount := len(results)

ingressResults, err := ValidateIngresses(&config, kubeResources)
if err != nil {
return AuditData{}, err
}
results = append(results, ingressResults...)

auditData := AuditData{
PolarisOutputVersion: PolarisOutputVersion,
Expand All @@ -38,7 +45,7 @@ func RunAudit(config conf.Configuration, kubeResources *kube.ResourceProvider) (
Nodes: len(kubeResources.Nodes),
Pods: len(kubeResources.Controllers), // TODO validate that this is still valuable
Namespaces: len(kubeResources.Namespaces),
Controllers: len(results),
Controllers: controllerCount,
},
Results: results,
}
Expand Down
51 changes: 51 additions & 0 deletions pkg/validator/ingress.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// 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/fairwindsops/polaris/pkg/kube"
"k8s.io/api/extensions/v1beta1"
)

// ValidateIngresses validates all the ingresses in a ResourceProvider
func ValidateIngresses(config *conf.Configuration, kubeResources *kube.ResourceProvider) ([]Result, error) {
var results []Result
for _, ingress := range kubeResources.Ingresses {
result, err := ValidateIngress(config, ingress)
if err != nil {
return []Result{}, err
}
results = append(results, result)
}
return results, nil
}

// ValidateIngress validates a single ingress
func ValidateIngress(config *conf.Configuration, ingress v1beta1.Ingress) (Result, error) {
results, err := applyIngressSchemaChecks(config, ingress)
if err != nil {
return Result{}, err
}

result := Result{
Kind: ingress.Kind,
Name: ingress.ObjectMeta.GetName(),
Namespace: ingress.ObjectMeta.GetNamespace(),
Results: results,
}

return result, nil
}
64 changes: 64 additions & 0 deletions pkg/validator/ingress_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// 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 (
"testing"

conf "github.com/fairwindsops/polaris/pkg/config"
"github.com/fairwindsops/polaris/test"
"github.com/stretchr/testify/assert"

extv1beta1 "k8s.io/api/extensions/v1beta1"
)

func TestValidateIngress(t *testing.T) {
c := conf.Configuration{
Checks: map[string]conf.Severity{
"tlsSettingsMissing": conf.SeverityWarning,
},
}
ingress := test.MockIngress()

var actualResult Result
actualResult, err := ValidateIngress(&c, ingress)
if err != nil {
panic(err)
}
results := actualResult.Results["tlsSettingsMissing"]

assert.False(t, results.Success)
assert.Equal(t, conf.Severity("warning"), results.Severity)
assert.Equal(t, "Security", results.Category)
assert.EqualValues(t, "Ingress does not have TLS configured", results.Message)

tls := extv1beta1.IngressTLS{
Hosts: []string{"test"},
SecretName: "secret",
}

ingress.Spec.TLS = []extv1beta1.IngressTLS{tls}
actualResult, err = ValidateIngress(&c, ingress)
if err != nil {
panic(err)
}
results = actualResult.Results["tlsSettingsMissing"]

assert.True(t, results.Success)
assert.Equal(t, conf.Severity("warning"), results.Severity)
assert.Equal(t, "Security", results.Category)
assert.EqualValues(t, "Ingress has TLS configured", results.Message)

}
8 changes: 4 additions & 4 deletions pkg/validator/output.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ type AuditData struct {
SourceName string
DisplayName string
ClusterInfo ClusterInfo
Results []ControllerResult
Results []Result
}

// ClusterInfo contains Polaris results as well as some high-level stats
Expand All @@ -57,13 +57,13 @@ type ResultMessage struct {
// ResultSet contiains the results for a set of checks
type ResultSet map[string]ResultMessage

// ControllerResult provides results for a controller
type ControllerResult struct {
// Result provides results for a Kubernetes object
type Result struct {
Name string
Namespace string
Kind string
Results ResultSet
PodResult PodResult
PodResult *PodResult
CreatedTime time.Time
}

Expand Down
Loading