Automating k8s cluster security with kyverno polices for governance and compliance

In a microservices architecture, teams work within dedicated namespaces to ensure service isolation. The DevOps engineer is responsible for creating CI/CD pipelines to deploy these services into the Kubernetes cluster. While the deployment and development of microservices are managed by the teams, the DevOps engineer also oversees cluster management and administrative tasks, including scaling, governance, compliance, high availability, security, cost optimization, day-to-day operations, and observability.

In k8s, we have an admission controller which implement governance and security.

Problem: writing admission controllers need expertise in GO and also if organization compliance rules keeps growing or modifying with time, its hard for managing and creation of admission controllers and Day to Day operations.

The Solution: Kyverno - A Dynamic Admission Controller

Kyverno provides a solution as a dynamic admission controller that simplifies the enforcement of governance and security policies without the need for custom Go code. It allows organizations to define policies in YAML files and automatically apply them within the Kubernetes cluster.

How Kyverno Works

Once deployed in the Kubernetes cluster, Kyverno functions as a dynamic admission controller. It reads the policy rules defined in the YAML files and validates whether the incoming resource creation or modification complies with these rules. The policies are flexible, making it easy to define and enforce governance and compliance requirements across the cluster.

Examples:

  • Pod Resource Requests and Limits: Every pod created must have resource requests and limits set.

  • Namespace Resource Quotas and Limit Ranges: Each namespace must have Resource Quotas and Limit Ranges defined.

  • Deny All Traffic by Default: When a new namespace is created, it will automatically have all traffic denied by default.

  • Eviction Policy for Host Path and EmptyDir Volumes: Ensure that no pod uses HostPath or EmptyDir volumes unless specified.

  • Mandatory Pod Labels: Every pod must include specific labels for tracking and organization.

  • Health Probes: Ensure that every pod includes Liveness (LP), Readiness (RP), and Startup Probes (SP) to manage lifecycle and availability.

Steps for Implementing Kyverno

  1. Ensure the cluster is set up (Kind for local development). Then, deploy Kyverno to the cluster using a manifest or Helm chart

     kubectl create -f https://github.com/kyverno/kyverno/releases/download/v1.8.5/install.yaml
    

    All resources have been deployed in the kyverno Namespace.

  2. Create a policy YAML to enforce resource requests and limits (RR/RL) on pods

     apiVersion: kyverno.io/v1
     kind: ClusterPolicy
     metadata:
       name: require-requests-limits
       annotations:
         policies.kyverno.io/title: Require Limits and Requests
         policies.kyverno.io/category: Best Practices, EKS Best Practices
         policies.kyverno.io/severity: medium
         policies.kyverno.io/subject: Pod
         policies.kyverno.io/minversion: 1.6.0
         policies.kyverno.io/description: >-
           As application workloads share cluster resources, it is important to limit resources
           requested and consumed by each Pod. It is recommended to require resource requests and
           limits per Pod, especially for memory and CPU. If a Namespace level request or limit is specified,
           defaults will automatically be applied to each Pod based on the LimitRange configuration.
           This policy validates that all containers have something specified for memory and CPU
           requests and memory limits.
     spec:
       validationFailureAction: audit
       background: true
       rules:
       - name: validate-resources
         match:
           any:
           - resources:
               kinds:
               - Pod
         validate:
           message: "CPU and memory resource requests and memory limits are required for containers."
           pattern:
             spec:
               containers:
               - resources:
                   requests:
                     memory: "?*"
                     cpu: "?*"
                   limits:
                     memory: "?*"
               =(initContainers):
               - resources:
                   requests:
                     memory: "?*"
                     cpu: "?*"
                   limits:
                     memory: "?*"
               =(ephemeralContainers):
               - resources:
                   requests:
                     memory: "?*"
                     cpu: "?*"
                   limits:
                     memory: "?*"
    
  3. Apply the clusterpolicy and verify the status

  4. Verify the logs of the kyverno pods to identify that kyverno pod recognizes the cluster policy deployed

  5. Now, Try creating a Nginx pod and identify the events

Although we created a ClusterPolicy, If the policy is set to Audit, non-compliant pods will still be created, but violations will be logged:

  • Kubernetes Events (resource-level logs)

  • Policy Reports (compliance overview)

  1. If the policy is set to Enforce, and try creating a new pod and observe the request will be blocked, and a message will indicate why the pod creation was denied.

  2. Now, try deleting the policy and deploy redis again, now it will be deployed as no policy to enforce

  3. Use the generate action to implement namespace creation with predefined Resource Quotas and Limit Ranges

     apiVersion: kyverno.io/v1
     kind: ClusterPolicy
     metadata:
       name: add-ns-quota
       annotations:
         policies.kyverno.io/title: Add Quota
         policies.kyverno.io/category: Multi-Tenancy, EKS Best Practices
         policies.kyverno.io/subject: ResourceQuota, LimitRange
         policies.kyverno.io/minversion: 1.6.0
         policies.kyverno.io/description: >-
           To better control the number of resources that can be created in a given
           Namespace and provide default resource consumption limits for Pods,
           ResourceQuota and LimitRange resources are recommended.
           This policy will generate ResourceQuota and LimitRange resources when
           a new Namespace is created.
     spec:
       rules:
       - name: generate-resourcequota
         match:
           any:
           - resources:
               kinds:
               - Namespace
         generate:
           apiVersion: v1
           kind: ResourceQuota
           name: default-resourcequota
           synchronize: true
           namespace: "{{request.object.metadata.name}}"
           data:
             spec:
               hard:
                 requests.cpu: '4'
                 requests.memory: '16Gi'
                 limits.cpu: '4'
                 limits.memory: '16Gi'
       - name: generate-limitrange
         match:
           any:
           - resources:
               kinds:
               - Namespace
         generate:
           apiVersion: v1
           kind: LimitRange
           name: default-limitrange
           synchronize: true
           namespace: "{{request.object.metadata.name}}"
           data:
             spec:
               limits:
               - default:
                   cpu: 500m
                   memory: 1Gi
                 defaultRequest:
                   cpu: 200m
                   memory: 256Mi
                 type: Container
    

    ResourceQuota(RQ) defines the total resources (CPU, memory, storage, etc.) allocated to a namespace as a whole.

    LimitRanges(LR)specifies default resource requests and limits for individual containers running within the namespace.

    This above picture also shows that when a namespace is created, the policy will automatically enforce default Resource Quotas and Limit Ranges.