Rego

Policy based on Rego Rules

The approver-policy Rego plugin introduces 3 new flags:

--rego-replicate
--rego-replicate-cluster
--rego-policy-directory
Copy to clipboard

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'
Copy to clipboard

--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"
Copy to clipboard

--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) == 0
message := sprintf("no ingress was found with a tls hosts entry with hostnames: %v", [concat(",", csr_hosts)])
}
Copy to clipboard

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 returned
deny_all_csr[message] {
1 == 1
message := "this rule denies all certificate requests"
}
Copy to clipboard
# 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
}
Copy to clipboard

Invalid Examples:

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

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.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 -
Copy to clipboard

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/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"
Copy to clipboard

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 resource
data.resources.<resource name>.<namespace>.<name>.<Kubernetes object>
# Cluster Scoped resourece
data.resources.<resource name>.<name>.<Kubernetes object>
Copy to clipboard

It can also be visualized like so:

{
"resources": {
"services": {
"default": {
"example-service": {...},
"another-service": {...},
...
},
...
},
"namespaces": {
"default": {...},
"kube-system": {...},
...
},
...
}
}
Copy to clipboard

On this page