Search code examples
open-policy-agentrego

rego check if item in list exists in item in another list


We have an undertermined list of items in a resource that need to be checked in case they are using one of the deprecated given paramenters. In gatekeeper, the constraint with the parameters looks like this:

apiVersion: constraints.gatekeeper.sh/v1beta1
kind: SOMEKind
metadata:
  name: somename
spec:
  match:
    kinds:
    - apiGroups: ["networking.istio.io"]
      kinds: ["EnvoyFilter"]
  parameters:
    envoyfilters:
    - http_squash:
      canonical: "envoy.filters.http.squash"
      deprecated: "envoy.squash"
    - listener_http_inspector:
      canonical: "envoy.filters.listener.http_inspector"
      deprecated: "envoy.listener.http_inspector"

The resource to check looks like this:

apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
  name: envoyfilter
spec:
  configPatches:
  - applyTo: HTTP_FILTER
    match:
      context: SIDECAR_OUTBOUND
      listener:
        filterChain:
          filter:
            name: envoy.listener.http_inspector
            subFilter:
              name: envoy.filters.http.router

We would need to make sure than for any .spec.configPatches[].match.listener.filterChain.filter.name that could exist in the analyzed resource, if that name matches any of the given "parameters.envoyfilters[].deprecated given, a violation would happen.

Due to constraints, we are unable to update opa/gk at the moment so we can't use the "import future.keywords".

The reason for having the "canonical" in the list, is because we would like to provide the "good" alertnative fitler to use in the msg of the violation.

We are trying different approaches but this is the latest (not working) one:

      violation[{"msg": msg}] {
        config_patches := input.review.object.spec.configPatches[match][listener][filterchain][filter][_].name
        deprecated_envoyfilters := input.parameters.envoyfilters
        use_deprecated_envoyfilters(config_patches,deprecated_envoyfilters)
        msg := sprintf("REVIEW OBJECT: %v", [config_patches])
      }

      contains(filters, filter) {
        filters[_] = filter
      }

      use_deprecated_envoyfilters(config_patches,deprecated_envoyfilters) = true {
        counter := [ef | efs := config_patches ; contains(efs,deprecated_envoyfilters[_].deprecated) ]
        count(counter) > 0

Solution

  • First of, did you really mean these parameters?

      parameters:
        envoyfilters:
        - http_squash:
          canonical: "envoy.filters.http.squash"
          deprecated: "envoy.squash"
        - listener_http_inspector:
          canonical: "envoy.filters.listener.http_inspector"
          deprecated: "envoy.listener.http_inspector"
    

    because they translate to, for example,

    {
      "http_squash": null,
      "canonical": "envoy.filters.http.squash",
      "deprecated": "envoy.squash"
    },
    

    So I think some key/val structure is nicer to work with here, let's go with:

      envoyfilters:
        http_squash:
          canonical: "envoy.filters.http.squash"
          deprecated: "envoy.squash"
        listener_http_inspector:
          canonical: "envoy.filters.listener.http_inspector"
          deprecated: "envoy.listener.http_inspector"
    

    Having converted your yaml to the GK input via kube-review, and adding the parameters to the input, I've put up this example on the playground: https://play.openpolicyagent.org/p/o8QrD3xj1y

    It boils down to this:

    package play
    
    violation[{"msg": msg}] {
        config_patch := input.review.object.spec.configPatches[match][listener][filterchain][filter][_].name
        some depr_name # name of deprecation
        input.parameters.envoyfilters[depr_name].deprecated == config_patch
        better := input.parameters.envoyfilters[depr_name].canonical
    
        msg := sprintf("REVIEW OBJECT: %s, violation %s, use %s instead", [config_patch, depr_name, better])
    }
    

    Looping over the different config patches happens implicitly by Rego trying to satisfy

        config_patch := input.review.object.spec.configPatches[match][listener][filterchain][filter][_].name
    

    and then we need to look up in our parameters if one of the to-be-applied patches is deprecated:

        some depr_name # name of deprecation
        input.parameters.envoyfilters[depr_name].deprecated == config_patch
        better := input.parameters.envoyfilters[depr_name].canonical