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 <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.
-
Custom systems and Custom policy types do not support stacks.
-
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.
- Stack:
not allowed
andpriority == "maximum"
. For example, untrusted networks.allowed
andpriority == "maximum"
. For example, superusersnot allowed
allowed
- System:
not allowed
andpriority == "maximum"
allowed
andpriority == "maximum"
not allowed
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 by OPA in DAS and can be defined at the stack level in the stacks.<stack-id>.system.log
package, where <stack-id>
is the stack's ID displayed in the stack settings.
The following example shows the default decision masking policy added for Kubernetes stack types. This policy instructs OPA to remove remove all data for Kubernetes Secrets from requests before logging the decision.
package stacks.<stack-id>.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"
}
Additional information on decision masking in DAS can be found in the Decision Logs - Decision Masking documentation.
Selector Semantics
The simplest selector is a label. For example, a key-value pair, such as "environment": {"production"}
). If required, selectors have additional expressivity.
Each selector key corresponds to a list of values, instead of a single string.
Now, you can match more than one value per key (e.g., "environment": {"live", "production"}
), or, if you need a boolean key, you can match no value at all (e.g., "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 the OPA Glob documentation.
Feature Flags
This section applies only to Kubernetes stack types.
Stack rules can make exceptions for a specific system (or number of systems) through the stack'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 the 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 <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:
-
Click on SYSTEMS or STACKS >> Your System or Stack >> Access Control >> Permissions pane.
-
In the Permissions pane, click the (+) sign, and select Add user permissions… button from the drop down list.
-
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 usersally@hooli.com
is entered, her name tag is added in the field and is highlighted in yellow color (indicatingSally
is a new user). WhenSally
logins in through SSO,sally@hooli.com
becomes an existing user, appears in the Users drop down list, andSally's
name tag is highlighted. -
Roles (required): Select or enter a role. For example, enter SystemOwner.
-
-
Click the Add permissions button.
In a future release, Styra will allow permissions on specific packages.
Now, you have added the user permissions for your system or stack.