Access Control Policy with OPA
Default Policy in Chronicle
The default policy in Chronicle defines the access control rules for
authorization decisions. It consists of the allow_transactions
and
common_rules
packages, which define the authorization rules for
transaction.
Default Policy Description
The default policy in Chronicle is defined using the
Rego policy language
of the Open Policy Agent (OPA). It consists of two main packages:
allow_transactions
and common_rules
.
allow_transactions
Package
The allow_transactions
package is responsible for defining authorization rules
related to transaction processing in Chronicle. It depends on the rules
specified in the common_rules
package.
The following rules are defined in the allow_transactions
package:
package allow_transactions
import data.common_rules
import future.keywords.in
import input
default allowed_users = false
allowed_users {
common_rules.allowed_users
}
default allow_defines = false
allow_defines {
common_rules.allow_defines
}
default deny_all = false
-
allowed_users
: This rule determines whether users are allowed to access resources or perform actions. It relies on theallowed_users
rule defined in thecommon_rules
package. -
allow_defines
: This rule specifies whether users are allowed to execute Chronicle'sdefine
Mutation and Submission operations. It also relies on theallow_defines
rule defined in thecommon_rules
package. -
deny_all
: This rule denies access to all resources or actions by default. It ensures that if no other rules match, access is denied.
common_rules
Package
The common_rules
package defines authorization rules that are common to
various parts of Chronicle's functionality.
The following rules are defined in the common_rules
package:
package common_rules
import future.keywords.in
import input
allowed := {"chronicle", "anonymous", "jwt"}
allowed_users {
input.type in allowed
}
allow_defines {
data.context.operation in ["Mutation", "Submission"]
startswith(data.context.state[0], "define")
}
-
allowed_users
: This rule determines whether users of specific types are allowed to access resources or perform actions. In the current implementation, users with the types"chronicle"
,"anonymous"
, and"jwt"
are allowed. -
allow_defines
: This rule specifies whether users can execute Chronicle'sdefine
Mutation and Submission operations. It checks the operation type and verifies if the state starts with the string"define"
.
Modifying the Default Policy
To modify the default policy in Chronicle, you can follow these steps:
-
Update the Rego policy files to define the desired access control rules.
-
Use the
opactl
command-line tool to load the updated policy bundle into the OPA Transaction Processor. -
Configure Chronicle's settings to match the policy by setting the appropriate Sawtooth settings entries.
Detailed instructions on modifying the default policy can be found in the Configuration section of this documentation.
OPA Standard
The OPA standard, or Open Policy Agent, is a policy engine that enables fine-grained control over authorization decisions within an application or infrastructure. It provides a declarative language, Rego, for expressing policies, and can be integrated with various applications and services via APIs. The OPA standard allows policies to be enforced at various stages of the application stack, and can be used to enforce compliance with regulations or to implement customized security policies, based on user identity, roles, and permissions.
OPA Execution in Chronicle
Chronicle integrates OPA execution at its GraphQL API request point and in its Transaction Processor to ensure access control and policy compliance. OPA verifies user permissions against policies that define which actions are allowed and denied to whom. Integrating OPA execution in the transaction processor ensures that only transactions that meet specific criteria are accepted and processed by the network.
Authorization Rules in OPA
Authorization rules in Open Policy Agent (OPA) determine whether a user or client is authorized to access a resource or perform an action.
As an example of how to define an authorization rule in OPA, suppose we have a REST API endpoint that returns a list of users but we want to restrict access to this endpoint only to users who have the "admin" role. We can define an OPA policy like this:
See here for the OPA documentation on the Rego policy language.
Here are two examples of Rego policies used in Chronicle's development:
The common_rules
package defines two authorization rules: allowed_users
and
allow_defines
. The former allows users who belong to the allowed
set to access
resources or perform actions, while the latter allows users to execute Chronicle
define
Mutation and Submission operations.
package common_rules
import future.keywords.in
import input
allowed := {"chronicle", "anonymous"}
allowed_users {
input.type in allowed
}
allow_defines {
data.context.operation in ["Mutation", "Submission"]
startswith(data.context.state[0], "define")
}
The allow_transactions
package uses the rules specified in common_rules
(above)
by default.
package allow_transactions
import data.common_rules
import future.keywords.in
import input
default allowed_users = false
allowed_users {
common_rules.allowed_users
}
default allow_defines = false
allow_defines {
common_rules.allow_defines
}
default deny_all = false
The discussion of authorization
tokens
mentions OAuth scope which is an important means by which an authorization
server can assert that the authenticated user is authorized to perform
specific actions within Chronicle. If, for example, in the above rules, we
wish to allow defines to only those users whose role grants them the
write:instance
scope, we may note the granted scopes in a variable:
then, among the rules for allow_defines
, include,
Additionally, for this scenario, the allowed input.type
values should
include "jwt"
, corresponding to users whose identity and access rights are
demonstrated by their presentation of a token issued by an authorization
server. Users' scopes are typically defined in that server's settings for
role-based access control.
opa-tp
The opa-tp command-line interface (CLI) is used to interact with the Chronicle OPA-TP (Transaction Processor) for Public Key Infrastructure (PKI) and OPA rule storage.
The available options are:
-
--connect
(-C
): Sets the sawtooth validator address. This option takes a URL as its argument. -
--completions
: Generate shell completions for the opa-tp command. This option takes a shell type as its argument (either bash, zsh, or fish). -
--instrument
: Instrument the opa-tp process using theRUST_LOG
environment variable. This option takes a URL as its argument. -
--console-logging
: Enable console logging foropa-tp
using theRUST_LOG
environment variable. This option takes a string argument, which can be set topretty
orjson
to enable pretty-printed or JSON-formatted logging output, respectively.
OpaTransactionHandler
holds the configuration for the handler, including the family
name, family versions, and namespaces. Its apply
method works as follows.
The verify_signed_operation
function takes a submission
and optional root_keys
argument and verifies that the submission
is valid. If the payload is a bootstrap
root operation, the function returns Ok
. If the payload is a signed operation,
the function checks that there are root keys available and that the signature
matches the public key associated with the operation. If the signature is valid and
the verifying key matches the current key in the root keys, the function returns
Ok
. Otherwise, the function returns an error.
The apply_signed_operation
function takes a payload
, request
, and context
and applies the signed operation or bootstrap root operation. If the payload is a
bootstrap root operation, the function checks that the OPA TP has not already been
bootstrapped and sets the root key. If the payload is a signed operation, the
function calls apply_signed_operation_payload
with the operation payload.
The apply_signed_operation_payload
function takes a request
, payload
, and context
and applies the operation payload. If the payload is a register key operation, the
function checks that the key ID is not "root", the key is not already registered,
and sets the key. If the payload is not a register key operation, the function
returns an error.
opactl
opactl
allows users to submit transactions to the Sawtooth network that specify
operations on OPA (Open Policy Agent) policies stored on the ledger. The tool provides
various commands for querying, listing, and modifying OPA policy states on the Sawtooth
network.
opactl
provides a command-line interface for managing keys and transactions in
the OPA Transaction Processor. It has the following commands:
bootstrap
The bootstrap
command initializes the OPA Transaction Processor with a root key.
bootstrap
's arguments include:
--batcher-key
(-t
): An optional argument that specifies the path to the PEM-encoded private key to be used for signing a transaction. If not specified, an ephemeral key will be generated.
bootstrap
Example
generate
The generate
command generates a new private key and writes it to stdout.
generate
's arguments include:
--output
(-o
): An optional argument that specifies the path to write the private key. If not specified, the key is written to stdout.
generate
Example
rotate-root
The rotate-root
command rotates the root key for the OPA Transaction Processor.
-
--new-root-key
(-n
): A required argument that specifies the name of the key to be registered as the new root key. -
--batcher-key
(-t
): An optional argument that specifies the name of the key to be used for signing the transaction.
rotate-root
Example
register-key
The register-key
command registers a new non-root key with the OPA transaction
processor.
register-key
's arguments include:
-
--new-key
(-k
): A required argument that specifies the path to the PEM-encoded private key to be registered. -
--id
(-i
): A required argument that specifies the name to associate with the new key. -
--overwrite
(-o
): An optional flag allowing re-registration of a non-root key.
register-key
Example
rotate-key
The rotate-key
command rotates the key with the specified ID for the OPA
Transaction Processor.
rotate-key
's arguments include:
-
--current-key
(-c
): A required argument that specifies the path to the PEM-encoded private key currently registered for the given ID. -
--root-key
(-r
): A required argument that specifies the path to the PEM-encoded private key currently registered as the root key. -
--new-key
(-n
): A required argument that specifies the path to the PEM-encoded private key to be registered for the given ID. -
--id
(-i
): A required argument that specifies the ID of the key to be rotated. -
--batcher-key
(-t
): An optional argument that specifies the path to the PEM-encoded private key to be used for signing the transaction. If not specified, an ephemeral key will be generated.
rotate-key
Example
set-policy
Sets a policy with a given ID, using a specified policy compiled to .bundle.tar.gz
and requiring access to the root private key. The command takes the following
arguments:
-
--id
(-i
): A required argument that specifies the ID of the new policy. -
--policy
(-p
): A required argument that specifies the path or url of the policy bundle. -
--root-key
(-k
): A required argument that specifies the path of a PEM-encoded private key that has access to the root. -
--batcher-key
(-t
): An optional argument that specifies the path of a PEM-encoded private key for the batcher.
set-policy
Example
Or
opactl set-policy -i my_policy \
-p file://$PWD/relative/path/to/policy.bundle.tar.gz \
-k /path/to/private/key
Or
opactl set-policy -i my_policy \
-p https://server.address/path/to/policy.bundle.tar.gz \
-k /path/to/private/key
get-key
Gets the currently registered public key, with an option to specify the key ID and an option to write the key to a file. The command takes the following arguments:
-
--id
(-i
): An optional argument that specifies the ID of the key. If not specified, the root key is returned. -
--output
(-o
): An optional argument that specifies the path to write the key to. If not specified, the key is written to stdout.
get-key
Example
get-policy
Gets the currently registered policy, with an option to specify the policy ID and an option to write the policy to a file. The command takes the following arguments:
-
--id
(-i
): A required argument that specifies the ID of the policy. If not specified, the default policy is returned. -
--output
(-o
): An optional argument that specifies the path where the policy will be written.
get-policy
Example
Configuring Chronicle to use OPA
By default, an embedded policy is used that allows all graphql operations and transactions. To use a custom policy you must:
Bundle your policy to target WASM
Build your custom Rego files into a bundle. For example, using the opa
command-line tool that can be downloaded from the
Open Policy Agent project:
Listed entry points (after -e
) must be defined in your custom policy.
Load a policy bundle using opactl
The policy id here must match the rego.
Configure Sawtooth with settings that match the policy
2 settings entries are required, you should use
sawset with the
following 2 settings keys, using the policy name and entrypoint you have defined
and previously uploaded to the opa-tp
.
For the example rego we are using, these entries will be:
chronicle.opa.policy_name=allow_transactions
chronicle.opa.entrypoint=allow_transactions.allowed_users
Once this has been set, you should restart Chronicle for them to be applied, the transaction processor should not need to be restarted.
Update the policy bundle already in effect
Once a policy is set, one would not expect that it would often need changing.
When a new policy is required, repeating the above operations with
opactl set-policy
and/or sawset
, then restarting Chronicle, will cause
your new policy to take effect. It is advised to always restart Chronicle
after the policy is changed.
Load OPA Policy Bundle from a URL or File Path
To configure Chronicle to use an OPA policy bundle loaded from a URL or a file path, follow these steps:
Start Chronicle with the --opa-bundle-address
option, providing the URL or file
path as the argument value.
For example:
or
Make sure to replace https://example.com/policy-bundle.tar.gz
with the actual
URL or /path/to/policy-bundle.tar.gz
with the actual file path of the OPA policy
bundle you want to load.
Note that when using --opa-bundle-address
option, the --opa-policy-name
and
--opa-policy-entrypoint
options must be provided.
For example: