May 16, 2019

Protect critical Kubernetes namespaces with Open Policy Agent

Introduction

Update 2020-05-16: Gatekeeper superseeds OPA so there is a new post, that replaces this one

Update 2019-09-08: after finding a critical bug causing my cluster to hang and becoming unusable after a restart I did some investigation and testing and have updated the project on Github.

Open Policy Agent is an open-source, general-purpose policy engine that enables unified, context-aware policy enforcement across the entire stack. OPA provides greater flexibility and expressiveness than hard-coded service logic or ad-hoc domain-specific languages and comes with powerful tooling to help anyone get started.

In this post we will explore OPA with the purpose of implementing a policy that prevents from inadvertently deleting Kubernetes namespaces annotated with protected: "yes". This policy is intended to avoid that system and application namespaces get deleted by mistake.

All examples as well as the installation of OPA are on github.

Install OPA

The installation of OPA is covered in this readme.

Create the policy

The policy we want to create will run as an admission controller when deleting a namespace, and check whether the namespaces has an annotation protected: "yes". If that’s the case the deletion will be rejected.

This policy can be found here.

package kubernetes.admission

import data.kubernetes.namespaces

import input.request.object.metadata.annotations as annotations

deny[msg] {
    input.request.kind.kind = "Namespace"
    input.request.operation = "DELETE"
    missing_required_annotations[msg]
}

# Require no "protected" annotation or protected="no" to allow namespace deletion 
missing_required_annotations[msg] {
    annotation = namespaces[input.request.namespace].metadata.annotations["protected"]
    not annotation = "no"
    msg = "Namespaces annotated with protected=yes can not be deleted"

The policy will run on any DELETE operation for objects of the kind Namespace, and will check if there an annotation protected:

  • if there is no annotation or the annotation protected: "no", the policy will accept the deletion
  • is there is an annotation different from “no”, e.g. protected: "yes" the policy will deny the operation

Test the policy

Deploy the policy into the opa namespace as a configmap:

kubectl -n opa create configmap protected-namespaces --from-file=protected-namespaces.rego

Create two random namespace:

kubectl create ns test1
kubectl create ns test2

Annotate the second namespace:

kubectl annotate ns test1 protected=yes

Delete the first namespace:

kubectl delete ns test1

This should have worked:

namespace "test1" deleted

Now try to delete the second namespace:

kubectl delete ns test2

This should have failed:

Error from server (Namespaces annotated with protected=yes can not be deleted): admission webhook "validating-webhook.openpolicyagent.org" denied the request: Namespaces annotated with protected=yes can not be deleted

If now we overwrite the annotation and retry the deletion will be performed:

kubectl annotate ns test2 protected=no --overwrite
namespace "test2" annotated

kubectl delete ns test2
namespace "test2" deleted

Conclusion

OPA offers a flexible way to formalize rules in a declarative way using the rego language. The language certainly comes with a steep learning curve but the model for declaring and deploying policies is very flexible.

Further readings

Content licensed under CC BY 4.0