Lead Image © bloomua, 123RF.com

Lead Image © bloomua, 123RF.com

Policy rulesets in cloud-native environments

Just Enough

Article from ADMIN 71/2022
By
What a user is allowed to do in a program is usually defined by a role model, which often poses numerous challenges, especially in the cloud or for infrastructure as code. The free Open Policy Agent offers a flexible way to manage user rights.

Infrastructure as Code (IaC) has become a successful recipe for declarative, machine-readable code, so it only makes sense to apply this system to security and, in particular, to authoring policies in an attempt to implement rules within an organization in a scalable way. One representative of this genre that has recently received greater attention is the Open Policy Agent (OPA) project [1], which is backed by startup Styra. OPA is a general-purpose policy engine that enables consistent, context-aware policy enforcement across the stack.

OPA at a Glance

OPA is hosted by Cloud Native Computing Foundation (CNCF), the organization behind Kubernetes. Designed for cloud-native environments, OPA combines the relatively easy-to-learn and -read Rego policy language with a policy model and application programming interface (API), which allows for a kind of universal framework that applies rules to any kind of stacks. One of the great advantages of OPA is the ability to decouple security policies from code and its use – regardless of how often the code changes.

From a technical point of view, OPA is tied to the input. Once data is available, the OPA code decides how to handle it (e.g., allowing or blocking with an allow or deny policy). Another advantage is that OPA processes take input and create output in both JSON and YAML formats, meaning that IT managers do not have to stick to a predefined API. All told, writing rules is relatively easy, and OPA supports read, evaluate, print, and loop (REPL, i.e., shell-based code execution). Of practical value is that you do not have to write all the policies yourself, because you can easily find ready-made policy bundles online for many use cases, and they are likely to contain a useful, predefined set of rules. A freely accessible Playground [2] and a free Styra Academy [3] can help you learn.

As expected, OPA use cases all relate to security:

  • The expressiveness of OPA and Rego make it easy to define rules for user authorization and keep them in a central location.
  • OPA can help with authorization if you use API gateways such as Kong, Traefik, or Tyk.
  • OPA has various areas of application for continuous integration and continuous delivery or deployment (CI/CD), such as checking IaC code against a ruleset. Typical examples include preventing public IP addresses on virtual machines.
  • OPA plays a major role in the Kubernetes environment. Admission controllers can send requests to OPA to receive a decision on which resources can be deployed in a Kubernetes cluster.

One hurdle you might encounter is having to relearn Rego. OPA is based on the Go programming language, and the only client SDK is for Go at the moment.

Installing OPA

OPA is a single, quickly installed binary file. It supports Linux, macOS, and Windows. On Linux, you install the binary, change the file attributes to make it executable, and add the location of the OPA binaries to your PATH variable:

curl -L -o opa https://openpolicyagent.org/downloads/latest/opa_linux_amd64
chmod 755 opa
export PATH=<path to the OPA binary>

If you work with Visual Studio Code, you should install a plugin by selecting the Extension menu in the left pane and searching for Open Policy Agent . When found, click Install (Figure 1). Once you have installed the extension, you will be able to create a new file in Rego with rules such as:

package hello
default allow_hello = false
default allow_world = false
allow_hello {
   "hello" != ""
}
allow_world {
   "world" != "world"
}
Figure 1: The OPA plugin for Visual Studio Code makes life easy.

To evaluate the rule, run View | Command Palette | OPA Evaluate Package . A second tab then opens to display the results. The results on the right side are easy to understand: To begin, the allow_hello and allow_world variables are set to false, then their values are checked in a function.

Working with Variables, Objects, and Functions

To run simple expressions, you can start an interactive shell from the terminal with the opa run command and then run commands. For example, begin by defining some variables,

greeting := "Hello"
max_height := 42
pi := 3.14159
allowed := true
location := null

then output the values (in an array, if so desired),

[greeting, max_height, pi, allowed, location]
[
   "Hello",
   42,
   3.14159,
   true,
   null
]

and define objects:

ips_by_port := {
   80: ["1.1.1.1", "1.1.1.2"],
   443: ["2.2.2.1"],
}
ips_by_port[80]
[
   "1.1.1.1",
   "1.1.1.2"
]

This example first defines an object that comprises sub-elements, including an array of strings for defining IP addresses. The second block accesses those elements:

Rules are an important element in Rego. In the next example, the default value is false. If an input has a role value equal to admin, the value changes to true. Alternatively, you can change the default if the role is user and the has_permissions field is true:

default allow = false
allow = true {
   input.role == "admin"
}
allow = true {
   input.role == "user"
   input.has_permission == true
}

Functions can also be defined, as in this code fragment, which implements and calls a multiply function:

package function
multiply(a,b) = m {
   m := a*b
}
result1 = r {
   r := multiply(3,4)
}
result2 = r {
   r := multiply(3,9)
}

The Rego programming language has native functions for:

  • numbers
  • bit manipulation
  • aggregation
  • sets (array, set, object)
  • strings
  • regular expressions
  • HTTP
  • JSON web tokens (JWT)

Listing 1 shows some function call examples.

Listing 1

Example Function Calls

# Rounding up
# 11
round(10.9)
# Generate an array of numbers
# [9, 8, 7, 6, 5, 4, 3, 2, 1]
numbers.range(9, 1)
# The type is "array"
# "array"
type_name(["apple","banana","pineaplle"])
# The input is a set
# true
is_set({1,2,3})
# The object includes two elements
# 2
count({"width":1024, "height":768})
# [1,2,3,4,5,4,5,6]
array.concat([1,2,3,4,5],[4,5,6])
# Extract key3, because this is not part of the object; a "val3" value is returned
obj := {"key1":"val1", "key2":"val2"}
# "val3"
object.get(obj, "key3", "val3")
# Ayt what position does the word "world" occur?
# 7
indexof("Hello, world", "world")
# POST HTTP call with header and body
http.send({"url":"http://httpbin.org/post", "method":"post", "timeout":"3s", "headers":{"token":"111"}, "body":{"key": "val"}})

Buy this article as PDF

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

Buy ADMIN Magazine

SINGLE ISSUES
 
SUBSCRIPTIONS
 
TABLET & SMARTPHONE APPS
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.

Learn More”>
	</a>

<hr>		    
			</div>
		    		</div>

		<div class=