Skip to content

Commit

Permalink
Add policy bundles (#146)
Browse files Browse the repository at this point in the history
* Add policy bundles

---------

Co-authored-by: Darius Mejeras <darius@mejeras.lt>
  • Loading branch information
damejeras and damejeras committed Jul 4, 2023
1 parent 881d3ec commit 1ef9a85
Show file tree
Hide file tree
Showing 8 changed files with 119 additions and 10 deletions.
60 changes: 60 additions & 0 deletions castai/linter_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,66 @@ var LinterRuleMap = map[string]LinterRule{
"additional-capabilities": AdditionalCapabilities,
}

var HostIsolationBundle = map[string]LinterRule{
"drop-net-raw-capability": DropNetRawCapability,
"host-ipc": HostIPC,
"host-network": HostNetwork,
"host-pid": HostPID,
"privilege-escalation-container": PrivilegeEsxalationContainer,
"privileged-container": PrivilegedContainer,
"run-as-non-root": RunAsNonRoot,
"unsafe-sysctls": UnsafeSysctls,
"additional-capabilities": AdditionalCapabilities,
"no-read-only-root-fs": NoReadOnlyRootFS,
"privileged-ports": PrivilegedProts,
}

var GoodPracticesBundle = map[string]LinterRule{
"sa-token-automount": TokenAutomount,
"exposed-services": ExposedService,
"deprecated-service-account-field": DeprecatedServiceAccountField,
"latest-tag": LatestTag,
"mismatching-selector": MismatchingSelector,
"no-anti-affinity": NoAntiAffinity,
"no-liveness-probe": NoLivenessProbe,
"no-readiness-probe": NoReadinessProe,
"no-rolling-update-strategy": NoRollingUpdateStrategy,
"unset-memory-requirements": UnsetMempryRequirements,
"use-namespace": UseNamespace,
"default-service-account": DefaultServiceAccount,
"has-security-context": HasSecurityContext,
"read-secret-from-env-var": ReadSecretFromEnvVar,
"env-var-secret": EnvVarSecret,
"network-policy-per-namespace": NetworkPolicyPerNamespace,
}

var PortsBundle = map[string]LinterRule{
"invalid-target-ports": InvalidTargetPorta,
"ssh-port": SSHPort,
}

var MountPointsBundle = map[string]LinterRule{
"docker-sock": DockerSock,
"containerd-sock": ContainerdSock,
"writable-host-mount": WritableHostMount,
"unsafe-proc-mount": UnsafeProcMount,
"sensitive-host-mounts": SensitiveHostMounts,
}

var DanglingResourcesBundle = map[string]LinterRule{
"dangling-service": DanglingService,
"dangling-networkpolicy": DanglingNetworkPolicy,
"dangling-horizontalpodautoscaler": DanglingHPA,
"dangling-ingress": DanglingIngress,
}

var RBACBundle = map[string]LinterRule{
"cluster-admin-role-binding": ClusterAdminRoleBinding,
"access-to-secrets": AccessToSecrets,
"wildcard-in-rules": WildcardInRules,
"access-to-create-pods": AccessToCreatePods,
}

type LinterCheck struct {
ResourceID string `json:"resourceID"`
Passed *LinterRuleSet `json:"passed"`
Expand Down
2 changes: 2 additions & 0 deletions charts/castai-kvisor/templates/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,8 @@ spec:
value: {{ ((.Values.policyEnforcement | default dict).enabled | default false) | quote }}
- name: POLICY_ENFORCEMENT_WEBHOOK_NAME
value: {{ ((.Values.policyEnforcement | default dict).webhookName | default "kvisor.cast.ai") | quote }}
- name: POLICY_ENFORCEMENT_BUNDLES
value: {{ (join "," (.Values.policyEnforcement | default dict).bundles | default "") | quote }}
- name: STATUS_PORT
value: {{ ((.Values.kvisor | default dict).statusPort | default 7071) | quote }}
- name: API_URL
Expand Down
1 change: 1 addition & 0 deletions charts/castai-kvisor/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ updateStrategy:
policyEnforcement:
enabled: false
webhookName: "kvisor.cast.ai"
bundles: ["host-isolation", "good-practices", "ports", "mount-points", "dangling-resources", "rbac"]

# Kvisor service configuration.
kvisor:
Expand Down
2 changes: 1 addition & 1 deletion cmd/agent/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ func run(ctx context.Context, logger logrus.FieldLogger, castaiClient castai.Cli
return fmt.Errorf("setting up linter: %w", err)
}

policyEnforcer := policy.NewEnforcer(linter)
policyEnforcer := policy.NewEnforcer(linter, cfg.PolicyEnforcement)
telemetryObservers = append(telemetryObservers, policyEnforcer.TelemetryObserver())

if cfg.Linter.Enabled {
Expand Down
14 changes: 12 additions & 2 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package config
import (
"fmt"
"os"
"strings"
"time"

"github.com/kelseyhightower/envconfig"
Expand Down Expand Up @@ -33,8 +34,17 @@ type Config struct {
}

type PolicyEnforcement struct {
Enabled bool `envconfig:"ENABLED" yaml:"enabled"`
WebhookName string `envconfig:"WEBHOOK_NAME" yaml:"webhookName"`
Enabled bool `envconfig:"ENABLED" yaml:"enabled"`
WebhookName string `envconfig:"WEBHOOK_NAME" yaml:"webhookName"`
Bundles Bundles `envconfig:"BUNDLES" yaml:"bundles"`
}

type Bundles []string

func (b *Bundles) Decode(input string) error {
result := strings.Split(input, ",")
*b = result
return nil
}

type CloudScan struct {
Expand Down
3 changes: 3 additions & 0 deletions config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ func newTestConfig() Config {
StatusPort: 7071,
Provider: "gke",
DeltaSyncInterval: 15 * time.Second,
PolicyEnforcement: PolicyEnforcement{
Bundles: Bundles{},
},
ImageScan: ImageScan{
Enabled: true,
ScanInterval: 20 * time.Second,
Expand Down
38 changes: 35 additions & 3 deletions policy/enforcer.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"sort"
"sync"

"github.com/samber/lo"
"golang.stackrox.io/kube-linter/pkg/k8sutil"
"golang.stackrox.io/kube-linter/pkg/lintcontext"
appsv1 "k8s.io/api/apps/v1"
Expand All @@ -19,6 +20,7 @@ import (

"github.com/castai/kvisor/castai"
"github.com/castai/kvisor/castai/telemetry"
"github.com/castai/kvisor/config"
"github.com/castai/kvisor/linters/kubelinter"
)

Expand All @@ -31,15 +33,41 @@ type enforcer struct {
objectFilters []objectFilter
linter *kubelinter.Linter
enforcedRules []string
bundleRules []string
mutex sync.RWMutex
cfg *config.PolicyEnforcement
}

func NewEnforcer(linter *kubelinter.Linter) Enforcer {
func NewEnforcer(linter *kubelinter.Linter, cfg config.PolicyEnforcement) Enforcer {
rules := map[string]struct{}{}
for _, bundle := range cfg.Bundles {
var ruleMap map[string]castai.LinterRule
switch bundle {
case "host-isolation":
ruleMap = castai.HostIsolationBundle
case "good-practices":
ruleMap = castai.GoodPracticesBundle
case "ports":
ruleMap = castai.PortsBundle
case "mount-points":
ruleMap = castai.MountPointsBundle
case "dangling-resources":
ruleMap = castai.DanglingResourcesBundle
case "rbac":
ruleMap = castai.RBACBundle
}
for key := range ruleMap {
rules[key] = struct{}{}
}
}

return &enforcer{
objectFilters: []objectFilter{
skipObjectsWithOwners,
},
linter: linter,
linter: linter,
bundleRules: lo.Keys(rules),
cfg: &cfg,
}
}

Expand Down Expand Up @@ -176,6 +204,10 @@ func (e *enforcer) Handle(ctx context.Context, request admission.Request) admiss
return admission.Errored(http.StatusInternalServerError, err)
}

if len(checks) == 0 {
return admission.Allowed("no rules enforced on object")
}

if len(checks) != 1 {
return admission.Errored(http.StatusInternalServerError, fmt.Errorf("unexpected checks len %d", len(checks)))
}
Expand All @@ -192,5 +224,5 @@ func (e *enforcer) Handle(ctx context.Context, request admission.Request) admiss
func (e *enforcer) rules() []string {
e.mutex.RLock()
defer e.mutex.RUnlock()
return e.enforcedRules
return lo.Uniq(append(e.enforcedRules, e.bundleRules...))
}
9 changes: 5 additions & 4 deletions policy/enforcer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"

"github.com/castai/kvisor/castai"
"github.com/castai/kvisor/config"
"github.com/castai/kvisor/linters/kubelinter"
)

Expand All @@ -25,7 +26,7 @@ func TestEnforcer(t *testing.T) {
t.Run("denies deployment", func(t *testing.T) {
r := require.New(t)
ctx := context.Background()
e := NewEnforcer(linter)
e := NewEnforcer(linter, config.PolicyEnforcement{})
obs := e.TelemetryObserver()
obs(&castai.TelemetryResponse{
EnforcedRules: lo.Keys(castai.LinterRuleMap),
Expand All @@ -49,7 +50,7 @@ func TestEnforcer(t *testing.T) {
t.Run("request with no rules enforced", func(t *testing.T) {
r := require.New(t)
ctx := context.Background()
e := NewEnforcer(linter)
e := NewEnforcer(linter, config.PolicyEnforcement{})
var req admission.Request
b, err := os.ReadFile("../testdata/admission/sample-deployment.json")
r.NoError(err)
Expand All @@ -69,7 +70,7 @@ func TestEnforcer(t *testing.T) {
t.Run("allows pod", func(t *testing.T) {
r := require.New(t)
ctx := context.Background()
e := NewEnforcer(linter)
e := NewEnforcer(linter, config.PolicyEnforcement{})
obs := e.TelemetryObserver()
obs(&castai.TelemetryResponse{
EnforcedRules: []string{"latest-tag"},
Expand All @@ -93,7 +94,7 @@ func TestEnforcer(t *testing.T) {
t.Run("denies pod with owners", func(t *testing.T) {
r := require.New(t)
ctx := context.Background()
e := NewEnforcer(linter)
e := NewEnforcer(linter, config.PolicyEnforcement{})
obs := e.TelemetryObserver()
obs(&castai.TelemetryResponse{
EnforcedRules: lo.Keys(castai.LinterRuleMap),
Expand Down

0 comments on commit 1ef9a85

Please sign in to comment.