policies: drop-cap-net-raw policy does not work as expected

Software version numbers

Kubernetes: v1.20.8 Kyverno version: v1.4.2-rc3

Describe the bug The resource which has multiple drop capabilities can not pass the drop-cap-net-raw policy.

To Reproduce

  1. Apply drop-cap-net-raw policy
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: drop-cap-net-raw
  annotations:
    policies.kyverno.io/title: Drop CAP_NET_RAW
    policies.kyverno.io/category: Best Practices
    policies.kyverno.io/severity: medium
    policies.kyverno.io/subject: Pod
    policies.kyverno.io/description: >-
      Capabilities permit privileged actions without giving full root access. The
      CAP_NET_RAW capability, enabled by default, allows processes in a container to
      forge packets and bind to any interface potentially leading to MitM attacks.
      This policy ensures that all containers explicitly drop the CAP_NET_RAW
      ability.
spec:
  validationFailureAction: enforce
  rules:
  - name: drop-cap-net-raw
    match:
      resources:
        kinds:
        - Pod
    validate:
      message: "The capability CAP_NET_RAW must be explicitly dropped."
      pattern:
        spec:
          containers:
          - securityContext:
              capabilities:
                drop: ["NET_RAW"]
          =(initContainers):
          - securityContext:
              capabilities:
                drop: ["NET_RAW"]
  1. Apply the following resource
apiVersion: v1
kind: Pod
metadata:
  name: test
spec:
  containers:
  - name: test
    image: debian:9
    command: ["/bin/sh", "-c", "sleep infinity"]
    securityContext:
      capabilities:
        drop:
        - NET_RAW
        - SETUID

Expected behavior Resource Pod/default/test created successfully.

Actual behavior Resource Pod/default/test was blocked due to the drop-cap-net-raw policy.

About this issue

  • Original URL
  • State: closed
  • Created 3 years ago
  • Comments: 23 (17 by maintainers)

Most upvoted comments

@Danny-Wei although I still have a question out to the chief maintainer of this JMESPath capability, with some help from the JMESPath community I was able to build a query which does definitely work in ensuring that all containers, whether they be initContainers or containers, explicitly specify NET_RAW in their drop[] arrays.

Here’s the Kyverno policy which should (yet doesn’t currently) work.

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: drop-cap-net-raw
  annotations:
    policies.kyverno.io/title: Drop CAP_NET_RAW
    policies.kyverno.io/category: Best Practices
    policies.kyverno.io/severity: medium
    policies.kyverno.io/subject: Pod
    policies.kyverno.io/description: >-
      Capabilities permit privileged actions without giving full root access. The
      CAP_NET_RAW capability, enabled by default, allows processes in a container to
      forge packets and bind to any interface potentially leading to MitM attacks.
      This policy ensures that all containers explicitly drop the CAP_NET_RAW
      ability.
spec:
  validationFailureAction: enforce
  background: false
  rules:
  - name: drop-cap-net-raw
    match:
      resources:
        kinds:
        - Pod
    validate:
      message: The capability NET_RAW must be explicitly dropped.
      deny:
        conditions:
          any:
          - key: "request.object.spec.[containers, initContainers][].securityContext.capabilities.drop.contains(@, 'NET_RAW') | !contains(@, `false`)"
            operator: NotEquals
            value: "true"

We know this JMESPath query works because it has been verified on the jmespath.org (https://jmespath.org/) site’s live query. Here’s what you can use to verify yourself.

Paste in the JSON Pod spec:

{
  "apiVersion": "v1",
  "kind": "Pod",
  "metadata": {
    "name": "test"
  },
  "spec": {
    "initContainers": [
      {
        "name": "jimmy",
        "image": "defdasdabian:923",
        "command": [
          "/bin/sh",
          "-c",
          "sleep infinity"
        ],
        "securityContext": {
          "capabilities": {
            "drop": [
              "NET_RAW",
              "SETUID"
            ]
          }
        }
      }
    ],
    "containers": [
      {
        "name": "test",
        "image": "defdasdabian:923",
        "command": [
          "/bin/sh",
          "-c",
          "sleep infinity"
        ],
        "securityContext": {
          "capabilities": {
            "drop": [
              "SETUID",
              "CAP_FOO_BAR",
              "NET_RAW"
            ]
          }
        }
      },
      {
        "name": "asdf",
        "image": "defdasdabian:923",
        "command": [
          "/bin/sh",
          "-c",
          "sleep infinity"
        ],
        "securityContext": {
          "capabilities": {
            "drop": [
              "NET_RAW",
              "SOME_THING"
            ]
          }
        }
      }
    ]
  }
}

In the query box on top, paste in the full query:

spec.[containers, initContainers][].securityContext.capabilities.drop.contains(@, 'NET_RAW') | !contains(@, `false`)

The result should return true.

Go to any one of the drop[] arrays and change one of the NET_RAW entries to something else, perhaps NET_ASDF and see the return value is now false. You should be able to do this for any of the other NET_RAW entries as well and until every init/container has this it should continue to report false.

Update: With Kyverno 1.4.3, these contains() policies now work as intended and a slight modification to the one above works.

cpol.yaml

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: drop-cap-net-raw
  annotations:
    policies.kyverno.io/title: Drop CAP_NET_RAW
    policies.kyverno.io/category: Best Practices
    policies.kyverno.io/severity: medium
    policies.kyverno.io/subject: Pod
    policies.kyverno.io/description: >-
      Capabilities permit privileged actions without giving full root access. The
      CAP_NET_RAW capability, enabled by default, allows processes in a container to
      forge packets and bind to any interface potentially leading to MitM attacks.
      This policy ensures that all containers explicitly drop the CAP_NET_RAW
      ability.
spec:
  validationFailureAction: enforce
  background: false
  rules:
  - name: drop-cap-net-raw
    match:
      resources:
        kinds:
        - Pod
    validate:
      message: The capability NET_RAW must be explicitly dropped.
      deny:
        conditions:
        # Get all the entries in each initContainers and containers drop[] array and ensures that every instance contains NET_RAW. If not, deny the request.
        # backticks around false statement (in the key) implies a JSON object and so the value must not be in quotes or is interpreted as a string.
        - key: "{{request.object.spec.[containers, initContainers][].securityContext.capabilities.drop.contains(@, 'NET_RAW') | !contains(@, `false`)}}"
          operator: Equals
          value: false

Test with pod.yaml

apiVersion: v1
kind: Pod
metadata:
  name: test
spec:
  initContainers:
  - name: jimmy
    image: defdasdabian:923
    command: ["/bin/sh", "-c", "sleep infinity"]
    securityContext:
      capabilities:
        drop:
        - NET_RAW
        - SETUID
  containers:
  - name: test
    image: defdasdabian:923
    command: ["/bin/sh", "-c", "sleep infinity"]
    securityContext:
      capabilities:
        drop:
        - SETUID
        - CAP_FOO_BAR
        - NET_RAW
  - name: asdf
    image: defdasdabian:923
    command: ["/bin/sh", "-c", "sleep infinity"]
    securityContext:
      capabilities:
        drop:
        - NET_RAW
        - SOME_THING

This should pass as-is. But changing any of the NET_RAW entries even slightly (for example, changing one to XXXNET_RAWYYY) will cause the Pod to fail.

Using this JMESPath query will be useful many other places in writing policies for Kyverno because it is a condensed yet effective way of checking if a value is found in every array of a given init/container.

Closing issue.

Yes, you’re right, you’ve picked up on something I did in my testing. However, two things:

  1. Even if someone could specify a capability that contains that as a substring, it wouldn’t do anything. So the underlying kernel wouldn’t recognize XXXNET_RAWYYY or anything similar. The capability would have to be precise.
  2. It should just be possible to do full string match which is what the contains function does, however I’m noticing the behavior of this in Kyverno is not consistent with the behavior in the jp tool which contains the upstream JMESPath logic. We are investigating.