Rego
Policy based on Rego Rules
The approver-policy Rego plugin introduces 3 new flags:
--rego-replicate--rego-replicate-cluster--rego-policy-directory
Taking inspiration from the kube-mgmt project, both the optional --rego-replicate and --rego-replicate-cluster flags define the resource types that will be watched by the plugin. These will then be made available to the Rego rules during evaluation.
--rego-replicate
--rego-replicate=<group/version/resource[/namespace]>
Watches resources that are namespaced. Can be optionally scoped 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
--rego-replicate-cluster=<group/version/resource>
Watches resources that are cluster scoped. 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
--rego-policy-directory
Defines the directory that your Rego policies are defined and loaded from. This directory, and all child directories, will be loaded.
Rules are intended to be mounted into the approver Pod via a Kubernetes Volume, such as a ConfigMap or Secret.
approver-policy-rego will update the Rego modules it has loaded if and when they are modified in the mounted volume for directory.
Writing Rego rules
The approver-policy Rego plugin works by allowing CertificateRequestPolicy rules to be written in Rego. Here is an example rule which ensures a CertificateRequest 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) == 0message := sprintf("no ingress was found with a tls hosts entry with hostnames: %v", [concat(",", csr_hosts)])}
Rego has a bit of a learning curve so we suggest that you get familiar with the language before continuing. This OPA playground link has the above policy in it and can be used to inspect how it's working with an example CertificateRequest.
Rego Rule Format
Rego rules used for policy evaluation in approver-policy-rego must both:
- Be named deny_... (note, other supporting rules can be defined 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 returneddeny_all_csr[message] {1 == 1message := "this rule denies all certificate requests"}
# GOOD: partial rule format used, string message returned, supporting ruleuseddeny_all_csr[message] {supporting_rulemessage := "this rule denies all certificate requests"}# supporting_rule is only used in deny_all_csr, not the policysupporting_rule {1 == 1}
Invalid Examples:
# BAD: non string returneddeny_all_csr[message] {1 == 1message := 1}
# BAD: complete rule format used, no message returneddeny_all_csr {1 == 1}
Using the approver
The plugin will parse and load any Rego rules that are under the configured directory (--rego-policy-directory). This command will create a policy in a ConfigMap to be loaded into the approver:
$ cat << EOF | > ingress.regopackage ingressdeny_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) == 0message := 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 CertificateRequestPolicy 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/v1alpha1kind: CertificateRequestPolicymetadata:name: rego-examplespec:allowed:dnsNames:values: ["*.example.com"]required: trueselector:issuerRef:group: cert-manager.iokind: ClusterIssuername: letsencrypt-prodplugins:rego:values:rules: "data.ingress.deny_dns_names_not_found_in_ingress_tls"
Input & Data
The approver will make information about the context available for you to use in policy decisions.
The input document
The CertificateRequest that is to be evaluated 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 resourcedata.resources.<resource name>.<namespace>.<name>.<Kubernetes object># Cluster Scoped resourecedata.resources.<resource name>.<name>.<Kubernetes object>
It can also be visualized like so:
{"resources": {"services": {"default": {"example-service": {...},"another-service": {...},...},...},"namespaces": {"default": {...},"kube-system": {...},...},...}}