Policy rulesets in cloud-native environments

Just Enough

Policy Example

The language constructs shown let you define policies to detect anomalies, incorrect configurations, or instances of poor practice. The code in Listing 2 shows an example of how to identify Amazon Web Service (AWS) users who do not use multifactor authentication (MFA) when logging in.

Listing 2

AWS Users Without MFA

01 [
02    ....
03    {
04       "Path": "/",
05       "UserName": "liav",
06       "Arn": "arn:aws:iam::123456789:user/ferdinand",
07       "CreateDate": "2016-07-27 23:53:34+00:00",
08       "MFA": [
09          {
10          "UserName": "ferdinand",
11          "SerialNumber": "arn:aws:iam::123456789:mfa/ferdinand",
12          "EnableDate": "2021-04-25 09:00:38+00:00"
13          }
14       ],
15       "Groups": [
16          {
17          "GroupName": "RND-Admins"
18          }
19       ]
20    },
21    {
22       "Path": "/",
23       "UserName": "guido",
24       "Arn": "arn:aws:iam::123456789:user/guido",
25       "CreateDate": "1979-10-07 19:53:34+00:00",
26       "MFA": []
27       "Groups": [
28          {
29          "GroupName": "DevOps-Admins"
30          }
31       ]
32    },
33    ....
34 ]

After you have exported this input from AWS and saved it in the input.json file, you can use it as input for a policy:

package match
   count(input.MFA) > 0
match {
   not active_with_mfa

This example looks to see whether users have stored MFA devices.

OPA and Kubernetes

One of the best known use cases for OPA is on Kubernetes. From a technical point of view, you install OPA as a pod in the cluster. The admission controller then forwards the user's request to the address stored in the validating or mutating webhook. The app responding on this address mutates the received manifest or sends back an allow or deny response. Both are enforced by the admission controller.

The controllers are – to put this briefly – responsible for checking incoming (and authenticated) API requests and either allowing or rejecting them depending on the result. Internally, the admission control process has two phases: a mutating phase, in which requests can be modified, and a validation phase for verification. An admission controller variant can intervene at either of these two points.

One example of such a controller is LimitRanger, which provides pods with the default request resources while not allowing pods to grab more resources than intended by the limit. Of the more than 30 default admission controllers, two are worth special attention: ValidatingAdmissionWebhooks and MutatingAdmissionWebhooks. They do not implement any logic themselves but let you register REST endpoints of a service running in the cluster.

OPA is used as an admission controller, as well, and can thus configure a number of security features in the cluster by:

  • defining that all containers must have sidecars (e.g., to perform logging or auditing tasks),
  • providing all resources with annotations,
  • modifying the container image definition so that images can only be loaded from a defined image registry, and
  • setting node, pod affinity, and anti-affinity selectors for deployments.

Installing OPA in Kubernetes is quite easy [4]. The current Gatekeeper major release was published in 2019 in a collaboration between Google, Microsoft, Red Hat, and Styra (Figure 2).

Figure 2: Gatekeeper uses webhooks to integrate with the Kubernetes architecture. Source: Kubernetes documentation [5]

To define rules, you need to configure a constraint template, with which you define both the Rego code used for checking resources and the elements to which the constraint is applied.

Listing 3 shows how to set up a template if you want to force namespace labeling. The API type here is templates.gatekeeper.sh/v1beta1. The name field in the metadata section displays the name of the constraint. Within the spec section, you need to create a custom resource definition (CRD) in Kubernetes named K8sRequiredLabels and then configure the schema in the open-APIV3Schema block. The object has an attribute named labels and comprises an array of strings.

Listing 3

Adding Labels to Namespaces

01 apiVersion: templates.gatekeeper.sh/v1beta1
02 kind: ConstraintTemplate
03 metadata:
04    name: k8srequiredlabels
05 spec:
06    crd:
07       spec:
08          names:
09             kind: K8sRequiredLabels
10          validation:
11             # Schema for the parameter field
12             openAPIV3Schema:
13                properties:
14                   labels:
15                      type: array
16                      items: string
17    targets:
18       - target: admission.k8s.gatekeeper.sh
19          rego: |
20             package k8srequiredlabels
21             deny[{"msg": msg, "details": {"missing_labels": missing}}] {
22                provided := {label | input.review.object.metadata.labels[label]}
23                required := {label | label := input.parameters.labels[_]}
24                missing := required - provided
25                count(missing) > 0
26                msg := sprintf("you must provide labels: %v", [missing])
27             }

In the targets section, you use a constraint to specify who is responsible for the evaluation, which is the installed admission controller in this case. In the rego attribute, you then store the Rego code, which checks whether a namespace includes the desired labels.

To upload the constraint to the Kubernetes cluster, use the kubectl command:

kubectl apply --f https://raw.githubusercontent.com/open-policy-agent/gatekeeper/master/demo/basic/templates/k8srequiredlabels_template.yaml

Now you can use the constraint to define which labels must be present in the namespaces by creating a YAML file with the content shown in Listing 4.

Listing 4

Label Constraints

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

CRD is then installed with:

kubectl apply --f https://raw.githubusercontent.com/open-policy-agent/gatekeeper/master/demo/basic/constraints/all_ns_must_have_gatekeeper.yaml

At this point you will want to test the OPA rules and create a namespace with missing labels. The error message shown in Listing 5 should appear.

Listing 5

Error Message

kubectl apply -f badns.yaml
Error from server ([denied by ns-must-have-gk] you must provide labels: {"gatekeeper"}): error when creating "badns.yaml": admission webhook "validation.gatekeeper.sh" denied the request: [denied by ns-must-have-gk] you must provide labels: {"gatekeeper"}

IaC and OPA

OPA is very flexible and thus is suited to a wide variety of security use cases, for example, reviewing Terraform code is a popular application. You can run OPA manually, or you can include it in an IaC pipeline to run automatically during deployment. For this to work, you need to save the Terraform execution plan and convert it to JSON format:

terraform init
terraform plan --out tfplan.binary
terraform show -json tfplan.binary > tfplan.json

Next, you need to get to work on implementing the policy. The example in Listing 6 shows how to ensure that all S3 buckets in AWS have a private access control list (ACL). To run this test, enter:

Listing 6

Checking S3 Buckets for ACLs

01 package terraform
02 deny[msg] {
03    changeset := input.resource_changes[_]
04    is_create_or_update(changeset.change.actions)
05    changeset.type == "aws_s3_bucket"
06    changeset.change.after.acl != "private"
07    msg := sprintf("S3 bucket %v has a non-private acl", [
08       changeset.name
09    ])
10 }
11 is_create_or_update(actions) { actions[_] == "create" }
12 is_create_or_update(actions) { actions[_] == "update" }
opa eval --fail-defined --format raw --input tfplan.json --data policy/ 'data.main.deny[x]'

Buy this article as PDF

Express-Checkout as PDF
Price $2.95
(incl. VAT)

Buy ADMIN Magazine

Get it on Google Play

US / Canada

Get it on Google Play

UK / Australia

Related content

comments powered by Disqus
Subscribe to our ADMIN Newsletters
Subscribe to our Linux Newsletters
Find Linux and Open Source Jobs

Support Our Work

ADMIN content is made possible with support from readers like you. Please consider contributing when you've found an article to be beneficial.