Skip to main content

Overview

Styra stacks allow you to define a uniform set of rules for multiple systems. These rules are not specified repeatedly for one system at a time. In addition, stack rules are evaluated with higher priority than system rules, so that the stack rules can be used to implement organizational supervision and enforcement.

A Styra system includes a collection of rules that manage a single software system. You can connect your cluster to the Styra control plane using styra-das-id.styra.com or by following the system’s installation instructions. Now, any rule that you define will be distributed to the connected cluster, where the rules will be monitored and enforced by Open Policy Agent (OPA).

A Styra stack also serves as a collection of rules. However, instead of controlling one target directly, a Stack oversees multiple systems that share a specific type (example: Kubernetes), a common function (example: production) or contract (example: PCI compliance). When you define a stack rule, it applies to all the systems the stack manages. The rule is synchronized with all corresponding OPA instances, and it is monitored or enforced.

note
  1. Custom systems and Custom policy types do not support stacks.

  2. Notifications can be generated by a notification policy from a Kubernetes system or Kubernetes stack. For more information, see the Notifications page.

Concepts

This section covers some concepts that are important to understand when you use Stacks.

Composability and Conflict Resolution

Because of the dynamic and versatile way that labels and selectors interact, more than one stack can control the same system. As discussed earlier, stack rules preempt system rules. For example, denying all requests that originate outside of a trusted network.

With multiple decision makers, conflicts can arise so decisions have evolved from being reason strings to being structured objects. This includes the decision itself (example: Allowed or Denied), along with metadata about the decision (example: a message explaining the rationale behind the decision).

The following shows an example of a rule implementation before stacks.

deny[reason] {
reason := "Because I said so"
}

The following shows an example of a rule implementation enhanced with a new rule name.

enforce[decision] {
decision := {
"allowed": false,
"message": "Because I said so"
}
}

The outcome is the same, but more information about the decision semantics is represented in the above example. This structure is used to enhance the decision capabilities.

You can alter the decision’s behavior to explicitly allow a condition.

Example: Allow all requests from a list of trusted superusers.

enforce[decision] {
decision := {
"allowed": true,
"message": "Because I said so",
"priority": "maximum"
}
}

When conflicts arise between decisions, you must use the following metadata to choose the decision with the highest priority. The closer a decision is to the top of the following list, the higher its precedence.

  1. Stack:
    1. not allowed and priority == "maximum". For example, untrusted networks.
    2. allowed and priority == "maximum". For example, superusers
    3. not allowed
    4. allowed
  2. System:
    1. not allowed and priority == "maximum"
    2. allowed and priority == "maximum"
    3. not allowed
    4. allowed

In other words, stack decisions are higher priority than system decisions. For example, "priority": "maximum" decisions take precedence over decisions with no priority. If two decisions are equal, then not allowed supersedes allow.

Decision Masking

Decision masking allows you to remove information from each decision before it gets logged.

The following example shows a policy that instructs OPA to remove all data from secrets before logging the decision.

package system.log

mask["/input/request/object/data"] {
input.input.request.kind.kind == "Secret"
}

mask["/input/request/oldObject/data"] {
input.input.request.kind.kind == "Secret"
}

mask["/input/request/object/metadata/annotations"] {
input.input.request.kind.kind == "Secret"
}

mask["/input/request/oldObject/metadata/annotations"] {
input.input.request.kind.kind == "Secret"
}

:::infp Decision masking is supported by Kubernetes and Envoy stacks only. :::

For more information on decision masking, see the masking sensitive data section.

Selector Semantics

The simplest selector is a label. For example, a key-value pair, such as "environment": {"production"}). If required, selectors have additional expressivity.

info

Each selector key corresponds to a list of values, instead of a single string.

Now, you can match more than one value per key (example: "environment": {"live", "production"}), or if you need a boolean key, then you can match no value at all (example: "pci-compliant": set()).

The values are glob-matched. You can use special glob characters such as ? (any single character) and * (zero or more characters) to match substrings between separator characters (., _, and -). For more information about glob matching, see OPA documentation.

Feature Flags

Stack rules can make exceptions for a specific system (or number of systems) through the system’s Features module. This module allows you to define rich data that a stack can use in its rule implementations.

The following shows an example of a definition in Features module.

approved_proxies := {
"https://skunkworks.cicd.co"
}

To use the definition of a Features module in a stack rule, look up a system’s features by using data.context.system_id:

package admission_control

features := data.metadata[data.context.system_id].features

approved_proxies := {
"https://us-cent-proxy.cicd.co",
"https://us-east-proxy.cicd.co",
"https://us-west-proxy.cicd.co"
}

enforce[decision] {
# title: Restrict Proxies

has_prohibited_proxy

decision := {
"allowed": false,
"message": sprintf("Proxy %v isn’t approved", [proxy])
}
}

has_prohibited_proxy {
not features.approved_proxies == features.approved_proxies
not approved_proxies[input.proxy]
}

has_prohibited_proxy {
not features.approved_proxies[input.proxy]
not approved_proxies[input.proxy]
}

Any system that defines features.approved_proxies is allowed to use the corresponding proxy servers, in addition to those usually allowed by the stack.

Access Control

Only an administrator for the styra-das-id.styra.com workspace can modify the Labels and Features modules. An user can be assigned as a system owner, only if the user is capable of managing a system without circumventing the stacks. A system owner has full control over the system, but is not allowed to modify labels or features.

To add user permissions for Alice (an existing user) and Sally (a new user) on the system level or stack level, do the following:

  1. Click on SYSTEMS or STACKS >> Your System or Stack >> Access Control >> Permissions pane.

  2. In the Permissions pane, click the (+) sign, and select Add user permissions… button from the drop down list.

  3. In your system > Add user permissions or stack > Add user permissions dialog, do the following:

    • Users: Select or enter existing users or you can enter new user emails. The existing user alice@hooli.com appears in the drop down list and once selected, her name tag is added in the field and is highlighted in gray color. When the new user sally@hooli.com is entered, her name tag is added in the field and is highlighted in yellow color (indicating Sally is a new user). When Sally logins in through SSO, sally@hooli.com becomes an existing user, appears in the Users drop down list, and Sally's name tag is highlighted.

    • Roles (required): Select or enter a role. For example, enter SystemOwner.

  4. Click the Add permissions button.

    important

    In a future release, Styra will allow permissions on specific packages.

Now, you have added the user permissions for your system or stack.