Ruleset Policies

Published: April 29, 2024

What are Ruleset Policies

Ruleset Policies are a way of defining a set of allow or deny rules for a given scope. Currently, ruleset policies are supported for the following scope(s):

  • Kubernetes Clusters

Ruleset Policies, themselves are very simple. They define a scope with a selector, and they contain a list of pointers to reusable rulesets. And they also define a set of response actions when deviations occur.

apiVersion: spyderbat/v1
kind: SpyderbatPolicy
metadata:
  createdBy: demo.user@spyderbat.com
  creationTimestamp: 1712787973
  lastUpdatedBy: demo.user@spyderbat.com
  lastUpdatedTimestamp: 1714417836
  name: demo-cluster-policy
  selectorHash: 66e45259eba6ed4365e28e7e673a18cf
  type: cluster
  uid: pol:xxxxxxxxxxxxxxxxxxxx
  version: 1
spec:
  clusterSelector:
    matchFields:
      name: demo-cluster
  enabled: true
  mode: audit
  rulesets:
  - demo-cluster-ruleset
  response:
    default:
    - makeRedFlag:
        severity: high
    actions: []

The Rulesets used by a Ruleset Policy are policy-agnostic and as such can be defined once and used across multiple policies. Rulesets contain a set of allow or deny rules. Each rule contains a target, verb, list of values, and optional selectors (for additional scoping).

  • Target: what the rule is referring to within the scope of the policy.

    • ex. container::image this means that we are allowing or denying containers using images specified in the values field.

  • Verb: The currently available verbs for ruleset rules are allow or deny. Any object matching a deny rule will generate a Deviation.

  • Values: This is the set of values that are allowed or denied. If the target is container::image then the values should be container images that are either allowed or denied.

  • Selectors: Optional selectors that further define the scope of a single rule. For instance you may want a rule that defines allowed activity in a specific namespace within a cluster.

The following is an example rule that allows containers with the images docker.io/guyduchatelet/spyderbat-demo:1 and docker.io/library/mongo:latest in the namespaces rsvp-svc-dev and rsvp-svc-prod.

namespaceSelector:
  matchExpressions:
  - {key: kubernetes.io/metadata.name, operator: In, values: [rsvp-svc-dev, rsvp-svc-prod]}
target: container::image
values:
- docker.io/guyduchatelet/spyderbat-demo:1
- docker.io/library/mongo:latest
verb: allow

The following rule denies the image docker.io/guyduchatelet/spyderbat-demo:2 globally.

target: container::image
values:
- docker.io/guyduchatelet/spyderbat-demo:2
verb: deny

The following is an example ruleset automatically generated from a demo cluster:

apiVersion: spyderbat/v1
kind: SpyderbatRuleset
metadata:
  createdBy: demo.user@spyderbat.com
  creationTimestamp: 1712787972
  lastUpdatedBy: demo.user@spyderbat.com
  lastUpdatedTimestamp: 1714162618
  name: demo-cluster-ruleset
  type: cluster
  uid: rs:xxxxxxxxxxxxxxxxxxxx
  version: 1
spec:
  rules:
  - namespaceSelector:
      matchExpressions:
      - {key: kubernetes.io/metadata.name, operator: In, values: [rsvp-svc-dev, rsvp-svc-prod]}
    target: container::image
    values:
    - docker.io/guyduchatelet/spyderbat-demo:1
    - docker.io/library/mongo:latest
    verb: allow
  - namespaceSelector:
      matchLabels:
        kubernetes.io/metadata.name: kube-system
    target: container::image
    values:
    - 602401143452.dkr.ecr.us-west-2.amazonaws.com/amazon-k8s-cni-init:v1.10.1-eksbuild.1
    - 602401143452.dkr.ecr.us-west-2.amazonaws.com/amazon-k8s-cni:v1.10.1-eksbuild.1
    - 602401143452.dkr.ecr.us-west-2.amazonaws.com/eks/coredns:v1.8.7-eksbuild.1
    - 602401143452.dkr.ecr.us-west-2.amazonaws.com/eks/kube-proxy:v1.22.6-eksbuild.1
    - public.ecr.aws/aws-secrets-manager/secrets-store-csi-driver-provider-aws:1.0.r2-58-g4ddce6a-2024.01.31.21.42
    - registry.k8s.io/csi-secrets-store/driver:v1.4.2
    - registry.k8s.io/sig-storage/csi-node-driver-registrar:v2.10.0
    - registry.k8s.io/sig-storage/livenessprobe:v2.12.0
    verb: allow
  - target: container::image
    values:
    - docker.io/guyduchatelet/spyderbat-demo:2
    verb: deny
  - namespaceSelector:
      matchLabels:
        kubernetes.io/metadata.name: prometheus
    target: container::image
    values:
    - quay.io/prometheus/node-exporter:v1.7.0
    - quay.io/prometheus/pushgateway:v1.7.0
    - registry.k8s.io/kube-state-metrics/kube-state-metrics:v2.10.1
    verb: allow
  - namespaceSelector:
      matchLabels:
        kubernetes.io/metadata.name: spyderbat
    target: container::image
    values:
    - public.ecr.aws/a6j2k0g1/aws-agent:latest
    - public.ecr.aws/a6j2k0g1/nano-agent:latest
    verb: allow

How rules are evaluated

Rules are evaluated based on a specific hierarchy. Scoped rules take precedence over global rules, explicit rules take precedence over wildcarded rules, deny rules are evaluated first, and anything that matches no rules is denied by default.

Evaluation Order

  1. Scoped explicit deny

  2. Scoped explicit allow

  3. Scoped wildcarded deny

  4. Scoped wildcarded allow

  5. Global explicit deny

  6. Global explicit allow

  7. Global wildcarded deny

  8. Global wildcarded allow

  9. Default deny

*Scoped

the rule contains a selector

*Explicit

the matched value contains no wildcard characters

*Wildcarded

the matched value contains a wildcard character *

Examples

Scenario 1 (Global Explicit Allow):

Image: docker.io/guyduchatelet/spyderbat-demo:1

spec:
  rules:
  - target: container::image
    values:
    - docker.io/guyduchatelet/spyderbat-demo:1
    verb: allow

A container with the image docker.io/guyduchatelet/spyderbat-demo:1 would be allowed globally.

Scenario 2 (Default Deny)

Image: docker.io/guyduchatelet/spyderbat-demo:bad-tag

spec:
  rules:
  - target: container::image
    values:
    - docker.io/guyduchatelet/spyderbat-demo:1
    verb: allow

A container with the image docker.io/guyduchatelet/spyderbat-demo:bad-tag would be denied by default.

Scenario 3 (Global Explicit Allow with Global Wildcard Deny):

Image 1: docker.io/guyduchatelet/spyderbat-demo:1 Image 2: docker.io/guyduchatelet/spyderbat-demo:bad-tag

spec:
  rules:
  - target: container::image
    values:
    - docker.io/guyduchatelet/spyderbat-demo:1
    verb: allow
  - target: container::image
    values:
    - docker.io/guyduchatelet/spyderbat-demo:*
    verb: deny

Global explicit allow is evaluated before global wildcarded deny so Image 1 is allowed. Image 2 is denied by the global wildcarded deny.

Scenario 3 (Scoped Wildcarded Allow with Global Explicit Deny):

Image 1: docker.io/guyduchatelet/spyderbat-demo:1 Namespace labels: {kubernetes.io/metadata.name: rsvp-demo-prod} Image 2: docker.io/guyduchatelet/spyderbat-demo:bad-tag Namespace labels: {kubernetes.io/metadata.name: rsvp-demo-prod} Image 3: docker.io/guyduchatelet/spyderbat-demo:bad-tag Namespace labels: {kubernetes.io/metadata.name: rsvp-demo-dev}

spec:
  rules:
  - namespaceSelector:
      matchLabels:
        kubernetes.io/metadata.name: rsvp-demo-prod
    target: container::image
    values:
    - docker.io/guyduchatelet/spyderbat-demo:*
    verb: allow
  - target: container::image
    values:
    - docker.io/guyduchatelet/spyderbat-demo:bad-tag
    verb: deny

Since the first rule has a namespace selector, that rule is scoped. Scoped wildcarded allow rules are evaluated before global explicit deny rules so Image 1 and Image 2 are allowed. Image 3 is denied by the global explicit deny rule.

Scenario 4 (Scoped Explicit Allow with Scoped Wildcarded Deny)

Image 1: docker.io/guyduchatelet/spyderbat-demo:1 Namespace labels: {kubernetes.io/metadata.name: rsvp-demo-prod} Image 2: docker.io/guyduchatelet/spyderbat-demo:bad-tag Namespace labels: {kubernetes.io/metadata.name: rsvp-demo-prod} Image 3: docker.io/guyduchatelet/spyderbat-demo:bad-tag Namespace labels: {kubernetes.io/metadata.name: rsvp-demo-dev}

spec:
  rules:
  - namespaceSelector:
      matchLabels:
        kubernetes.io/metadata.name: rsvp-demo-prod
    target: container::image
    values:
    - docker.io/guyduchatelet/spyderbat-demo:1
    verb: allow
  - namespaceSelector:
      matchLabels:
        kubernetes.io/metadata.name: rsvp-demo-prod
    target: container::image
    values:
    - docker.io/guyduchatelet/spyderbat-demo:*
    verb: deny

Both rules are scoped because they have a namespace selector. Scoped explicit allow rules are evaluated before scoped wildcarded deny rules so Image 1 is allowed. Image 2 is denied by the scope wildcarded deny rule. Image 3 does not match the scope of any rule so it is denied by default.

Scenario 5 (Scoped Explicit Allow with Scoped Explicit Deny)

Image: docker.io/guyduchatelet/spyderbat-demo:1 Namespace labels: {kubernetes.io/metadata.name: rsvp-demo-prod}

spec:
  rules:
  - namespaceSelector:
      matchLabels:
        kubernetes.io/metadata.name: rsvp-demo-prod
    target: container::image
    values:
    - docker.io/guyduchatelet/spyderbat-demo:1
    verb: allow
  - namespaceSelector:
      matchLabels:
        kubernetes.io/metadata.name: rsvp-demo-prod
    target: container::image
    values:
    - docker.io/guyduchatelet/spyderbat-demo:1
    verb: deny

Scoped explicit deny rules are evaluated before scope explicit allow rules, so the image is denied by scoped explicit allow.

Quick Start Tutorial

To quickly get started using using Cluster Ruleset Policies follow our tutorial using spyctl.

How to Put Guardrails Around Your K8s Cluster

Last updated

© SPYDERBAT, Inc., All Rights Reserved