Containers

Policy-based countermeasures for Kubernetes – Part 2

Choosing the Right Policy-As-Code Solution

In Part 1 of this series, we introduced the concept of policy-as-code (PaC), and discussed the following solutions: OPA, OPA/Gatekeeper and MagTape.

In this post (Part 2) we will review the Kyverno and k-rail PaC solutions.

For Kubernetes, there are several PaC solutions available in the open-source software (OSS) community. The following list is of PaC solutions, with their respective Cloud Native Computing Foundation (CNCF) project status (if applicable):

As we explore the PaC use cases, we will discuss the characteristics of each solution and point out aspects that can help organizations choose the best fit for their requirements.

All of the preceding solutions integrate with the Kubernetes API server using admission controllers and webhooks. These admission controllers are enabled for all Amazon Elastic Kubernetes Service (EKS) clusters; moreover, all of these solutions work well to validate EKS (EC2 and AWS Fargate) inbound Kubernetes API server requests.

Kubernetes integration and operation

In Kubernetes, the control plane is responsible for maintaining the desired state of a cluster. Requests are made to the control plane via calls to the Kubernetes API server. Valid changes are persisted into etcd by the API server. In fact, only the API server interacts directly with etcd. Once the desired state changes are in etcd, the API server, along with controllers and kubelets, ensure that actual cluster state reflects desired cluster state, which is stored in etcd.

Kubernetes admission controllers are a means by which the API server request flow can be intercepted and extended for operational needs, such as enforcing security controls and best practices.

According to Kubernetes documentation:

An admission controller is a piece of code that intercepts requests to the Kubernetes API server prior to persistence of the object, but after the request is authenticated and authorized.

As seen in the flow following diagram, PaC solutions work with the mutating and validating admission controllers in the API server request flow. Every Kubernetes cluster state change request goes through the API server request flow. Successful requests are persisted into etcd in the cluster control plane.

The PaC solutions can mutate inbound API server request JSON payloads, before Kubernetes object schema validation is performed. After Kubernetes object schema validation is performed, the PaC solutions policy engines can validate the inbound request, by matching policies to the request JSON payload, and using the policies to verify correct data in the payload. All of the preceding solutions are installed within the cluster.

Kubernetes API server webhooks for mutating and validating API server requests can be configured to use services outside of the respective Kubernetes cluster, as seen in this blog: https://aws.amazon.com/blogs/containers/building-serverless-admission-webhooks-for-kubernetes-with-aws-sam/

PaC solutions are integrated to a Kubernetes cluster API server, as an admission webhook target, via the Kubernetes ValidatingWebhookConfiguration resource. The following is an example configuration.

apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
  name: policy-validating-webhook-cfg
webhooks:
- admissionReviewVersions:
  - v1beta1
  clientConfig:
    caBundle: <ENCODED_CERTIFICATE_DATA>
    service:
      name: <SERVICE_NAME>
      namespace: <SERVICE_NAMESPACE>
      path: /<SERVICE_PATH>
      port: <SERVICE_PORT>
  failurePolicy: Ignore
  matchPolicy: Equivalent
  name: <WEBHOOK_NAME>
  namespaceSelector: {}
  objectSelector: {}
  rules:
  - apiGroups:
    - <KUBERNETES_API_GROUP>
    apiVersions:
    - <API_VERSION>
    operations:
    - CREATE
    - UPDATE
    resources:
    - <KUBERNETES_RESOURCE>
    scope: '*'
  sideEffects: NoneOnDryRun
  timeoutSeconds: 3

For the purposes of this post, we will examine validation policies and configurations from multiple solutions.

Guardrails and instant feedback

Using validating admission controllers and PaC solutions allows organizations to erect guardrails that prevent unwanted cluster changes, while still enabling users to move fast. The following example response is from a Kubernetes API server request. The request was a kubectl apply -f ... request to create a Kubernetes Deployment resource, where the source image registry in the container spec was not on a list of allowed registries.

Error from server ("DEPLOYMENT_INVALID": "BAD_REGISTRY/read-only-container:v0.0.1" 
image is not sourced from an authorized registry. Resource ID (ns/name/kind): 
"test/test/Deployment"): error when creating "tests/11-dep-reg-allow.yaml": 
admission webhook "validating-webhook.openpolicyagent.org" denied the request: 
"DEPLOYMENT_INVALID": "BAD_REGISTRY/read-only-container:v0.0.1" image is not 
sourced from an authorized registry. Resource ID (ns/name/kind): 
"test/test/Deployment"

The API server sent the request payload to a PaC policy engine service installed in the cluster. The policy engine applied a matching policy and determined that the API server request payload was invalid. Matching and validation were based on the rules defined in the policy. The policy engine service responded to the API server with a boolean False, and a message of why the request was invalid. Then the API server (within 1-2 seconds) responded to the requester, in this case, the kubectl client. The end result was instant feedback from the cluster that (1) the request failed and (2) why it failed. This fast-failure with instant feedback improved the user-experience and helped the user quickly troubleshoot and correct their mistake.

Policy authoring – Kyverno

If you are just not into writing and maintaining Rego for your PaC solution, and would rather work with YAML and other native Kubernetes constructs, then Kyverno may be your choice. When installed, Kyverno includes CRDs that enable authors to create policies that are either scoped to namespaces or cluster-wide.

The following example Kyverno ClusterPolicy resource, backed by a CRD, solves the allowed-registry use case that we have been discussing. This cluster-wide policy contains one rule: validate-registries. According to Kyverno documentation:

Each rule has a match clause, an optional exclude clause, and one of a mutate, validate, or generate clause.

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: deployment-pod-valid-registry
  annotations:
    policies.kyverno.io/category: Security
spec:
  background: false
  validationFailureAction: enforce
  rules:
  - name: validate-registries
    match:
      resources:
        kinds:
        - Pod
    preconditions:
    - key: "{{ request.operation }}"
      operator: In
      value: ["CREATE", "UPDATE"]
    validate:
      message: "Unknown image registry"
      pattern:
        spec:
          containers:
          - image: "GOOD_REGISTRY/* | VERY_GOOD_REGISTRY/*"

The preceding policy validates that when a Pod resource is created or updated, the container images for the pod must include the GOOD_REGISTRY or VERY_GOOD_REGISTRY values.

This policy is written explicitly for a Pod resource and not a Deployment resource. However, this policy does apply to both Pods and Deployments. In fact, “the policy applies to all resources capable of generating Pods.” This is made possible by the Kyverno feature that auto-generates rules for pod controllers. This feature automatically annotates the policy with the pod-policies.kyverno.io/autogen-controllers: DaemonSet,Deployment,Job,StatefulSet,CronJob annotation, and adds match clauses to the annotated policy for the other resources.

...
    - match:
      resources:
        kinds:
        - DaemonSet
        - Deployment
        - Job
        - StatefulSet
    name: autogen-validate-registries
    validate:
      message: Unknown image registry
      pattern:
        spec:
          template:
            spec:
              containers:
              - image: GOOD_REGISTRY/* | VERY_GOOD_REGISTRY/*
  - match:
      resources:
        kinds:
        - CronJob
    name: autogen-cronjob-validate-registries
    validate:
      message: Unknown image registry
      pattern:
        spec:
          jobTemplate:
            spec:
              template:
                spec:
                  containers:
                  - image: GOOD_REGISTRY/* | VERY_GOOD_REGISTRY/*
...

Besides mutation and validation, Kyverno policies support rules that can generate resources, with the generate clause. This feature solves the use case when additional resources need to be generated based on the creation or update of another resource. For example, a common Kubernetes use case for multi-tenant clusters is to provision namespaces and then create deny-all-in/out network policies to lock-down the namespaces, before any new pods are created. With the generate clause, Kyverno policies can be created that solve for that use case. That use case and others are described in the following document: https://neonmirrors.net/post/2021-01/kyverno-the-swiss-army-knife-of-kubernetes/#resource-padlock

Another use case for the generate clause is a Velero self-service backup solution described in this article: https://nirmata.com/2021/01/24/self-service-velero-backups-with-kyverno/. According to the article:

In order to enable self-service backups with Velero, we will use the “generate” capabilities of Kyverno to automatically generate a schedule to backup the resources and data when a specific label, nirmata.io/auto-backup=enabled is added to a namespace.

For examples of Kyverno policies, please see the following repos:

Policy authoring – k-rail

k-rail is described as “workload policy enforcement tool for Kubernetes.” In k-rail, policies are written in Golang, and integrated directly into the policy engine. Installing k-rail via helm installs the policy engine, as well as two ConfigMap resources that contain partially externalized policy configurations and exemptions. Sticking with our theme of allowed-registry policies, the following sample policy validates that images are sourced from allowed registries.

// Copyright 2019 Cruise LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//    https://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package pod

import (
    "context"
    "regexp"

    admissionv1 "k8s.io/api/admission/v1"
    corev1 "k8s.io/api/core/v1"

    "github.com/cruise-automation/k-rail/policies"
    "github.com/cruise-automation/k-rail/resource"
)

type PolicyTrustedRepository struct{}

func (p PolicyTrustedRepository) Name() string {
    return "pod_trusted_repository"
}

func (p PolicyTrustedRepository) Validate(ctx context.Context, config policies.Config, ar *admissionv1.AdmissionRequest) ([]policies.ResourceViolation, []policies.PatchOperation) {

    resourceViolations := []policies.ResourceViolation{}

    podResource := resource.GetPodResource(ctx, ar)
    if podResource == nil {
        return resourceViolations, nil
    }

    validateContainer := func(container corev1.Container) {
        matches := 0
        for _, pattern := range config.PolicyTrustedRepositoryRegexes {
            if matched, _ := regexp.MatchString(pattern, container.Image); matched {
                matches++
            }
        }

        if matches == 0 {
            violationText := "Trusted Image Repository: image must be sourced from a trusted repository. Untrusted Images: " + container.Image
            resourceViolations = append(resourceViolations, policies.ResourceViolation{
                Namespace:    ar.Namespace,
                ResourceName: podResource.ResourceName,
                ResourceKind: podResource.ResourceKind,
                Violation:    violationText,
                Policy:       p.Name(),
            })
        }
    }

    for _, container := range podResource.PodSpec.Containers {
        validateContainer(container)
    }

    for _, container := range podResource.PodSpec.InitContainers {
        validateContainer(container)
    }

    return resourceViolations, nil
}

While k-rail policies are written in Golang, the policies are configured and enabled via entries in the k-rail-config ConfigMap in the k-rail namespace. The following snippet is used to configure the RegEx settings for the registry matching and enable the policy for validation.

...
  policy_config:
...
    policy_trusted_repository_regexes:
      - '^regsitry.k8s.io/.*'               # official k8s GCR repo
      - '^[A-Za-z0-9\-:@]+$'           # official docker hub images
...
  policies:
    - name: "pod_trusted_repository"
      enabled: True
      report_only: False
...

Sample k-rail policies can be had from the k-rail GitHub repo: https://github.com/cruise-automation/k-rail/tree/master/policies

Webhook failure modes (fail open vs. fail closed)

Services that integrate into the Kubernetes API server as validating admission controllers can be configured to fail open or closed. In the fail open scenario, if the API server webhook call to the validation service times out (default 10 secs), changes from the respective API server request are permitted to proceed to etcd. Fail open is used so that clusters are not rendered inoperable when the connected validation services are unavailable. Like OPA and OPA/Gatekeeper, Kyverno is set up, at least currently, to fail open. By default, the MagTape (covered in Part 1) and k-rail solutions are set to fail closed, and run multiple pods to service the calls from the API server.

At the time of this writing, work on a Kyverno fail-closed functionality is in progress.

Failure modes for these solutions are set up in the respective validating webhook configuration resources using the failurePolicy element. The following setting is for a fail closed scenario.

failurePolicy: Fail

Background scanning

Due the prevalence of the fail-open scenario, and the strong desire to not “brick” a cluster (or fleet thereof), solutions have emerged, both open-source and commercial, that provide auditing and background scanning features. These solutions are designed to be compensating controls for the case when preventative validation is not possible and reactive detection is required.

Kyverno offers the background scanning feature (https://kyverno.io/docs/writing-policies/background/) that enables its polices to be used to detect violations in existing resources. According to the documentation:

Kyverno can validate existing resources in the cluster that may have been created before a policy was created. This can be useful when evaluating the potential effects new policies will have on a cluster prior to changing them to enforce mode. The application of policies to existing resources is referred to as background scanning.

On the commercial side, Nirmata, the creators of Kyverno, offer a commercial policy-as-code solution called Continuous Compliance. According to Nirmata, their Continuous Compliance solution provides:

Managed policy groups using a GitOps style workflow and deploy across your fleet of clusters.

In-cluster controls to validate, mutate, and generate resource configurations based on policies.

Automated fine-grained configuration changes to provide self-service and eliminate delays.

Customizable reporting and sharing with workload scorecards, remediation guidelines, and best practice recommendations, along with CI/CD integrations.

Extensibility – programming and data

The Kyverno and k-rail solutions discussed in this series of posts are open-source solutions, primarily written in Golang. Given those characteristics, these solutions can be extended, at a minimum, via their respective policy syntax, or by contributing to the OSS projects. Beyond that, Kyverno also provides the ability to use external data via Kubernetes ConfigMap resources in the following document: https://kyverno.io/docs/writing-policies/variables/.

The following Kyverno example uses config map data. The ns-roles-dictionary config map is used to build a dictionary data structure of allowed roles per environment keys.

kind: ConfigMap
apiVersion: v1
metadata:
  name: ns-roles-dictionary
  namespace: kyverno
  labels:
    app: kyverno
    owner: jimmy
data:
  prod: "arn:aws:iam::123456789012:role/prod"
  dev: "arn:aws:iam::123456789012:role/dev"
  kyverno-test: "[\"arn:aws:iam::123456789012:role/test\", \"arn:aws:iam::123456789012:role/dev\"]"

The data in the preceding config map is used in the following Kyverno policy to provide the allowed roles per environment that the policy needs to validate.

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: deployment-valid-role
  labels:
    app: kyverno
    owner: jimmy
  annotations:
    policies.kyverno.io/category: Security
    policies.kyverno.io/description: Rules to enforce valid roles, based on namespace-role dictionary
spec:
  validationFailureAction: enforce
  rules:
  - name: validate-role-annotation
    context:
      - name: ns-roles-dictionary
        configMap:
          name: ns-roles-dictionary
          namespace: kyverno
    match:
      resources:
        kinds:
        - Deployment
    preconditions:
    - key: "{{ request.object.metadata.namespace }}"
      operator: In
      value: ["prod", "dev", "kyverno-test"]
    - key: "{{ request.object.spec.template.metadata.annotations.\"iam.amazonaws.com/role\" }}"
      operator: NotEquals
      value: ""
    validate:
      message: "Annotation iam.amazonaws.com/role \"{{ request.object.spec.template.metadata.annotations.\"iam.amazonaws.com/role\" }}\" is not allowed for the \"{{ request.object.metadata.namespace }}\" namespace."
      deny:
        conditions:
        - key: "{{ request.object.spec.template.metadata.annotations.\"iam.amazonaws.com/role\" }}"
          operator: NotIn
          value:  "{{ \"ns-roles-dictionary\".data.\"{{ request.object.metadata.namespace }}\" }}"

Kubernetes policy architecture and direction

As discussed in Part 1 one of this series, the future of policy architecture for Kubernetes is being formed in the Kubernetes Policy Working Group. According to the description of the group, their purpose is to:

Provide an overall architecture that describes both the current policy related implementations as well as future policy related proposals in Kubernetes. Through a collaborative method, we want to present both dev and end user a universal view of policy architecture in Kubernetes.

Creators and sustainers of some of the solutions discussed in this series, as well as other interested parties, are engaged in this group. What comes from this group will drive the future directions for policy-as-code solutions. There are several initiatives being considered and underway that might interest users and decision makers, and help cluster admins and security professionals. Two of those initiatives are:

An interesting specification from the Policy WG that has made it into Kyverno as a feature is the ability to report on background policy violations. Policy Reports, seen in the following example, are backed by CRDs, and provide insight into policy activity from background scans.

apiVersion: wgpolicyk8s.io/v1alpha1
kind: PolicyReport
...
results:
- category: Security
  message: validation rule 'dep-validate-privileged' passed.
  policy: deployment-pod-security-context
  resources:
  - apiVersion: apps/v1
    kind: Deployment
    name: test
    namespace: kyverno-test
    uid: b38229d2-a9f1-41b8-ad0e-b0f1a677f0db
  rule: dep-validate-privileged
  scored: true
  status: pass
- category: Security
  message: validation rule 'dep-validate-allowPrivilegeEscalation' passed.
  policy: deployment-pod-security-context
  resources:
  - apiVersion: apps/v1
    kind: Deployment
    name: test
    namespace: kyverno-test
    uid: b38229d2-a9f1-41b8-ad0e-b0f1a677f0db
  rule: dep-validate-allowPrivilegeEscalation
  scored: true
  status: pass
- category: Security
  message: validation rule 'dep-validate-readOnlyRootFilesystem' passed.
  policy: deployment-pod-security-context
  resources:
  - apiVersion: apps/v1
    kind: Deployment
    name: test
    namespace: kyverno-test
    uid: b38229d2-a9f1-41b8-ad0e-b0f1a677f0db
  rule: dep-validate-readOnlyRootFilesystem
  scored: true
  status: pass
- category: Security
  message: 'validation error: Unknown image registry. Rule autogen-validate-registries
    failed at path /spec/template/spec/containers/0/image/'
  policy: deployment-pod-valid-registry
  resources:
  - apiVersion: apps/v1
    kind: Deployment
    name: test
    namespace: kyverno-test
    uid: b38229d2-a9f1-41b8-ad0e-b0f1a677f0db
  rule: autogen-validate-registries
  scored: true
  status: fail
- message: validation rule 'deployment-labels' passed.
  policy: deployment-require-labels
  resources:
  - apiVersion: apps/v1
    kind: Deployment
    name: test
    namespace: kyverno-test
    uid: b38229d2-a9f1-41b8-ad0e-b0f1a677f0db
  rule: deployment-labels
  scored: true
  status: pass
- category: Security
  message: validation rule 'pod-validate-readOnlyRootFilesystem' passed.
  policy: deployment-pod-security-context
  resources:
  - apiVersion: v1
    kind: Pod
    name: test-68b9dcd84b-98qpt
    namespace: kyverno-test
    uid: d8b3a0f3-09b5-4ec1-a5a4-76f21b0d174a
  rule: pod-validate-readOnlyRootFilesystem
  scored: true
  status: pass
- message: validation rule 'pod-labels' passed.
  policy: deployment-require-labels
  resources:
  - apiVersion: v1
    kind: Pod
    name: test-68b9dcd84b-98qpt
    namespace: kyverno-test
    uid: d8b3a0f3-09b5-4ec1-a5a4-76f21b0d174a
  rule: pod-labels
  scored: true
  status: pass
- category: Security
  message: validation rule 'pod-validate-runAsNonRoot' anyPattern[3] passed.
  policy: deployment-pod-security-context
  resources:
  - apiVersion: v1
    kind: Pod
    name: test-68b9dcd84b-98qpt
    namespace: kyverno-test
    uid: d8b3a0f3-09b5-4ec1-a5a4-76f21b0d174a
  rule: pod-validate-runAsNonRoot
  scored: true
  status: pass
- category: Security
  message: Annotation iam.amazonaws.com/role "arn:aws:iam::123456789012:role/test"
    is not allow for the "kyverno-test" namespace.
  policy: deployment-valid-role
  resources:
  - apiVersion: apps/v1
    kind: Deployment
    name: test
    namespace: kyverno-test
    uid: b38229d2-a9f1-41b8-ad0e-b0f1a677f0db
  rule: validate-role-annotation
  scored: true
  status: pass
summary:
  error: 0
  fail: 1
  pass: 8
  skip: 0
  warn: 0

The preceding report shows passes and fails for policies implemented in a test cluster.

Shifting policy enforcement to the left

In the context of DevOps, “shifting left” means that steps or stages that normally take place later in automated CICD pipelines, or even within the Systems Development Life Cycle (SDLC), are moved to earlier steps or stages in the process. The premise is based on the value proposition that it is faster, cheaper, and less complex to identify and remediate issues earlier in the process. Shifting issues identification and remediation to the left can also reduce impact on downstream systems and stakeholders.

Not withstanding the mature integration between Kubernetes and PaC solutions, shifting data mutation and validation decisions to the left is a foundational characteristic of DevOps. To that end, the ability to evaluate policies and structured data in automated DevOps pipelines, or even at the developer desktop, should be considered when evaluating PaC solutions.

Kyverno offers a solution for shifting evaluations to the left. The Kyverno Command-Line Interface (CLI) allows users to validate policies and structured data, outside of Kubernetes clusters. While there are multiple ways to install the CLI, I opted to use krew to install the Kyverno CLI as a kubectl plugin, with the following command:

kubectl krew install kyverno

Using the validate subcommand, the Kyverno CLI can be used to validate policies before they are used to evaluate data.

kubectl kyverno validate 3-dep-pod-valid-registry.yaml
----------------------------------------------------------------------
Policy deployment-pod-valid-registry is valid.

The apply subcommand is used to evaluate structured data using a Kyverno policy written for Kubernetes, with the Kyverno CLI.

kubectl kyverno apply 3-dep-pod-valid-registry.yaml -r ../test/11-dep-reg-allow.yaml

applying 1 policy to 1 resource...

policy deployment-pod-valid-registry -> resource kyverno-test/Deployment/test failed:
1. autogen-validate-registries: validation error: Unknown image registry. Rule autogen-validate-registries failed at path /spec/template/spec/containers/0/image/

pass: 0, fail: 1, warn: 0, error: 0, skip: 0

It’s important to point out that Kyverno auto-gen feature works with the Kyverno CLI on policy evaluations outside of the Kubernetes cluster, like it did earlier, inside the cluster, as an admission controller solution.

The Kyverno CLI also offers the ability to create Policy Reports, an emerging specification from the Kubernetes Policy Working Group. The following example is the same apply command with the —policy-report flag used to generate the policy report for the policy/data evaluation.

kubectl kyverno apply 3-dep-pod-valid-registry.yaml -r ../test/11-dep-reg-allow.yaml --policy-report

applying 1 policy to 1 resource...
----------------------------------------------------------------------
POLICY REPORT:
----------------------------------------------------------------------
apiVersion: wgpolicyk8s.io/v1alpha1
kind: ClusterPolicyReport
metadata:
  name: clusterpolicyreport
results:
- message: 'validation error: Unknown image registry. Rule autogen-validate-registries failed at path /spec/template/spec/containers/0/image/'
  policy: deployment-pod-valid-registry
  resources:
  - apiVersion: apps/v1
    kind: Deployment
    name: test
    namespace: kyverno-test
  rule: autogen-validate-registries
  scored: true
  status: fail
summary:
  error: 0
  fail: 1
  pass: 0
  skip: 0
  warn: 0

Policy reports provide structured report data that can be consumed by downstream processes and systems, such as those used for security information and event management (SIEM).

Being able to reuse these Kubernetes PaC solutions in DevOps pipelines enables organizations to empower their developers to fail, react, and succeed fast, with minimal impact to operating environments. DevOps usage of these PaC solutions can validate both structured data and the policies used for evaluation. The capability to test and validate actual policies before they are used to evaluate data enables organizations to correctly apply traceability and provenance (which is important to audit requirements) to data validation methods used in their processes.

Policy decisions made early in the software lifecycle or container supply chain have the following affects:

  • Save both time and resources
  • Prevent unwanted behaviors and changes
  • Reduce attack surfaces and blast radii
  • Minimize impacts to systems and environments

Making the right choice for your use case and organization

Choosing the right policy engine requires that you consider multiple factors. Some of those factors include:

  • Community adoption of solution
  • Complexity of solution, or conversely, ease-of-use
  • Alignment of solution to organizational capabilities
  • Alignment of solution to compute strategies
  • Use cases that solution will need to satisfy across your enterprise
  • Support model (community vs enterprise)
  • Solution extensibility
  • Integration to existing tooling

Given the preceding factors, organizations should decide on the level of importance (weight) each factor carries within their respective organizations, as well as the level of fit each solution provides for those weighted factors. Organizations may also need to add organization-specific factors not listed. A simple and effective approach towards a data-driven decision is to use a spreadsheet or scorecard, fed by data from solution testing and evaluation.

The sample scorecard illustrates how organizations could choose the right solution for their needs. The weight and fit values are multiplied for each Solution-Factor combination, and those products are summed for an overall solution score. It’s important to understand that, for a data-driven outcome, the “fit” column should be based on criteria determined by the organization, and satisfaction of said criteria, by the solution under evaluation, demonstrated in testing and/or proofs-of-concept. During the evaluation of these solutions, the open-source communities for each project are valuable resources to gain insights about potential use cases, and to help organizations learn more about the individual solutions.

Summary

As more organizations move to containers as a means of delivering applications and solutions, these same organizations must keep up with the never-ending and ever-changing demand on security, compliance, privacy, and best practices. There is no such thing as “secure enough“ or ”good enough.” Application strategies that deliver fast-paced solutions and innovation can use policy-as-code solutions for more secure outcomes and enforcement of best practices.

Policy-as-code solutions provide automated guardrails that both enable users as well as prevent unwanted behaviors. While not a one-way door, choosing the right policy-as-code solution is not a trivial task. Organizations must consider several factors when making the right choice. Making data-driven decisions based on the outcome of testing and proofs-of-concept is a sound approach. Regardless of selected solution, policy-as-code is emerging as a foundational component to DevOps and defense-in-depth strategies.

The Kyverno policies shared in this post are available from the EKS Best Practices Guides GitHub companion repository: https://github.com/aws/aws-eks-best-practices/tree/master/policies