Skip to content

Approver Policy Enterprise Rego plugin

The Approver Policy Enterprise Rego plugin introduces 3 new flags:

  --rego-replicate
  --rego-replicate-cluster
  --rego-policy-directory

The optional --rego-replicate and --rego-replicate-cluster flags define the resource types watched by the plugin. These are then made available to the Rego rules during evaluation.

  • --rego-replicate

    Usage: --rego-replicate=<group/version/resource[/namespace]>

    Use this flag if you want to watch namespaced resources. Optionally you can add a scope to watch a single namespace. Note that resources belonging to the core Kubernetes group (i.e. services, deployments, ...) have an empty group string, and therefore are defined with a leading '/':

        --rego-replicate="networking.k8s.io/v1/ingresses"
        --rego-replicate="/v1/deployments"
        --rego-replicate="/v1/services/my-namespace'
    
  • --rego-replicate-cluster

    Usage: --rego-replicate-cluster=<group/version/resource>

    Use this flag if you want to watch cluster scoped resources. Note that resources belonging to the core Kubernetes group (i.e. namespaces, ...) have an empty group string, and therefore are defined with a leading '/':

        --rego-replicate-cluster="trust.cert-manager.io/v1alpha1/bundles"
        --rego-replicate-cluster="/v1/namespaces"
    
  • --rego-policy-directory

    Usage: --rego-policy-directory

    Use this flag to define the directory where your Rego policies are defined and loaded from. This directory, and all child directories, are loaded. Rules are mounted into the approver Pod via a Kubernetes Volume, such as a ConfigMap or Secret. The Approver Policy Enterprise Rego plugin updates the Rego modules it has loaded if and when they are modified in the mounted volume for the directory.

Writing Rego rules

The Approver Policy Enterprise Rego plugin let's you write certificate request policy rules in Rego. The following sample rule ensures a certificate request contains the hostnames of an Ingress resource in the same namespace:

deny_dns_names_not_found_in_ingress_tls[message] {
  csr := crypto.x509.parse_certificate_request(input.spec.request)
  csr_hosts := {x|x := csr.DNSNames[_] }
  matching_ingresses := {ingress|
    ingress := data.resources.ingresses[input.metadata.namespace][_]
    matching_tls := {tls|
      tls := ingress.spec.tls[_]
      hosts := {x| x := tls.hosts[_] }
      hosts == csr_hosts
    }
    count(matching_tls) > 0
  }
  count(matching_ingresses) == 0
  message := sprintf("no ingress was found with a tls hosts entry with hostnames: %v", [concat(",", csr_hosts)])
}

See this OPAplayground link for more information about the above policy. You can use it to inspect how it's working with an example certificate request.

Rego Rule Format

Rego rules used for policy evaluation in the Approver Policy Enterprise Rego plugin must:

  • Be named deny_... (you can define other supporting rules without this prefix. This is only required if the rules is referenced in policies).
  • Return a string message when a request is denied and nothing when the request is accepted.

Valid Examples:

# GOOD: partial rule format used, string message returned
deny_all_csr[message] {
  1 == 1
  message := "this rule denies all certificate requests"
}
# GOOD: partial rule format used, string message returned, supporting rule
used
deny_all_csr[message] {
  supporting_rule
  message := "this rule denies all certificate requests"
}
# supporting_rule is only used in deny_all_csr, not the policy
supporting_rule {
  1 == 1
}

Invalid Examples:

# BAD: non string returned
deny_all_csr[message] {
  1 == 1
  message := 1
}
# BAD: complete rule format used, no message returned
deny_all_csr {
  1 == 1
}

Using the approver

The plugin parses and loads any Rego rules that are under the configured directory (--rego-policy-directory). The following command creates a policy in a ConfigMap to load into the approver:

$ cat << EOF | > ingress.rego
package ingress
deny_dns_names_not_found_in_ingress_tls[message] {
  csr := crypto.x509.parse_certificate_request(input.spec.request)
  csr_hosts := {x|x := csr.DNSNames[_] }
  matching_ingresses := {ingress|
    ingress := data.resources.ingresses[input.metadata.namespace][_]
    matching_tls := {tls|
      tls := ingress.spec.tls[_]
      hosts := {x| x := tls.hosts[_] }
      hosts == csr_hosts
    }
    count(matching_tls) > 0
  }
  count(matching_ingresses) == 0
  message := sprintf("no ingress was found with a tls hosts entry with hostnames: %v", [concat(",", csr_hosts)])
}
EOF
$ kubectl create configmap approver-policy-rego -n cert-manager --from-file=ingress.rego --dry-run=true -o yaml | kubectl apply -f -

Loaded rules are then available under the path data.<package name>.<rule name> for reference in certificate request policy resources under the path: spec.plugins.rego.values.rules.

Our example rule above in the ingress package will be available at:data.ingress.deny_dns_names_not_found_in_ingress_tls

The following CertificateRequestPolicy uses the rule as defined above:

apiVersion: policy.cert-manager.io/v1alpha1
kind: CertificateRequestPolicy
metadata:
  name: rego-example
spec:
  allowed:
    dnsNames:
      values: ["*.example.com"]
      required: true
  selector:
    issuerRef:
      group: cert-manager.io
      kind: ClusterIssuer
      name: letsencrypt-prod
  plugins:
    rego:
      values:
        rules: "data.ingress.deny_dns_names_not_found_in_ingress_tls"

Input & Data

The approver makes information about the context available for you to use in policy decisions.

The input document

The certificate request you want to evaluate is available at the input path within Rego rules.

Rego data & data.resources

The watched Kubernetes resources are made available to the Rego rules during evaluation. These can be accessed from resources under data, and have the following structure:

# Namespaced resource
data.resources.<resource name>.<namespace>.<name>.<Kubernetes object>
# Cluster Scoped resourece
data.resources.<resource name>.<name>.<Kubernetes object>

Alternatively, in JSON format:

{
  "resources": {
    "services": {
      "default": {
        "example-service": {...},
        "another-service": {...},
         ...
      },
      ...
      },
      "namespaces": {
         "default": {...},
         "kube-system": {...},
         ...
      },
      ...
   }
}
  • To learn more about the Rego policy language, see Learning Rego.