Skip to content

Commit

Permalink
Add CIS 4.1 checks from Kubelinter (#16)
Browse files Browse the repository at this point in the history
  • Loading branch information
matas-cast authored Sep 29, 2022
1 parent 4630764 commit eeb9c88
Show file tree
Hide file tree
Showing 5 changed files with 133 additions and 12 deletions.
17 changes: 14 additions & 3 deletions castai/linter_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const (
DeprecatedServiceAccountField
DokerSock
DropNetRawCapability
EnvVarSercret
EnvVarSecret
ExposedService
HostIPC
HostNetwork
Expand All @@ -35,14 +35,19 @@ const (
UseNamespace
WritableHostMount
ClusterAdminRoleBinding
AccessToSecrets
DefaultServiceAccount
WildcardInRules
AccessToCreatePods
TokenAutomount
)

var LinterRuleMap = map[string]LinterRule{
"dangling-service": DanglingService,
"deprecated-service-account-field": DeprecatedServiceAccountField,
"docker-sock": DokerSock,
"drop-net-raw-capability": DropNetRawCapability,
"env-var-secret": EnvVarSercret,
"env-var-secret": EnvVarSecret,
"exposed-services": ExposedService,
"host-ipc": HostIPC,
"host-network": HostNetwork,
Expand All @@ -66,7 +71,13 @@ var LinterRuleMap = map[string]LinterRule{
"unset-memory-requirements": UnsetMempryRequirements,
"use-namespace": UseNamespace,
"writable-host-mount": WritableHostMount,
"cluster-admin-role-binding": ClusterAdminRoleBinding,
// CIS 4.1
"cluster-admin-role-binding": ClusterAdminRoleBinding,
"access-to-secrets": AccessToSecrets,
"wildcard-in-rules": WildcardInRules,
"access-to-create-pods": AccessToCreatePods,
"default-service-account": DefaultServiceAccount,
"sa-token-automount": TokenAutomount,
}

type LinterCheck struct {
Expand Down
31 changes: 22 additions & 9 deletions controller/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,20 @@ func New(
k8sVersion version.Interface,
) *Controller {
typeInformerMap := map[reflect.Type]cache.SharedInformer{
reflect.TypeOf(&corev1.Node{}): f.Core().V1().Nodes().Informer(),
reflect.TypeOf(&corev1.Pod{}): f.Core().V1().Pods().Informer(),
reflect.TypeOf(&corev1.Namespace{}): f.Core().V1().Namespaces().Informer(),
reflect.TypeOf(&corev1.Service{}): f.Core().V1().Services().Informer(),
reflect.TypeOf(&corev1.Node{}): f.Core().V1().Nodes().Informer(),
reflect.TypeOf(&corev1.Pod{}): f.Core().V1().Pods().Informer(),
reflect.TypeOf(&corev1.Namespace{}): f.Core().V1().Namespaces().Informer(),
reflect.TypeOf(&corev1.Service{}): f.Core().V1().Services().Informer(),
reflect.TypeOf(&appsv1.Deployment{}): f.Apps().V1().Deployments().Informer(),
reflect.TypeOf(&appsv1.DaemonSet{}): f.Apps().V1().DaemonSets().Informer(),
reflect.TypeOf(&appsv1.ReplicaSet{}): f.Apps().V1().ReplicaSets().Informer(),
reflect.TypeOf(&appsv1.StatefulSet{}): f.Apps().V1().StatefulSets().Informer(),
reflect.TypeOf(&batchv1.Job{}): f.Batch().V1().Jobs().Informer(),
// RBAC
reflect.TypeOf(&rbacv1.ClusterRoleBinding{}): f.Rbac().V1().ClusterRoleBindings().Informer(),
reflect.TypeOf(&appsv1.Deployment{}): f.Apps().V1().Deployments().Informer(),
reflect.TypeOf(&appsv1.ReplicaSet{}): f.Apps().V1().ReplicaSets().Informer(),
reflect.TypeOf(&appsv1.DaemonSet{}): f.Apps().V1().DaemonSets().Informer(),
reflect.TypeOf(&appsv1.StatefulSet{}): f.Apps().V1().StatefulSets().Informer(),
reflect.TypeOf(&batchv1.Job{}): f.Batch().V1().Jobs().Informer(),
reflect.TypeOf(&rbacv1.RoleBinding{}): f.Rbac().V1().RoleBindings().Informer(),
reflect.TypeOf(&rbacv1.ClusterRole{}): f.Rbac().V1().ClusterRoles().Informer(),
reflect.TypeOf(&rbacv1.Role{}): f.Rbac().V1().Roles().Informer(),
}

if k8sVersion.MinorInt() >= 21 {
Expand Down Expand Up @@ -175,5 +179,14 @@ func addObjectMeta(o Object) {
case *rbacv1.ClusterRoleBinding:
o.Kind = "ClusterRoleBinding"
o.APIVersion = "rbac.authorization.k8s.io/v1"
case *rbacv1.RoleBinding:
o.Kind = "RoleBinding"
o.APIVersion = "rbac.authorization.k8s.io/v1"
case *rbacv1.ClusterRole:
o.Kind = "ClusterRole"
o.APIVersion = "rbac.authorization.k8s.io/v1"
case *rbacv1.Role:
o.Kind = "Role"
o.APIVersion = "rbac.authorization.k8s.io/v1"
}
}
3 changes: 3 additions & 0 deletions delta/subscriber.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,9 @@ func (s *Subscriber) RequiredInformers() []reflect.Type {
reflect.TypeOf(&appsv1.DaemonSet{}),
reflect.TypeOf(&appsv1.StatefulSet{}),
reflect.TypeOf(&rbacv1.ClusterRoleBinding{}),
reflect.TypeOf(&rbacv1.RoleBinding{}),
reflect.TypeOf(&rbacv1.ClusterRole{}),
reflect.TypeOf(&rbacv1.Role{}),
reflect.TypeOf(&batchv1.Job{}),
}
if s.k8sVersionMinor >= 21 {
Expand Down
89 changes: 89 additions & 0 deletions linters/kubelinter/customchecks/automount.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package customchecks

import (
"fmt"
"strings"

"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/util"
)

func Check() *config.Check {
return &config.Check{
Name: "sa-token-automount",
Description: "Service Account Token automount is not disabled",
Template: "sa-token-automount",
Params: map[string]interface{}{},
}
}

func init() {
templates.Register(check.Template{
HumanName: "Service Account Token mounts",
Key: "sa-token-automount",
SupportedObjectKinds: config.ObjectKindsDesc{
ObjectKinds: []string{objectkinds.DeploymentLike},
},
Parameters: ParamDescs,
ParseAndValidateParams: ParseAndValidate,
Instantiate: WrapInstantiateFunc(func(_ Params) (check.Func, error) {
return func(_ lintcontext.LintContext, object lintcontext.Object) []diagnostic.Diagnostic {
podSpec, found := extract.PodSpec(object.K8sObject)
if !found {
return nil
}
if podSpec.AutomountServiceAccountToken != nil && *podSpec.AutomountServiceAccountToken {
return nil
}
return []diagnostic.Diagnostic{{Message: "Resource does not have service account token automount disabled"}}
}, nil
}),
})
}

type Params struct {
}

var (
// Use some imports in case they don't get used otherwise.
_ = util.MustParseParameterDesc
_ = fmt.Sprintf

ParamDescs = []check.ParameterDesc{}
)

func (p *Params) Validate() error {
var validationErrors []string
if len(validationErrors) > 0 {
return fmt.Errorf("invalid parameters: %s", strings.Join(validationErrors, ", "))
}
return nil
}

// ParseAndValidate instantiates a Params object out of the passed map[string]interface{},
// validates it, and returns it.
// The return type is interface{} to satisfy the type in the Template struct.
func ParseAndValidate(m map[string]interface{}) (interface{}, error) {
var p Params
if err := util.DecodeMapStructure(m, &p); err != nil {
return nil, err
}
if err := p.Validate(); err != nil {
return nil, err
}
return p, nil
}

// WrapInstantiateFunc is a convenience wrapper that wraps an untyped instantiate function
// into a typed one.
func WrapInstantiateFunc(f func(p Params) (check.Func, error)) func(interface{}) (check.Func, error) {
return func(paramsInt interface{}) (check.Func, error) {
return f(paramsInt.(Params))
}
}
5 changes: 5 additions & 0 deletions linters/kubelinter/kubelinter.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ import (
"k8s.io/apimachinery/pkg/types"

casttypes "github.com/castai/sec-agent/castai"
"github.com/castai/sec-agent/linters/kubelinter/customchecks"
)

func New(checks []string) *Linter {
Expand All @@ -79,6 +80,10 @@ func (l *Linter) Run(objects []lintcontext.Object) ([]casttypes.LinterCheck, err
return nil, fmt.Errorf("load info from registry: %w", err)
}

if err := registry.Register(customchecks.Check()); err != nil {
return nil, fmt.Errorf("load custom CAST check: %w", err)
}

cfg := kubelinterconfig.Config{
Checks: kubelinterconfig.ChecksConfig{
AddAllBuiltIn: true,
Expand Down

0 comments on commit eeb9c88

Please sign in to comment.