Skip to content

Commit

Permalink
feature(lint): Adds a new template linter to check the restart policy…
Browse files Browse the repository at this point in the history
… for all deployments like objects (#915)

Co-authored-by: Jonathan Henrique Medeiros <jonathan.medeiros@picpay.com>
  • Loading branch information
jonathanmdr and Jonathan Henrique Medeiros authored Feb 26, 2025
1 parent 37f508e commit 99380d7
Show file tree
Hide file tree
Showing 10 changed files with 465 additions and 0 deletions.
9 changes: 9 additions & 0 deletions docs/generated/checks.md
Original file line number Diff line number Diff line change
Expand Up @@ -531,6 +531,15 @@ value: '[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+'
```yaml
key: owner
```
## restart-policy
**Enabled by default**: No
**Description**: Indicates when a deployment-like object does not use a restart policy
**Remediation**: Set up the restart policy for your object to 'Always' or 'OnFailure' to increase the fault tolerance.
**Template**: [restart-policy](templates.md#restart-policy)
## run-as-non-root
**Enabled by default**: Yes
Expand Down
9 changes: 9 additions & 0 deletions docs/generated/templates.md
Original file line number Diff line number Diff line change
Expand Up @@ -712,6 +712,15 @@ KubeLinter supports the following templates:
type: string
```

## Restart policy

**Key**: `restart-policy`

**Description**: Flag applications running without the restart policy.

**Supported Objects**: DeploymentLike


## Run as non-root user

**Key**: `run-as-non-root`
Expand Down
24 changes: 24 additions & 0 deletions e2etests/bats-tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -785,6 +785,30 @@ get_value_from() {
[[ "${count}" == "2" ]]
}

@test "restart-policy" {
tmp="tests/checks/restart-policy.yaml"
cmd="${KUBE_LINTER_BIN} lint --include restart-policy --do-not-auto-add-defaults --format json ${tmp}"
run ${cmd}

message1=$(get_value_from "${lines[0]}" '.Reports[0] | .Object.K8sObject.GroupVersionKind.Kind + " " + .Object.K8sObject.Name + ": " + .Diagnostic.Message')
message2=$(get_value_from "${lines[0]}" '.Reports[1] | .Object.K8sObject.GroupVersionKind.Kind + " " + .Object.K8sObject.Name + ": " + .Diagnostic.Message')
message3=$(get_value_from "${lines[0]}" '.Reports[2] | .Object.K8sObject.GroupVersionKind.Kind + " " + .Object.K8sObject.Name + ": " + .Diagnostic.Message')
message4=$(get_value_from "${lines[0]}" '.Reports[3] | .Object.K8sObject.GroupVersionKind.Kind + " " + .Object.K8sObject.Name + ": " + .Diagnostic.Message')
message5=$(get_value_from "${lines[0]}" '.Reports[4] | .Object.K8sObject.GroupVersionKind.Kind + " " + .Object.K8sObject.Name + ": " + .Diagnostic.Message')
message6=$(get_value_from "${lines[0]}" '.Reports[5] | .Object.K8sObject.GroupVersionKind.Kind + " " + .Object.K8sObject.Name + ": " + .Diagnostic.Message')
message7=$(get_value_from "${lines[0]}" '.Reports[6] | .Object.K8sObject.GroupVersionKind.Kind + " " + .Object.K8sObject.Name + ": " + .Diagnostic.Message')
count=$(get_value_from "${lines[0]}" '.Reports | length')

[[ "${message1}" == "Deployment fire-deployment-never: object has a restart policy defined with 'Never' but the only accepted restart policies are '[Always OnFailure]'" ]]
[[ "${message2}" == "Pod fire-pod-never: object has a restart policy defined with 'Never' but the only accepted restart policies are '[Always OnFailure]'" ]]
[[ "${message3}" == "DaemonSet fire-daemonset-never: object has a restart policy defined with 'Never' but the only accepted restart policies are '[Always OnFailure]'" ]]
[[ "${message4}" == "ReplicaSet fire-replicaset-never: object has a restart policy defined with 'Never' but the only accepted restart policies are '[Always OnFailure]'" ]]
[[ "${message5}" == "ReplicationController fire-replicationcontroller-never: object has a restart policy defined with 'Never' but the only accepted restart policies are '[Always OnFailure]'" ]]
[[ "${message6}" == "Job fire-job-never: object has a restart policy defined with 'Never' but the only accepted restart policies are '[Always OnFailure]'" ]]
[[ "${message7}" == "CronJob fire-cronjob-never: object has a restart policy defined with 'Never' but the only accepted restart policies are '[Always OnFailure]'" ]]
[[ "${count}" == "7" ]]
}

@test "run-as-non-root" {
tmp="tests/checks/run-as-non-root.yml"
cmd="${KUBE_LINTER_BIN} lint --include run-as-non-root --do-not-auto-add-defaults --format json ${tmp}"
Expand Down
8 changes: 8 additions & 0 deletions pkg/builtinchecks/yamls/restart-policy.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
name: "restart-policy"
description: "Indicates when a deployment-like object does not use a restart policy"
remediation: >-
Set up the restart policy for your object to 'Always' or 'OnFailure' to increase the fault tolerance.
scope:
objectKinds:
- DeploymentLike
template: "restart-policy"
1 change: 1 addition & 0 deletions pkg/templates/all/all.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ import (
_ "golang.stackrox.io/kube-linter/pkg/templates/replicas"
_ "golang.stackrox.io/kube-linter/pkg/templates/requiredannotation"
_ "golang.stackrox.io/kube-linter/pkg/templates/requiredlabel"
_ "golang.stackrox.io/kube-linter/pkg/templates/restartpolicy"
_ "golang.stackrox.io/kube-linter/pkg/templates/runasnonroot"
_ "golang.stackrox.io/kube-linter/pkg/templates/sccdenypriv"
_ "golang.stackrox.io/kube-linter/pkg/templates/serviceaccount"
Expand Down
52 changes: 52 additions & 0 deletions pkg/templates/restartpolicy/internal/params/gen-params.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions pkg/templates/restartpolicy/internal/params/params.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package params

// Params represents the params accepted by this template.
type Params struct {
}
50 changes: 50 additions & 0 deletions pkg/templates/restartpolicy/template.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package restartpolicy

import (
"fmt"

"golang.stackrox.io/kube-linter/pkg/check"
"golang.stackrox.io/kube-linter/pkg/config"
"golang.stackrox.io/kube-linter/pkg/diagnostic"
"golang.stackrox.io/kube-linter/pkg/extract"
"golang.stackrox.io/kube-linter/pkg/lintcontext"
"golang.stackrox.io/kube-linter/pkg/objectkinds"
"golang.stackrox.io/kube-linter/pkg/templates"
"golang.stackrox.io/kube-linter/pkg/templates/restartpolicy/internal/params"
coreV1 "k8s.io/api/core/v1"
)

const (
templateKey = "restart-policy"
)

var acceptedRestartPolicies = []coreV1.RestartPolicy{coreV1.RestartPolicyAlways, coreV1.RestartPolicyOnFailure}

func init() {
templates.Register(check.Template{
HumanName: "Restart policy",
Key: templateKey,
Description: "Flag applications running without the restart policy.",
SupportedObjectKinds: config.ObjectKindsDesc{
ObjectKinds: []string{objectkinds.DeploymentLike},
},
Parameters: params.ParamDescs,
ParseAndValidateParams: params.ParseAndValidate,
Instantiate: params.WrapInstantiateFunc(func(p params.Params) (check.Func, error) {
return func(_ lintcontext.LintContext, object lintcontext.Object) []diagnostic.Diagnostic {
spec, found := extract.PodSpec(object.K8sObject)
if !found {
return nil
}
for _, policy := range acceptedRestartPolicies {
if spec.RestartPolicy == policy {
return nil
}
}
return []diagnostic.Diagnostic{
{Message: fmt.Sprintf("object has a restart policy defined with '%s' but the only accepted restart policies are '%s'", spec.RestartPolicy, acceptedRestartPolicies)},
}
}, nil
}),
})
}
118 changes: 118 additions & 0 deletions pkg/templates/restartpolicy/template_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
package restartpolicy

import (
"testing"

"github.com/stretchr/testify/suite"
"golang.stackrox.io/kube-linter/pkg/diagnostic"
"golang.stackrox.io/kube-linter/pkg/lintcontext/mocks"
"golang.stackrox.io/kube-linter/pkg/templates"
"golang.stackrox.io/kube-linter/pkg/templates/restartpolicy/internal/params"
appsV1 "k8s.io/api/apps/v1"
coreV1 "k8s.io/api/core/v1"
)

func TestRestartPolicy(t *testing.T) {
suite.Run(t, new(RestartPolicyTestSuite))
}

type RestartPolicyTestSuite struct {
templates.TemplateTestSuite

ctx *mocks.MockLintContext
}

func (s *RestartPolicyTestSuite) SetupTest() {
s.Init(templateKey)
s.ctx = mocks.NewMockContext()
}

func (s *RestartPolicyTestSuite) addDeploymentWithRestartPolicy(name string, policy coreV1.RestartPolicy) {
s.ctx.AddMockDeployment(s.T(), name)
s.ctx.ModifyDeployment(s.T(), name, func(deployment *appsV1.Deployment) {
deployment.Spec.Template.Spec.RestartPolicy = policy
})
}

func (s *RestartPolicyTestSuite) addDeploymentWithEmptyRestartPolicy(name string) {
s.ctx.AddMockDeployment(s.T(), name)
s.ctx.ModifyDeployment(s.T(), name, func(deployment *appsV1.Deployment) {
deployment.Spec.Template.Spec.RestartPolicy = ""
})
}

func (s *RestartPolicyTestSuite) addDeploymentWithoutRestartPolicy(name string) {
s.ctx.AddMockDeployment(s.T(), name)
}

func (s *RestartPolicyTestSuite) addObjectWithoutPodSpec(name string) {
s.ctx.AddMockService(s.T(), name)
}

func (s *RestartPolicyTestSuite) TestInvalidRestartPolicies() {
const (
withoutRestartPolicy = "without-restart-policy"
emptyRestartPolicy = "empty-restart-policy"
restartPolicyNever = "restart-policy-never"
)

s.addDeploymentWithoutRestartPolicy(withoutRestartPolicy)
s.addDeploymentWithEmptyRestartPolicy(emptyRestartPolicy)
s.addDeploymentWithRestartPolicy(restartPolicyNever, coreV1.RestartPolicyNever)

s.Validate(s.ctx, []templates.TestCase{
{
Param: params.Params{},
Diagnostics: map[string][]diagnostic.Diagnostic{
withoutRestartPolicy: {
{Message: "object has a restart policy defined with '' but the only accepted restart policies are '[Always OnFailure]'"},
},
emptyRestartPolicy: {
{Message: "object has a restart policy defined with '' but the only accepted restart policies are '[Always OnFailure]'"},
},
restartPolicyNever: {
{Message: "object has a restart policy defined with 'Never' but the only accepted restart policies are '[Always OnFailure]'"},
},
},
ExpectInstantiationError: false,
},
})
}

func (s *RestartPolicyTestSuite) TestAcceptableRestartPolicy() {
const (
alwaysRestartPolicy = "restart-policy-always"
onFailureRestartPolicy = "restart-policy-on-failure"
)
s.addDeploymentWithRestartPolicy(alwaysRestartPolicy, coreV1.RestartPolicyAlways)
s.addDeploymentWithRestartPolicy(onFailureRestartPolicy, coreV1.RestartPolicyOnFailure)

s.Validate(s.ctx, []templates.TestCase{
{
Param: params.Params{},
Diagnostics: map[string][]diagnostic.Diagnostic{
alwaysRestartPolicy: nil,
onFailureRestartPolicy: nil,
},
ExpectInstantiationError: false,
},
})
}

func (s *RestartPolicyTestSuite) TestObjectWithoutPodSpec() {
const (
objectWithoutPodSpec = "object-without-pod-spec"
)

s.addObjectWithoutPodSpec(objectWithoutPodSpec)

s.Validate(s.ctx, []templates.TestCase{
{
Param: params.Params{},
Diagnostics: map[string][]diagnostic.Diagnostic{
objectWithoutPodSpec: nil,
},
ExpectInstantiationError: false,
},
})
}
Loading

0 comments on commit 99380d7

Please sign in to comment.