Integrating Open Policy Agent (OPA) With Kubernetes

Integrating Open Policy Agent (OPA) With Kubernetes
Integrating Open Policy Agent (OPA) With Kubernetes

Open policy agent (OPA, pronounced “oh-pa”) is a tool that provides a unified framework and language for declaring, implementing, and controlling the policies of each component in the cloud-native solution. It also supports policy as code of various platforms including Kubernetes. OPA provides declarative Rego language to describe policies, and offloads policy decisions to OPA, thereby reducing the decision-making process, which is decoupled from other responsibilities of an application. In this article, I have listed steps on Integrating Open Policy Agent (OPA) With Kubernetes.

Before getting into the integration part, we will go through some of the key benefits of using OPA.

What is Policy?

A policy is a series of rules governing the behavior of software applications and services. Example: Whether it is application layer or Kubernetes or host level infrastructure or cloud infrastructure or end-user data handled or where workloads are deployed you have to deal with policy all the time in day-to-day work. 

Usually, organizations have different strategies in forming policies that are based on legal requirements, authorization rules, technical constraints, architecture characteristics, Network policies, and repeated mistakes. Most of all policies will be contributed by different stakeholders like Product Owners, Architects, Developers, Security engineers, etc in an organization. A typical policy might be looking something like below

  1. Rules that define which users (identity) can perform which type of operations on which resource
  2. Rules that define only specific subnets are allowed for external traffic 
  3. Which clusters are allowed to deploy workloads?
  4. What OS functions the container can perform.
  5. When will the system be accessed at what time of day?
  6. Enforcing authorization in a microservice API

Difference between configuration vs policy

For beginners, there will be a slight confusion between configuration and policy. It is as simple as that, Configuration is a set of settings to parameterize the software, whereas policy is a set of rules and regulations that are enforced when we deploy, release or use software, etc. In short, a configuration is the set of settings, and a policy is a thing that enforces those settings?

Note XACML (OASIS standards) / ALFA: Extensible Access Control Markup Language is an open standard XML-based protocol designed to express control policies for enterprise security applications widely adopted since 2001. In the case of OPA, it has Redefined the Rule Engine.

3 Reasons Why do you need an Open Policy Agent (OPA)?

Policy enforcement is the fundamental problem of an organization. If we don’t have a  good solution for policy enforcement in the organization, then we’re gonna risk massive fines in terms of security breaches, outages, downtime, and a lot of chaos. Let see some scenarios on why to use an open policy agent.

No Policy 

Years back, a newbie did a small POC in the AWS cloud in XYZ company. He got access to a corporate AWS account and created an EC2 instance with Public IP and deployed ECS from there for his work. Since there is no policy available to restrict the instance creation with public IP, it invited hackers to access the VM, and eventually they started mining cryptocurrencies.

To conclude, Public IP should not be allowed for any instance in the corporate network. This would be a good example of why we need the policy to be considered for security breaches.

Setting up Custom Policy engine from Ground Zero

Say you need to implement the same strategy from ground zero. The required components, such as strategy language (Syntax and Semantics) and evaluation engine, need to be brainstormed, designed, implemented, tested, documented, and then maintained to ensure a positive user experience and expected behavior from the new strategy. This is a lot of work !!!

Policy hardcoded in application services

Say, you implement the policy which is tightly coupled with your microservices, it makes it very difficult for the policy to load new rules dynamically and flexibly change based on business requirements. Moreover, different services would have different descriptions of the policy, such as JSON, YAML, etc, so it will be very expensive and painful to manage the policy in the form of code (Policy as Code). 

BottomLine: Above all the scenarios will have a lot of challenges. In order to have a more flexible and consistent architecture, we must decouple the policy decision-making process from the complex business logic. Open policy agent does a very good job in terms of decoupling the decision making to a separate process. This is the core concept of the OPA architecture explained below.

What is Open Policy Agent (OPA)?

OPA is a Lightweight general-purpose policy engine that abstracts strategy decisions from your application into a common model and decouples the decision-making step from the complex business logic.

In layman terms, You can implement OPA  in any layer of the stack in the system and it will help you to offload the policy decision from your service. So you are decoupling the policy decision from the enforcement piece. It further allows declaratively specified policies to change based on business needs, update without recompilation or redeployment, and more importantly reduce the risk of human error.

As shown in the below diagram, OPA can be abstracted into the following 4 steps.

  1. Use Rego language to define the Policy and purpose.
  2. When your service needs a decision (should this request be “allowed” or “denied”) from OPA, this action (Query with the request method, the path, the user) is handed over to OPA to process to execute the Policy.
  3. OPA validates the attributes against the data (already provided).
  4. OPA executes the Rego code (in step 1) under specific input data (Data) and returns the result of Policy execution to the original service. The answer would be either “Allow” or “Deny”
OPA Policy Decoupling
OPA Policy Decoupling

OPA aims to help architects, developers, security practitioners to express policy as code and it can coexist with services. Opa can be integrated into third-party technologies such as Kubernetes, Terraform, Envoy, Docker, etc. The integration method can be sidecar, Standalone service, or library introduction. If you want to integrate OPA, you have to write the policies through a high-level declarative language (Rego) provided by OPA. Refer to the official document for OPA philosophy docs for details

What are the benefits of Policy As Code?

Managing policy as code has a lot of benefits, you can put them in source control and keep track of all changes happening to any given policy, like rollback, track changes in your policy code across versions, etc, can be managed very well. Some of the other benefits include

  1. Global unified policy description: The description of the policy is expressed in a unified language instead of embedding in the business logic code, nor described in a variety of different forms. 
  2. Improve operation and maintenance efficiency: Different policy codes can be easily reviewed and integrated according to the requirements. A typical example would be, we can enforce company-level policy code before developing its own business-related policies.  
  3. Improve the flexibility of business architecture: Multiple components can share the same policy to execute related logic and also we can dynamically update and load new policies without a change in the business logic code.

Integrating Open Policy Agent (OPA)

Hope you got an idea of what an open policy agent is. Let us take a look at the application scenarios of OPA. So In this article, I have focused on a small use case on how to integrate OPA Gatekeeper (hereafter Gatekeeper) as an admission controller with Kubernetes. Please note OPA is not tied to Kubernetes alone, neither is Kubernetes mandatory for using OPA. There are a lot of use cases available around to get OPA Integrated into anywhere in the stack to solve a variety of different policy-related issues.

Below are the two packages which enable Kubernetes to support OPA control.

  1. kube-mgmt
  2. Gatekeeper

Before jumping into Gatekeeper,  we have to get an idea of what is an admission controller and the benefits of deploying OPA as an admission controller.

What is An Admission Controller?

Admission Controller Phase
Admission Controller Phase

An admission controller is a piece of code used to intercept the request in the Kube-API server for authenticating and authorizing the request before the object is persisted and when the resources are created, updated, and deleted, all decisions are made through admission controllers. Among the series of admission controllers, there are two special ones, which are the focus of our attention today. More information can be found in the official documentation.

  1. MutatingAdmissionWebhook: Mainly used to parse and modify the request object before the request before forwarding it down the chain. Example: AlwaysPullImages admission controller
  2. ValidatingAdmissionWebhook: Mainly used for data format verification against specific data. Example: Namespace Admission controller

The benefits of deploying OPA as an admission controller in Kubernetes can meet the following requirements.

Security perspective

  1. We can prohibit the container from running as root or ensure that the container’s root file system is always mounted as read-only.
  2. Only pull images from a given private Image Registry.
  3. Reject deployments that do not meet security standards. For example, a container with a privileged flag can avoid many security checks. Can be verified through the OPA strategy.

Resource control Perspective

  1. OPA can define policies to allow cluster users to enforce certain conventions, such as having meaningful labels, comments, resource limits, or other settings. 
  2. All Pods are required to add resource limits and minimum replicas when spinned up.
  3. Prevent the creation of conflicting Ingress objects.

Access control Perspective

  1. Allow only specific user A to access all resources under namespace1 and namespace2.

What is Open Policy Agent (OPA) Gatekeeper

The Gatekeeper is a relatively new project which helps to integrate between OPA and Kubernetes, Gatekeeper can be started as a Pod in Kubernetes and will be registered as Dynamic Admission Controller with API Server after startup. Essentially, Gatekeeper is used as a Webhook Server. When a user uses kubectl or other methods to send a CURD request for a resource to the API Server, the request will be sent to the admission controller after Authentication and Authorization, and finally, AdmissionReview sent to the Gatekeeper in the form of a request. Gatekeeper makes a decision on this request according to the policy of the corresponding service ( configured in the form of CRD ), and  AdmissionReview returns the response to the API Server.

Its main architecture is shown in the following figure:

Admission Control Flow
Admission Control Flow

In the current design, Gatekeeper has the following CRD’s named ConstraintTemplate and Constraint which (is our primary focus today) can be considered similar to the relationship between classes and instances.

  1. ConstraintTemplate: The rego field of ConstraintTemplate specifically describes the Policy in Rego language, but does not specify the specific parameters in the Policy.
  2. Constraint: Constraint can be considered as an instantiation of a ConstraintTemplate, in which the unspecified parameters of the ConstraintTemplate are specifically configured. Therefore, multiple Constraints can be generated for the same ConstraintTemplate with different parameter configurations.

Gatekeeper vs OPA? Compared to OPA,  Gatekeeper offers more features like
 
1. Scalable and parameterized strategy definition method.
2. Through CRD defined constraints (constraints), you can easily create a common strategy.
3. A constraint template is defined through CRD, which adds some flexibility.
4. Provides an audit function. 

Prerequisites ( Prerequisites )

  1. Kubernetes version 1.14 or later
  2. The Kubernetes cluster should be up and running.
You Might Like: Kubernetes Learning Path For Beginners

Integrating Open Policy Agent (OPA) with Kubernetes  

Installation Steps

Step 1: Install OPA Gatekeeper CRD.

Step 2: Create a ConstraintTemplate manifest.

Step 3: Create a constraint manifest.

Step 4: Excercise the policy.

Step 5: Check the violation with the describe command.

Install OPA Gatekeeper CRD

kubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper/master/deploy/gatekeeper.yaml

namespace/gatekeeper-system created
resourcequota/gatekeeper-critical-pods created
customresourcedefinition.apiextensions.k8s.io/configs.config.gatekeeper.sh created
customresourcedefinition.apiextensions.k8s.io/constraintpodstatuses.status.gatekeeper.sh created
customresourcedefinition.apiextensions.k8s.io/constrainttemplatepodstatuses.status.gatekeeper.sh created
customresourcedefinition.apiextensions.k8s.io/constrainttemplates.templates.gatekeeper.sh created
serviceaccount/gatekeeper-admin created
podsecuritypolicy.policy/gatekeeper-admin created
role.rbac.authorization.k8s.io/gatekeeper-manager-role created
clusterrole.rbac.authorization.k8s.io/gatekeeper-manager-role created
rolebinding.rbac.authorization.k8s.io/gatekeeper-manager-rolebinding created
clusterrolebinding.rbac.authorization.k8s.io/gatekeeper-manager-rolebinding created
secret/gatekeeper-webhook-server-cert created
service/gatekeeper-webhook-service created
deployment.apps/gatekeeper-audit created
deployment.apps/gatekeeper-controller-manager created
poddisruptionbudget.policy/gatekeeper-controller-manager created
validatingwebhookconfiguration.admissionregistration.k8s.io/gatekeeper-validating-webhook-configuration created

Make sure Gatekeeper CRD is installed.

kubectl get crd
NAME                                                 CREATED AT
configs.config.gatekeeper.sh                         2021-06-28T09:01:12Z
constraintpodstatuses.status.gatekeeper.sh           2021-06-28T09:01:12Z
constrainttemplatepodstatuses.status.gatekeeper.sh   2021-06-28T09:01:12Z
constrainttemplates.templates.gatekeeper.sh          2021-06-28T09:01:12Z
k8salwaysdeny.constraints.gatekeeper.sh              2021-06-28T09:18:16Z
k8sminreplicacount.constraints.gatekeeper.sh         2021-06-28T11:39:54Z
k8srequiredlabels.constraints.gatekeeper.sh          2021-06-28T11:09:45Z
kubectl get pod,svc -n gatekeeper-system
NAME                                              READY   STATUS    RESTARTS   AGE
pod/gatekeeper-audit-76b86bcdb7-h4tmw              1/1    Running      0      2m46s
pod/gatekeeper-controller-manager-c7644c98-cjf9l   1/1    Running      0      2m46s
pod/gatekeeper-controller-manager-c7644c98-nfcq6   1/1    Running      0      2m46s
pod/gatekeeper-controller-manager-c7644c98-vcxbj   1/1    Running      0      2m46s
 
NAME                                 TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE
service/gatekeeper-webhook-service   ClusterIP   10.102.228.51   <none>        443/TCP   2m46s

Example of Deny all pod creation

This example enforces policy for all pods being rejected from being created.

Gatekeeper consists of ConstraintTemplate and Constraint CRD. Before defining a Constraint you have to define a Constraint Template. A ConstraintTemplate defines the policy that is used to enforce the constraint and also, the general schema of the constraint. So in Gatekeeper ConstraintTemplate, we defined the schema for parameters. And defined the actual parameter value in Constraint. This allows one to use the same template and define multiple constraints with different parameter values. 

The syntax expressed using Rego, I know it is still a bit annoying, but fortunately, the official source code provides some sample rules and basic code libraries for reference. In addition, you can also use Rego Playground for online debugging to write a slightly more complicated strategy.

Step 1: Create ConstraintTemplate manifest

vim template.yaml

apiVersion: templates.gatekeeper.sh/v1beta1
kind: ConstraintTemplate
metadata:
  name: k8salwaysdeny
spec:
  crd:
    spec:
      names:
        kind: K8sAlwaysDeny
      validation:
        # Schema for the 'parameters' field
        openAPIV3Schema:
          properties:
            message:
              type: string
  targets:
    - target: admission.k8s.gatekeeper.sh
      rego: |
        package k8salwaysdeny
        violation[{"msg": msg}] {
          1 > 0
          msg := input.parameters.message
        }

Let review the constraint template 

  1. violation[{“msg”: msg}] { }  – It defines a message and other details that would be returned to the user if the policy is violated. The rule is covered with the curly braces {}. 
  2. count(missing) > 0 or 1 > 0  – if the total count of items are more than Zero, then it means it is a violation of the policy 
  3. msg := input.parameters.message  – If it is a violation, the custom message variable will be returned to the client with a respective message.

Step 2: Create a template

kubectl create -f template.yaml 
constrainttemplate.templates.gatekeeper.sh/k8salwaysdeny created

Note: spec.crd.spec.names.kind – Defines the CRD of the constraint created in If there are multiple conditions in rego’s violation, it will be a violation if all the conditions are true.

Step 3: Check the template

kubectl get constrainttemplates
NAME            AGE
k8salwaysdeny   54s
kubectl api-resources | grep constraint

k8salwaysdeny                                  constraints.gatekeeper.sh/v1beta1      false        K8sAlwaysDeny
k8salwaysdeny                                  constraints.gatekeeper.sh/v1beta1      false        K8sAlwaysDeny
k8sminreplicacount                             constraints.gatekeeper.sh/v1beta1      false        K8sMinReplicaCount
k8srequiredlabels                              constraints.gatekeeper.sh/v1beta1      false        K8sRequiredLabels
constraintpodstatuses                          status.gatekeeper.sh/v1beta1           true         ConstraintPodStatus
constrainttemplatepodstatuses                  status.gatekeeper.sh/v1beta1           true         ConstraintTemplatePodStatus
constrainttemplates                            templates.gatekeeper.sh/v1beta1        false        ConstraintTemplate

Step 4: Create a constraint manifest

Now, using this ConstraintTemplate, create a Constraint as follows

vim constraints.yaml 
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sAlwaysDeny
metadata:
  name: pod-always-deny
spec:
  match:
    kinds:
      - apiGroups: [""]
        kinds: ["Pod"]
  parameters:
    message: "You are restricted to Create Pods"

We can define one or more constraints for the constraint template. In the above configuration, the “Kind” must match the value defined in the constraint template. Also in the “Kinds” part, we should define which k8 resource this policy will apply to. In our case, we are targeting that any apiGroup that has the Pod kind is a valid match. 

Step 5: Create a constraint

kubectl create -f constraints.yaml 
k8salwaysdeny.constraints.gatekeeper.sh/pod-always-deny created

Step 6: Check the constraint

kubectl get k8sAlwaysDeny
NAME              AGE
pod-always-deny   4m30s

Step 7: Exercise the policy

If you try to create a pod, you will get an error similar to below.

kubectl run pod --image=nginx
Error from server ([pod-always-deny] You are restricted to Create Pods): admission webhook "validation.gatekeeper.sh" denied the request: [pod-always-deny] You are restricted to Create Pods

Step 6: Check total Violations count and messages

Check the Status part of the describe command, you can check the current violation status.

kubectl describe k8sAlwaysDeny pod-always-deny
Name:         pod-always-deny
Namespace:    
Labels:       <none>
Annotations:  <none>
API Version:  constraints.gatekeeper.sh/v1beta1
Kind:         K8sAlwaysDeny
Metadata:
  Creation Timestamp:  2021-06-28T09:24:47Z
  Generation:          1
  Managed Fields:
    API Version:  constraints.gatekeeper.sh/v1beta1
    Fields Type:  FieldsV1
    fieldsV1:
      f:status:
    Manager:      gatekeeper
    Operation:    Update
    Time:         2021-06-28T09:24:47Z
    API Version:  constraints.gatekeeper.sh/v1beta1
    Fields Type:  FieldsV1
    fieldsV1:
      f:spec:
        .:
        f:match:
          .:
          f:kinds:
        f:parameters:
          .:
          f:message:
    Manager:         kubectl-create
    Operation:       Update
    Time:            2021-06-28T09:24:47Z
  Resource Version:  10486
  UID:               5df1c89e-0666-4248-9c92-cfdda1a661d3
Spec:
  Match:
    Kinds:
      API Groups:
        
      Kinds:
        Pod
  Parameters:
    Message:  You are restricted to Create Pods
Status:
  Audit Timestamp:  2021-06-28T09:29:38Z
  By Pod:
    Constraint UID:       5df1c89e-0666-4248-9c92-cfdda1a661d3
    Enforced:             true
    Id:                   gatekeeper-audit-76b86bcdb7-h4tmw
    Observed Generation:  1
    Operations:
      audit
      status
    Constraint UID:       5df1c89e-0666-4248-9c92-cfdda1a661d3
    Enforced:             true
    Id:                   gatekeeper-controller-manager-c7644c98-cjf9l
    Observed Generation:  1
    Operations:
      webhook
    Constraint UID:       5df1c89e-0666-4248-9c92-cfdda1a661d3
    Enforced:             true
    Id:                   gatekeeper-controller-manager-c7644c98-nfcq6
    Observed Generation:  1
    Operations:
      webhook
    Constraint UID:       5df1c89e-0666-4248-9c92-cfdda1a661d3
    Enforced:             true
    Id:                   gatekeeper-controller-manager-c7644c98-vcxbj
    Observed Generation:  1
    Operations:
      webhook
  Total Violations:  12
  Violations:
    Enforcement Action:  deny
    Kind:                Pod
    Message:             You are restricted to Create Pods
    Name:                hello-minikube
    Namespace:           default
    Enforcement Action:  deny
    Kind:                Pod
    Message:             You are restricted to Create Pods
    Name:                gatekeeper-audit-76b86bcdb7-h4tmw
    Namespace:           gatekeeper-system
    Enforcement Action:  deny
    Kind:                Pod
    Message:             You are restricted to Create Pods
    Name:                gatekeeper-controller-manager-c7644c98-cjf9l
    Namespace:           gatekeeper-system
    Enforcement Action:  deny
    Kind:                Pod
    Message:             You are restricted to Create Pods
    Name:                gatekeeper-controller-manager-c7644c98-nfcq6
    Namespace:           gatekeeper-system
    Enforcement Action:  deny
    Kind:                Pod
    Message:             You are restricted to Create Pods
    Name:                gatekeeper-controller-manager-c7644c98-vcxbj
    Namespace:           gatekeeper-system
    Enforcement Action:  deny
    Kind:                Pod
    Message:             You are restricted to Create Pods
    Name:                coredns-74ff55c5b-nbb4s
    Namespace:           kube-system
    Enforcement Action:  deny
    Kind:                Pod
    Message:             You are restricted to Create Pods
    Name:                etcd-minikube
    Namespace:           kube-system
    Enforcement Action:  deny
    Kind:                Pod
    Message:             You are restricted to Create Pods
    Name:                kube-apiserver-minikube
    Namespace:           kube-system
    Enforcement Action:  deny
    Kind:                Pod
    Message:             You are restricted to Create Pods
    Name:                kube-controller-manager-minikube
    Namespace:           kube-system
    Enforcement Action:  deny
    Kind:                Pod
    Message:             You are restricted to Create Pods
    Name:                kube-proxy-gzrx9
    Namespace:           kube-system
    Enforcement Action:  deny
    Kind:                Pod
    Message:             You are restricted to Create Pods
    Name:                kube-scheduler-minikube
    Namespace:           kube-system
    Enforcement Action:  deny
    Kind:                Pod
    Message:             You are restricted to Create Pods
    Name:                storage-provisioner
    Namespace:           kube-system
Events:                  <none>

Step 7: Modify the policy and exercise the changes.

Change the contrainttemplate by adding below false conditions. Repeat the steps from 1 to 6 and you should get Total Violations as 0.

violation[{"msg": msg}] {
        
          1 > 2 # false
          msg := input.parameters.message
        }

Step 8: Delete the resource.

kubectl delete -f template.yaml
kubectl delete -f constraint.yaml

Example of enforcing a label in Namespace

This example enforces a label in Namespaces.

Step 1: Create ConstraintTemplate manifest.

vim template_label.yaml 
 
apiVersion: templates.gatekeeper.sh/v1beta1
kind: ConstraintTemplate
metadata:
  name: k8srequiredlabels
spec:
  crd:
    spec:
      names:
        kind: K8sRequiredLabels
      validation:
        # Schema for the "parameters" field
        openAPIV3Schema:
          properties:
            labels:
              type: array
              items: string
  targets:
    - target: admission.k8s.gatekeeper.sh
      rego: |
        package k8srequiredlabels
 
        violation[{"msg": msg, "details": {"missing_labels": missing}}] {
          provided := {label | input.review.object.metadata.labels[label]}
          required := {label | label := input.parameters.labels[_]}
          missing := required - provided
          count(missing) > 0
          msg := sprintf("you must provide labels: %v", [missing])
        }

Step 2: Create a template.

kubectl create -f template_label.yaml 
constrainttemplate.templates.gatekeeper.sh/k8srequiredlabels created

Step 3: Create a constraint manifest

vim all_namespace_must_have_ns_constraints.yaml 

apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sRequiredLabels
metadata:
  name: ns-must-have-ns
spec:
  match:
    kinds:
      - apiGroups: [""]
        kinds: ["Namespace"]
  parameters:
    labels: ["ns"]

Step 4: Create a constraint

kubectl create -f all_namespace_must_have_ns_constraints.yaml 
k8srequiredlabels.constraints.gatekeeper.sh/ns-must-have-ns created
kubectl get K8sRequiredLabels
NAME               AGE
Ns-must-have-ns   60s

Step 5: Exercise the policy

If you try to create a Namespace without the label “ns”, you will get an error similar to below.

kubectl create namespace test
Error from server ([ns-must-have-ns] you must provide labels: {"ns"}): admission webhook "validation.gatekeeper.sh" denied the request: [ns-must-have-ns] you must provide labels: {"ns"}

Step 6: Check the violation with the describe command.

kubectl describe K8sRequiredLabels ns-must-have-ns                     
Name:         ns-must-have-ns
Namespace:    
Labels:       <none>
Annotations:  <none>
API Version:  constraints.gatekeeper.sh/v1beta1
Kind:         K8sRequiredLabels
Metadata:
  Creation Timestamp:  2021-06-28T11:12:16Z
  Generation:          1
  Managed Fields:
    API Version:  constraints.gatekeeper.sh/v1beta1
    Fields Type:  FieldsV1
    fieldsV1:
      f:status:
    Manager:      gatekeeper
    Operation:    Update
    Time:         2021-06-28T11:12:16Z
    API Version:  constraints.gatekeeper.sh/v1beta1
    Fields Type:  FieldsV1
    fieldsV1:
      f:spec:
        .:
        f:match:
          .:
          f:kinds:
        f:parameters:
          .:
          f:labels:
    Manager:         kubectl-create
    Operation:       Update
    Time:            2021-06-28T11:12:16Z
  Resource Version:  14794
  UID:               f3e62ad4-c999-4794-9d5b-20a816218bab
Spec:
  Match:
    Kinds:
      API Groups:
        
      Kinds:
        Namespace
  Parameters:
    Labels:
      ns
Status:
  Audit Timestamp:  2021-06-28T11:19:19Z
  By Pod:
    Constraint UID:       f3e62ad4-c999-4794-9d5b-20a816218bab
    Enforced:             true
    Id:                   gatekeeper-audit-76b86bcdb7-h4tmw
    Observed Generation:  1
    Operations:
      audit
      status
    Constraint UID:       f3e62ad4-c999-4794-9d5b-20a816218bab
    Enforced:             true
    Id:                   gatekeeper-controller-manager-c7644c98-cjf9l
    Observed Generation:  1
    Operations:
      webhook
    Constraint UID:       f3e62ad4-c999-4794-9d5b-20a816218bab
    Enforced:             true
    Id:                   gatekeeper-controller-manager-c7644c98-nfcq6
    Observed Generation:  1
    Operations:
      webhook
    Constraint UID:       f3e62ad4-c999-4794-9d5b-20a816218bab
    Enforced:             true
    Id:                   gatekeeper-controller-manager-c7644c98-vcxbj
    Observed Generation:  1
    Operations:
      webhook
  Total Violations:  5
  Violations:
    Enforcement Action:  deny
    Kind:                Namespace
    Message:             you must provide labels: {"ns"}
    Name:                default
    Enforcement Action:  deny
    Kind:                Namespace
    Message:             you must provide labels: {"ns"}
    Name:                gatekeeper-system
    Enforcement Action:  deny
    Kind:                Namespace
    Message:             you must provide labels: {"ns"}
    Name:                kube-node-lease
    Enforcement Action:  deny
    Kind:                Namespace
    Message:             you must provide labels: {"ns"}
    Name:                kube-public
    Enforcement Action:  deny
    Kind:                Namespace
    Message:             you must provide labels: {"ns"}
    Name:                kube-system
Events:                  <none>
 
 
{
  "apiVersion": "v1",
  "kind": "Namespace",
  "metadata": {
    "name": "development",
    "labels": {
      “Env”: "development"
    }
  }
}

Step 7: Modify the policy and excercise the changes.

Create a new YAML file called with the required label value “ns” similar to below.

vim namespace-dev.yaml 

{
  "apiVersion": "v1",
  "kind": "Namespace",
  "metadata": {
    "name": "development",
    "labels": {
      "ns": "development"
    }
  }
}

Step 8: Namespace is created successfully (policy is honored).

kubectl create -f namespace-dev.yaml
namespace/development created

Example of enforcing a minimum number of replicas in Deployment

This example of enforcing a minimum number of replicas (2) in Deployment

Step 1: Create ConstraintTemplate manifest.

apiVersion: templates.gatekeeper.sh/v1beta1
kind: ConstraintTemplate
metadata:
  name: k8sminreplicacount
spec:
  crd:
    spec:
      names:
        kind: K8sMinReplicaCount
      validation:
        # Schema for the `parameters` field
        openAPIV3Schema:
          properties:
            min:
              type: integer
  targets:
    - target: admission.k8s.gatekeeper.sh
      rego: |
        package k8sminreplicacount
 
        violation[{"msg": msg, "details": {"missing_replicas": missing}}] {
          provided := input.review.object.spec.replicas
          required := input.parameters.min
          missing := required - provided
          missing > 0
          msg := sprintf("you must provide %v more replicas", [missing])
        }

Step 2: Create a template.

kubectl create -f template_replicas.yaml 
constrainttemplate.templates.gatekeeper.sh/k8sminreplicacount created

Step 3: Create a constraint manifest

vim all_deployment_must_have_min_replicacount.yaml 

apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sMinReplicaCount
metadata:
  name: deployment-must-have-min-replicas
spec:
  match:
    kinds:
      - apiGroups: ["apps"]
        kinds: ["Deployment"]
  parameters:
    min: 2

Step 4: Create a constraint

kubectl create -f all_deployment_must_have_min_replicacount.yaml 
k8sminreplicacount.constraints.gatekeeper.sh/deployment-must-have-min-replicas created 

Step 5: Exercise the policy 

If you try creating a deployment with replicas less than 2, you will get an error similar to below.

kubectl create deploy test --image=nginx
error: failed to create deployment: admission webhook "validation.gatekeeper.sh" denied the request: [deployment-must-have-min-replicas] you must provide 1 more replicas

Step 6: Modify the policy and excercise the changes.

Create a new deployment YAML file with replicas value as 2 similar to below.

vim deployment.yaml 

apiVersion: apps/v1
kind: Deployment
metadata:
  name: test-nginx-deployment
  labels:
    app: testnginx
spec:
  replicas: 2
  selector:
    matchLabels:
      app: testnginx
  template:
    metadata:
      labels:
        app: testnginx
    spec:
      containers:
      - name: testnginx
        image: nginx:latest
        ports:
        - containerPort: 80

Step 8: Deployment is created successfully (policy is honored).

kubectl create -f deployment.yaml
deployment.apps/test-nginx-deployment created

Open Policy Agent (OPA) Takeaways

Gatekeeper is more extensible than Open Policy Agent and can manage policies by template. Implementing OPA in your application stack can bring a certain learning cost in the early stage, but the value of the later period is to allow the Policy to have similar characteristics of code like Reusability, testable, versioning, abstraction, etc.  

  1. Decouple the policy decision-making process from the complex business logic
  2. Unify & centralize the policy definition and facilitate the enforcement of those policies the entire stack.
  3. Manage policy as code. 

References

  1. OPA Documentation 
  2. Gatekeeper Documentation 
  3. OPA Gatekeeper: Policy and Governance for Kubernetes
  4. OPA Gatekeeper Introduction
  5. A Guide to Kubernetes Admission Controllers for a quick primer on admission controllers.
  6. Dynamic Admission Control for details on configuring external admission controllers.
  7. Gatekeeper samples Library for Rules.
  8. OPA Samples library for Rules
  9. OPA Constraint Framework
  10. Grammer: Policy Language , Policy Reference
  11. Deploy OPA as an admission controller from scratch

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top