-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[Security Hardened Kubernetes Cluster] Rule 2004 implementation (#376)
* Add GetServices utils func * Add service selector * Add rule 2004 * Register rule * Update docu * Add sugested changes * Use new object selector * Add minor changes to NamespacedObjectSelector * Add TODO * Fix unit test
- Loading branch information
1 parent
acaf802
commit f09debf
Showing
14 changed files
with
505 additions
and
35 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
112 changes: 112 additions & 0 deletions
112
pkg/provider/managedk8s/ruleset/securityhardenedk8s/rules/2004.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
// SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Gardener contributors | ||
// | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
package rules | ||
|
||
import ( | ||
"context" | ||
|
||
corev1 "k8s.io/api/core/v1" | ||
"k8s.io/apimachinery/pkg/labels" | ||
"k8s.io/apimachinery/pkg/util/validation/field" | ||
"sigs.k8s.io/controller-runtime/pkg/client" | ||
|
||
"github.com/gardener/diki/pkg/internal/utils" | ||
kubeutils "github.com/gardener/diki/pkg/kubernetes/utils" | ||
"github.com/gardener/diki/pkg/rule" | ||
"github.com/gardener/diki/pkg/shared/kubernetes/option" | ||
disaoptions "github.com/gardener/diki/pkg/shared/ruleset/disak8sstig/option" | ||
) | ||
|
||
var ( | ||
_ rule.Rule = &Rule2004{} | ||
_ rule.Severity = &Rule2004{} | ||
_ disaoptions.Option = &Options2004{} | ||
) | ||
|
||
type Rule2004 struct { | ||
Client client.Client | ||
Options *Options2004 | ||
} | ||
|
||
type Options2004 struct { | ||
AcceptedServices []AcceptedServices2004 `json:"acceptedServices" yaml:"acceptedServices"` | ||
} | ||
|
||
type AcceptedServices2004 struct { | ||
option.NamespacedObjectSelector | ||
Justification string `json:"justification" yaml:"justification"` | ||
} | ||
|
||
// Validate validates that option configurations are correctly defined | ||
func (o Options2004) Validate() field.ErrorList { | ||
var allErrs field.ErrorList | ||
|
||
for _, s := range o.AcceptedServices { | ||
allErrs = append(allErrs, s.Validate()...) | ||
} | ||
|
||
return allErrs | ||
} | ||
|
||
func (r *Rule2004) ID() string { | ||
return "2004" | ||
} | ||
|
||
func (r *Rule2004) Name() string { | ||
return "Limit the Services of type NodePort." | ||
} | ||
|
||
func (r *Rule2004) Severity() rule.SeverityLevel { | ||
return rule.SeverityMedium | ||
} | ||
|
||
func (r *Rule2004) Run(ctx context.Context) (rule.RuleResult, error) { | ||
var checkResults []rule.CheckResult | ||
|
||
services, err := kubeutils.GetServices(ctx, r.Client, "", labels.NewSelector(), 300) | ||
if err != nil { | ||
return rule.Result(r, rule.ErroredCheckResult(err.Error(), rule.NewTarget("kind", "serviceList"))), nil | ||
} | ||
|
||
namespaces, err := kubeutils.GetNamespaces(ctx, r.Client) | ||
if err != nil { | ||
return rule.Result(r, rule.ErroredCheckResult(err.Error(), rule.NewTarget("kind", "namespaceList"))), nil | ||
} | ||
|
||
for _, service := range services { | ||
serviceTarget := rule.NewTarget("kind", "service", "name", service.Name, "namespace", service.Namespace) | ||
|
||
if service.Spec.Type == corev1.ServiceTypeNodePort { | ||
if accepted, justification := r.accepted(service, namespaces[service.Namespace]); accepted { | ||
msg := "Service accepted to be of type NodePort." | ||
if justification != "" { | ||
msg = justification | ||
} | ||
checkResults = append(checkResults, rule.AcceptedCheckResult(msg, serviceTarget)) | ||
} else { | ||
checkResults = append(checkResults, rule.FailedCheckResult("Service should not be of type NodePort.", serviceTarget)) | ||
} | ||
} else { | ||
checkResults = append(checkResults, rule.PassedCheckResult("Service is not of type NodePort.", serviceTarget)) | ||
} | ||
} | ||
|
||
return rule.Result(r, checkResults...), nil | ||
} | ||
|
||
func (r *Rule2004) accepted(service corev1.Service, namespace corev1.Namespace) (bool, string) { | ||
if r.Options == nil { | ||
return false, "" | ||
} | ||
|
||
for _, acceptedService := range r.Options.AcceptedServices { | ||
if utils.MatchLabels(service.Labels, acceptedService.MatchLabels) && | ||
utils.MatchLabels(namespace.Labels, acceptedService.NamespaceMatchLabels) { | ||
return true, acceptedService.Justification | ||
} | ||
} | ||
|
||
return false, "" | ||
} |
93 changes: 93 additions & 0 deletions
93
pkg/provider/managedk8s/ruleset/securityhardenedk8s/rules/2004_test.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
// SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Gardener contributors | ||
// | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
package rules_test | ||
|
||
import ( | ||
"context" | ||
|
||
. "github.com/onsi/ginkgo/v2" | ||
. "github.com/onsi/gomega" | ||
corev1 "k8s.io/api/core/v1" | ||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
"sigs.k8s.io/controller-runtime/pkg/client" | ||
fakeclient "sigs.k8s.io/controller-runtime/pkg/client/fake" | ||
|
||
"github.com/gardener/diki/pkg/provider/managedk8s/ruleset/securityhardenedk8s/rules" | ||
"github.com/gardener/diki/pkg/rule" | ||
"github.com/gardener/diki/pkg/shared/kubernetes/option" | ||
) | ||
|
||
var _ = Describe("#2004", func() { | ||
var ( | ||
client client.Client | ||
service *corev1.Service | ||
ctx = context.TODO() | ||
namespaceName = "foo" | ||
namespace *corev1.Namespace | ||
) | ||
|
||
BeforeEach(func() { | ||
client = fakeclient.NewClientBuilder().Build() | ||
namespace = &corev1.Namespace{ | ||
ObjectMeta: metav1.ObjectMeta{ | ||
Name: namespaceName, | ||
Labels: map[string]string{ | ||
"foo": "bar", | ||
}, | ||
}, | ||
} | ||
service = &corev1.Service{ | ||
ObjectMeta: metav1.ObjectMeta{ | ||
Name: "foo", | ||
Namespace: namespaceName, | ||
Labels: map[string]string{ | ||
"foo": "bar", | ||
}, | ||
}, | ||
} | ||
}) | ||
|
||
DescribeTable("Run cases", | ||
func(serviceSpec corev1.ServiceSpec, ruleOptions rules.Options2004, expectedResult rule.CheckResult) { | ||
r := &rules.Rule2004{Client: client, Options: &ruleOptions} | ||
service.Spec = serviceSpec | ||
|
||
Expect(client.Create(ctx, service)).To(Succeed()) | ||
Expect(client.Create(ctx, namespace)).To(Succeed()) | ||
|
||
ruleResult, err := r.Run(ctx) | ||
Expect(err).ToNot(HaveOccurred()) | ||
Expect(ruleResult.CheckResults).To(Equal([]rule.CheckResult{expectedResult})) | ||
}, | ||
|
||
Entry("should pass when serviceSpec is not set", | ||
corev1.ServiceSpec{}, rules.Options2004{}, | ||
rule.CheckResult{Status: rule.Passed, Message: "Service is not of type NodePort.", Target: rule.NewTarget("kind", "service", "name", "foo", "namespace", "foo")}, | ||
), | ||
Entry("should fail when service is of type NodePort", | ||
corev1.ServiceSpec{Type: "NodePort"}, rules.Options2004{}, | ||
rule.CheckResult{Status: rule.Failed, Message: "Service should not be of type NodePort.", Target: rule.NewTarget("kind", "service", "name", "foo", "namespace", "foo")}, | ||
), | ||
Entry("should pass when service is not of type NodePort", | ||
corev1.ServiceSpec{Type: "ClusterIP"}, rules.Options2004{}, | ||
rule.CheckResult{Status: rule.Passed, Message: "Service is not of type NodePort.", Target: rule.NewTarget("kind", "service", "name", "foo", "namespace", "foo")}, | ||
), | ||
Entry("should pass when options are set", | ||
corev1.ServiceSpec{Type: "NodePort"}, | ||
rules.Options2004{ | ||
AcceptedServices: []rules.AcceptedServices2004{ | ||
{ | ||
NamespacedObjectSelector: option.NamespacedObjectSelector{ | ||
MatchLabels: map[string]string{"foo": "bar"}, | ||
NamespaceMatchLabels: map[string]string{"foo": "bar"}, | ||
}, | ||
Justification: "foo justify", | ||
}, | ||
}, | ||
}, | ||
rule.CheckResult{Status: rule.Accepted, Message: "foo justify", Target: rule.NewTarget("kind", "service", "name", "foo", "namespace", "foo")}, | ||
), | ||
) | ||
}) |
Oops, something went wrong.