Implementing OPA authorization

Introduction

The Stackable Platform offers an OpenPolicyAgent (OPA) operator for policy-based access control. This document shows how to configure a Stackable operator and its managed product to query OPA to enforce policy-based access control.

What is OPA?

OPA is an open source, general purpose policy engine. It supports a high-level declarative language called Rego. Rego enables you to specify complex policies as code and transfer the decision-making processes from your software to OPA. We refer to policies written in Rego as Rego rules.

The provided OPA REST API allows you to enforce policies within microservices, Kubernetes, CI/CD pipelines and more.

OPA accepts arbitrary structured input data (e.g. JSON) when running queries against the API (and your Rego rules), to decouple policy decision-making from policy enforcement.

Examples for OPA policies

The combination of arbitrary input data and the Rego rules enable you to specify and enforce almost any kind of policies. You can define powerful policies for e.g. user access for database tables, schemas, columns etc. You can enforce local network traffic, access time periods and many more.

See the OPA documentation for further examples.

Stackable Operator for OPA

The Stackable Operator for OPA deploys OPA as a DaemonSet in Kubernetes. This ensures that every registered Node runs exactly one OPA instance. In order to reduce traffic and latency, deployed products querying OPA must use the local OPA provided on their respective Node.

Service Discovery

Furthermore, the Stackable Operator for OPA deploys a service discovery ConfigMap (see Implementing service discovery) to expose its service URL to other Stackable operators. These operators then are configured to use the service discovery ConfigMap to extract the required URL and configure their products and authorizers. Authorizers are plugins for products that allow authorization and access control.

The ConfigMap has one data entry that points to the OPA ClusterIP service:

apiVersion: v1
kind: ConfigMap
metadata:
  name: {clustername}
data:
  OPA: http://{clustername}.{rolegroup}.svc.cluster.local:8081/

Provide Rego rules

In order for OPA to make policy decisions, Rego rules must be bundled and supplied. The Stackable Operator for OPA has its own controller to bundle policies and provide them to OPA. Polices can be provided via ConfigMaps:

apiVersion: v1
kind: ConfigMap
metadata:
  name: simple-product-rego
  labels:
    opa.stackable.tech/bundle: "product"
data:
  product.rego: |
    package product
    allow {
        true
    }

The OPA bundle controller creates a bundle.tar.gz file locally, bundling the content of every ConfigMap labeled with opa.stackable.tech/bundle (the label value can be arbitrary). This bundle is read and activated by OPA.

You can query the allow rule provided in the example above via:

http://{clustername}.{rolegroup}.svc.cluster.local:8081/data/v1/product/allow

Consume the discovery ConfigMap

In order to configure another operator and its product to query OPA, the service discovery ConfigMap must be consumed. There are two ways that qualify as best practice, depending on the product you are configuring:

Configure OPA access in the operator

The operator-rs framework has a module called opa.rs that offers a predefined struct OpaConfig and several helper methods to extract the OPA URL from the service discovery ConfigMap.

#[derive(Clone, Debug, Default, Deserialize, JsonSchema, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct OpaConfig {
    pub config_map_name: String,
    pub package: Option<String>,
}

You can use that struct in the CRD of your operator to configure the OPA service discovery ConfigMap as well as the desired package name. The method full_document_url_from_config_map reads the service discovery ConfigMap provided by the Stackable operator for OPA and constructs the URL to configure the product or its authorizers (the plugins that talk to OPA).

Usually OPA authorizers are configured with the URL to the OPA REST API, authentication / security and other properties like caching. This differs from authorizer to authorizer.

Authorizers

The Stackable platform uses internal (written by us) and external authorizers to configure the products. Here are some examples that may help you if you need to write an authorizer on your own:

Internal

External