Untitled

mail@pastecode.io avatar
unknown
plain_text
a year ago
12 kB
3
Indexable
Never
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
  name: k8spspallowedusers
  annotations:
    description: >-
      Controls the user and group IDs of the container and some volumes.
      Corresponds to the `runAsUser`, `runAsGroup`, `supplementalGroups`, and
      `fsGroup` fields in a PodSecurityPolicy. For more information, see
      https://kubernetes.io/docs/concepts/policy/pod-security-policy/#users-and-groups
spec:
  crd:
    spec:
      names:
        kind: K8sPSPAllowedUsers
      validation:
        openAPIV3Schema:
          type: object
          description: >-
            Controls the user and group IDs of the container and some volumes.
            Corresponds to the `runAsUser`, `runAsGroup`, `supplementalGroups`, and
            `fsGroup` fields in a PodSecurityPolicy. For more information, see
            https://kubernetes.io/docs/concepts/policy/pod-security-policy/#users-and-groups
          properties:
            exemptImages:
              description: >-
                Any container that uses an image that matches an entry in this list will be excluded
                from enforcement. Prefix-matching can be signified with `*`. For example: `my-image-*`.

                It is recommended that users use the fully-qualified Docker image name (e.g. start with a domain name)
                in order to avoid unexpectedly exempting images from an untrusted repository.
              type: array
              items:
                type: string
            runAsUser:
              type: object
              description: "Controls which user ID values are allowed in a Pod or container-level SecurityContext."
              properties:
                rule:
                  type: string
                  description: "A strategy for applying the runAsUser restriction."
                  enum:
                    - MustRunAs
                    - MustRunAsNonRoot
                    - RunAsAny
                ranges:
                  type: array
                  description: "A list of user ID ranges affected by the rule."
                  items:
                    type: object
                    description: "The range of user IDs affected by the rule."
                    properties:
                      min:
                        type: integer
                        description: "The minimum user ID in the range, inclusive."
                      max:
                        type: integer
                        description: "The maximum user ID in the range, inclusive."
            runAsGroup:
              type: object
              description: "Controls which group ID values are allowed in a Pod or container-level SecurityContext."
              properties:
                rule:
                  type: string
                  description: "A strategy for applying the runAsGroup restriction."
                  enum:
                    - MustRunAs
                    - MayRunAs
                    - RunAsAny
                ranges:
                  type: array
                  description: "A list of group ID ranges affected by the rule."
                  items:
                    type: object
                    description: "The range of group IDs affected by the rule."
                    properties:
                      min:
                        type: integer
                        description: "The minimum group ID in the range, inclusive."
                      max:
                        type: integer
                        description: "The maximum group ID in the range, inclusive."
            supplementalGroups:
              type: object
              description: "Controls the supplementalGroups values that are allowed in a Pod or container-level SecurityContext."
              properties:
                rule:
                  type: string
                  description: "A strategy for applying the supplementalGroups restriction."
                  enum:
                    - MustRunAs
                    - MayRunAs
                    - RunAsAny
                ranges:
                  type: array
                  description: "A list of group ID ranges affected by the rule."
                  items:
                    type: object
                    description: "The range of group IDs affected by the rule."
                    properties:
                      min:
                        type: integer
                        description: "The minimum group ID in the range, inclusive."
                      max:
                        type: integer
                        description: "The maximum group ID in the range, inclusive."
            fsGroup:
              type: object
              description: "Controls the fsGroup values that are allowed in a Pod or container-level SecurityContext."
              properties:
                rule:
                  type: string
                  description: "A strategy for applying the fsGroup restriction."
                  enum:
                    - MustRunAs
                    - MayRunAs
                    - RunAsAny
                ranges:
                  type: array
                  description: "A list of group ID ranges affected by the rule."
                  items:
                    type: object
                    description: "The range of group IDs affected by the rule."
                    properties:
                      min:
                        type: integer
                        description: "The minimum group ID in the range, inclusive."
                      max:
                        type: integer
                        description: "The maximum group ID in the range, inclusive."
  targets:
    - target: admission.k8s.gatekeeper.sh
      rego: |
        package k8spspallowedusers

        import data.lib.exempt_container.is_exempt

        violation[{"msg": msg}] {
          fields := ["runAsUser", "runAsGroup", "supplementalGroups", "fsGroup"]
          field := fields[_]
          container := input_containers[_]
          not is_exempt(container)
          msg := get_type_violation(field, container)
        }

        get_type_violation(field, container) = msg {
          field == "runAsUser"
          params := input.parameters[field]
          msg := get_user_violation(params, container)
        }

        get_type_violation(field, container) = msg {
          field != "runAsUser"
          params := input.parameters[field]
          msg := get_violation(field, params, container)
        }

        # RunAsUser (separate due to "MustRunAsNonRoot")
        get_user_violation(params, container) = msg {
          rule := params.rule
          provided_user := get_field_value("runAsUser", container, input.review)
          not accept_users(rule, provided_user)
          msg := sprintf("Container %v is attempting to run as disallowed user %v. Allowed runAsUser: %v", [container.name, provided_user, params])
        }

        get_user_violation(params, container) = msg {
          not get_field_value("runAsUser", container, input.review)
          params.rule = "MustRunAs"
          msg := sprintf("Container %v is attempting to run without a required securityContext/runAsUser", [container.name])
        }

        get_user_violation(params, container) = msg {
          params.rule = "MustRunAsNonRoot"
          not get_field_value("runAsUser", container, input.review)
          not get_field_value("runAsNonRoot", container, input.review)
          msg := sprintf("Container %v is attempting to run without a required securityContext/runAsNonRoot or securityContext/runAsUser != 0", [container.name])
        }

        accept_users("RunAsAny", provided_user) {true}

        accept_users("MustRunAsNonRoot", provided_user) = res {res := provided_user != 0}

        accept_users("MustRunAs", provided_user) = res  {
          ranges := input.parameters.runAsUser.ranges
          res := is_in_range(provided_user, ranges)
        }

        # Group Options
        get_violation(field, params, container) = msg {
          rule := params.rule
          provided_value := get_field_value(field, container, input.review)
          not is_array(provided_value)
          not accept_value(rule, provided_value, params.ranges)
          msg := sprintf("Container %v is attempting to run as disallowed group %v. Allowed %v: %v", [container.name, provided_value, field, params])
        }
        # SupplementalGroups is array value
        get_violation(field, params, container) = msg {
          rule := params.rule
          array_value := get_field_value(field, container, input.review)
          is_array(array_value)
          provided_value := array_value[_]
          not accept_value(rule, provided_value, params.ranges)
          msg := sprintf("Container %v is attempting to run with disallowed supplementalGroups %v. Allowed %v: %v", [container.name, array_value, field, params])
        }

        get_violation(field, params, container) = msg {
          not get_field_value(field, container, input.review)
          params.rule == "MustRunAs"
          msg := sprintf("Container %v is attempting to run without a required securityContext/%v. Allowed %v: %v", [container.name, field, field, params])
        }

        accept_value("RunAsAny", provided_value, ranges) {true}

        accept_value("MayRunAs", provided_value, ranges) = res { res := is_in_range(provided_value, ranges)}

        accept_value("MustRunAs", provided_value, ranges) = res { res := is_in_range(provided_value, ranges)}


        # If container level is provided, that takes precedence
        get_field_value(field, container, review) = out {
          container_value := get_seccontext_field(field, container)
          out := container_value
        }

        # If no container level exists, use pod level
        get_field_value(field, container, review) = out {
          not has_seccontext_field(field, container)
          review.kind.kind == "Pod"
          pod_value := get_seccontext_field(field, review.object.spec)
          out := pod_value
        }

        # Helper Functions
        is_in_range(val, ranges) = res {
          matching := {1 | val >= ranges[j].min; val <= ranges[j].max}
          res := count(matching) > 0
        }

        has_seccontext_field(field, obj) {
          get_seccontext_field(field, obj)
        }

        has_seccontext_field(field, obj) {
          get_seccontext_field(field, obj) == false
        }

        get_seccontext_field(field, obj) = out {
          out = obj.securityContext[field]
        }

        input_containers[c] {
          c := input.review.object.spec.containers[_]
        }
        input_containers[c] {
          c := input.review.object.spec.initContainers[_]
        }
        input_containers[c] {
            c := input.review.object.spec.ephemeralContainers[_]
        }
      libs:
        - |
          package lib.exempt_container

          is_exempt(container) {
              exempt_images := object.get(object.get(input, "parameters", {}), "exemptImages", [])
              img := container.image
              exemption := exempt_images[_]
              _matches_exemption(img, exemption)
          }

          _matches_exemption(img, exemption) {
              not endswith(exemption, "*")
              exemption == img
          }

          _matches_exemption(img, exemption) {
              endswith(exemption, "*")
              prefix := trim_suffix(exemption, "*")
              startswith(img, prefix)
          }