Hello, I'm Zhang Jintao.
In this article, I'd like to introduce you to a general policy engine that I personally like, called OPA, whose full name is Open Policy Agent.
Before we talk about OPA, let's talk about why we need a general policy engine and what problems OPA solves.
What problem does OPA solve
In the actual production environment, policy control is required in many scenarios, such as:
- Policies are needed to control whether users can log in to the server or do some operations;
- Policies are needed to control which projects / components can be deployed;
- Policies are needed to control how to access the database;
- Policies are needed to control which resources can be deployed to Kubernetes;
img
However, for these scenarios or software, the strategies for configuring them need to be coupled with the software, which are not unified and universal. It will also be chaotic in management, resulting in no small maintenance cost.
The emergence of OPA can unify the strategies configured everywhere and greatly reduce the maintenance cost. And decouple the strategy from the corresponding software / service to facilitate transplantation / reuse.
img
Development process of OPA
OPA was originally an open source project created by Styra company in 2016. At present, the company's main product is to provide visual Dashboard services for visual policy control and policy implementation.
OPA first entered CNCF and became a sandbox level project in 2018. It graduated from CNCF in February 2021. This process is relatively fast. It can also be seen that OPA is an active and widely used project.
What is OPA
As we mentioned earlier, Open Policy Agent (OPA) is an open source general policy engine, which can realize unified and context aware policy control in the whole stack.
OPA can separate (decouple) the policy decision from the business logic of the application. Looking at the essence through the phenomenon, the policy is a set of rules. The request is sent to the engine, and the engine makes decisions according to the rules.
img
Figure 3. Policy decoupling example of OPA
OPA is not responsible for the execution of specific tasks. It is only responsible for decision-making. Requests requiring decision-making are passed to OPA in JSON. After OPA makes decision, the results will also be returned in JSON.
Rego
The policy in OPA is represented by a DSL(Domain Specific Language) such as Rego.
Rego datalog( https://en.wikipedia.org/wiki/Datalog )And extends the support of datalog for structured document model to facilitate data processing in JSON.
Rego allows policy makers to focus on queries that return content rather than how to execute queries. At the same time, OPA also has built-in optimization during rule execution, which can be used by users by default.
Rego allows us to encapsulate and reuse logic using rules (if then), which can be complete or partial.
Each rule consists of a head and a body. In Rego, if the rule body assigns a true value to some variables, we say that the rule header is true. You can query its value by referring to any rule loaded into OPA through absolute path. The path of the rule is always: Data (all values generated by rules can be queried through global data variables. For example, data.example.rules.any_public_networks in the following example
- The complete rule is an if then statement that assigns a single value to a variable.
img
Figure 4. Rego complete rule example
- Some rules are if then statements that generate a set of values and assign the set to variables.
img
Figure 5, example of some Rego rules
- Logical or to define multiple rules with the same name in Rego. (when multiple expressions are connected together in a query, it represents logical AND)
img
Fig. 6 is an example diagram of full rules and partial rules of Rego rules or
Use of OPA
The use of OPA is still relatively simple. Let's take a look.
Install OPA
Binary mode
We can download its binary directly from the release page of OPA for use
➜ ~ wget -q -O ~/bin/opa https://github.com/open-policy-agent/opa/releases/download/v0.35.0/opa_linux_amd64_static ➜ ~ chmod +x ~/bin/opa ➜ ~ opa version Version: 0.35.0 Build Commit: a54537a Build Timestamp: 2021-12-01T02:11:47Z Build Hostname: 9e4cf671a460 Go Version: go1.17.3 WebAssembly: unavailable
container
We can use its official image
➜ ~ docker run --rm openpolicyagent/opa:0.35.0 version Version: 0.35.0 Build Commit: a54537a Build Timestamp: 2021-12-01T02:10:31Z Build Hostname: 4ee9b086e5de Go Version: go1.17.3 WebAssembly: available
OPA interaction
opa eval
The simplest command is opa eval. Of course, we can use it not only for policy execution, but also for expression calculation.
img
Figure 7. Usage help of opa eval
➜ ~ opa eval "6+6" { "result": [ { "expressions": [ { "value": 12, "text": "6+6", "location": { "row": 1, "col": 1 } } ] } ] }
opa run
opa run launches an interactive shell (REPL). We can use REPL to experiment with strategies and build new prototypes.
➜ ~ opa run OPA 0.35.0 (commit a54537a, built at 2021-12-01T02:11:47Z) Run 'help' to see a list of commands and check for updates. > true true > ["Hello", "OPA"] [ "Hello", "OPA" ] > pi := 3.14 Rule 'pi' defined in package repl. Type 'show' to see rules. > show package repl pi := 3.14 > pi > 1 true
We can also load policies directly, or run OPA as a service and query through HTTP. By default, OPA will listen on port 8181.
➜ ~ opa run --server {"addrs":[":8181"],"diagnostic-addrs":[],"level":"info","msg":"Initializing server.","time":"2021-12-07T01:12:47+08:00"}
Open the browser and you can also see a simple query window
img
opa is used as the library of go
OPA can be embedded into the Go program as a library. The easiest way to embed OPA as a library is to import the github.com/open-policy-agent/opa/rego package. The rego.New function is used to create an object that can be prepared or evaluated, PrepareForEval() to obtain an executable query.
Here is a simple example:
- directory structure
➜ opa tree . ├── data ├── go.mod ├── go.sum ├── input.json ├── k8s-label.rego └── main.go 1 directory, 5 files
- Policy file
The policy file here is to verify whether the INPUT contains a label named domain and whether the label starts with moelove -.
package kubernetes.validating.existence deny[msg] { not input.request.object.metadata.labels.domain msg := "Every resource must have a domain label" } deny[msg] { value := input.request.object.metadata.labels.domain not startswith(value, "moelove-") msg := sprintf("domain label must start with `moelove-`; found `%v`", [value]) }
- INPUT file, take the AdmissionReview of Kubernetes as an example
{ "kind": "AdmissionReview", "request": { "kind": { "kind": "Pod", "version": "v1" }, "object": { "metadata": { "name": "myapp", "labels": { "domain": "opa" } }, "spec": { "containers": [ { "image": "alpine", "name": "alpine" } ] } } } }
- main.go file
package main import ( "context" "encoding/json" "fmt" "log" "os" "github.com/open-policy-agent/opa/rego" ) func main() { ctx := context.Background() // Construct a Rego object that can be prepared or evaluated. r := rego.New( rego.Query(os.Args[2]), rego.Load([]string{os.Args[1]}, nil)) // Create a prepared query that can be evaluated. query, err := r.PrepareForEval(ctx) if err != nil { log.Fatal(err) } // Load the input document from stdin. var input interface{} dec := json.NewDecoder(os.Stdin) dec.UseNumber() if err := dec.Decode(&input); err != nil { log.Fatal(err) } rs, err := query.Eval(ctx, rego.EvalInput(input)) if err != nil { log.Fatal(err) } fmt.Println(rs) }
The results are as follows:
➜ opa go run main.go k8s-label.rego "data" < input.json [{[map[kubernetes:map[validating:map[existence:map[deny:[domain label must start with `moelove-`; found `opa`]]]]]] map[]}]
summary
The above is a general introduction to OPA. There are many application scenarios of OPA. In subsequent articles, we will share the application of OPA in Kubernetes and the scenario of applying OPA to CI/CD pipeline. Please look forward to it.