Protecting applications and APIs in ACP using Open Policy Agent
Instructions for developers on how to use Rego policies to protect APIs and applications in ACP.
About Rego policies in ACP
ACP allows you to create policies in two ways:
- Using the Cloudentity Policy Editor
- Using Rego policies
The Open Policy Agent (OPA) is an open source, general-purpose policy engine that enables unified, context-aware policy enforcement across the entire stack.
OPA gives you a high-level declarative language called Rego to author and enforce policies across your stack. When you query OPA for a policy decision, OPA evaluates the rules and data (which you provide) to produce an answer. The policy decision is sent back as the result of the query.
ACP contains an embedded Rego editor which also includes code samples to help you get started. When creating policies in ACP, keep in mind to take the policy type into account.
Policy type | Description |
---|---|
User | User policies validate requests involving user interaction. They can be assigned on a workspace level (Token issue policy), application level (User policy) and service scope level (Consent grant policy). |
Developer | Developer policies validate client registration and developer subscriptions to a given scope. They can be assigned on a workspace level (Client registration policy) and service scope level (Client assignment policy). |
Machine to machine | These policies validate a token request coming from a client using the Client credentials OAuth 2.0 flow. They can be assigned on a workspace level (Machine token policy) and service scope level (Machine to Machine policy). |
Dynamic Client Registration | DCR policies are used to validate Dynamic Client Registration requests. |
API Request | An API policy validates requests coming to APIs protected by a gateway bound to ACP. |
When ready, the policy can be assigned to one of its designated execution points.
Prerequisites
-
Access to an ACP tenant
-
Understanding of the Rego syntax
Create a policy
The video below shows how to create a policy and run it in test mode.
-
In your workspace, select Policies from the sidebar.
-
In the Policies view, select CREATE POLICY.
-
In the Create Policy popup window
-
Select the Policy type from the dropdown menu.
-
Specify the Policy name.
-
Select REGO as the Policy language.
-
Select Create.
Result
The OPA policy editor opens.
-
-
To define your policy, enter the policy code in the OPA language into the editor. Check the below request templates for help - they are also available in the right-hand policy menu.
When ready, Save your policy. You can now assign it to a valid execution point (see the Policy Types table above).
ACP request schema
The following schema is valid for all requests coming from ACP. For that reason, it is also used in the policy test mode. Note the three top-level objects:
authn_ctx
contains the authentication context claims, including scopes.contexts
contains dynamic scopes incontexts.scopes.users.*
.request
contains data specific to the HTTP request itself.
ACP would typically send input resembling the one below to the policy engine:
{
"authn_ctx": {
"scp": [
"scope_name"
],
"sub": "joe",
"groups": [
"group_name"
],
"email": "testjoe@cloudentity.com",
"email_verified": "testjoe@cloudentity.com",
"phone_number": "+1-555-6616-899",
"phone_number_verified": "+1-555-6616-899",
"address": {
"formatted": "",
"street_address": "1463 Perry Street",
"locality": "Dayton",
"region": "Kentucky",
"country": "US",
"postal_code": "41074"
},
"name": "Joe Test",
"given_name": "Joe",
"middle_name": "",
"family_name": "Test",
"nickname": "joe",
"preferred_username": "testjoe",
"profile": "",
"picture": "",
"website": "",
"gender": "male",
"birthdate": "1960-10-09",
"zoneinfo": "",
"locale": "",
"updated_at": ""
},
"contexts": {
"scopes": {
"users.*": [
{
"params": [
"joe"
],
"requested_name": "users.joe"
}
]
}
},
"request": {
"headers": {
"Content-Type": [
"application/json"
],
"X-Custom-Header": [
"BOT_DETECTED"
]
},
"method": "POST",
"path_params": {
"users": "admins"
},
"query_params": {
"limit": [
"1000"
],
"offset": [
"100"
]
},
"path": "/doawesomethings"
}
}
Your policies can verify all data passed in the above schema and validate requests based on it. Check the policy tips below and start writing!
Scope check policy
To write a policy checking for a scope in the request, you can use the following template:
package acp.authz
default allow = false
scope := "sample_service:write"
allow {
input.authn_ctx.scp[_] == scope
}
This policy validates the request when the required scope ("sample_service:write"
) is found in the
authentication context (input.authn_ctx.scp[_]
).
Dynamic scope check policy
To write a policy checking for a dynamic scope in the request, you can use the following template:
package acp.authz
default allow = false
allow {
input.scopes["users.*"][_].params[0] == input.authn_ctx.sub
}
This policy validates the request when the required value is found in the input.scopes
object,
where dynamic
scopes are stored.
HTTP request check policy
To write a policy checking the HTTP request parameters, you can use the following template:
package acp.authz
default allow = false
allow {
input.request.method == "POST"
input.request.headers["X-Custom-Header"][_] == "REGULAR_USER"
}
This policy validates the request only for a POST request containing a specific header.
HTTP header names format
REGO policies by their definition are case-sensitive when matching HTTP header names, but ACP authorizers follow the RFC-2616 specification which states that header names are case-insensitive. To allow authorizers to correctly validate REGO policies, header names are normalized to follow the canonical format.
Canonicalization converts the first letter and any letter following the hyphen to upper case and the rest of the letters are converted to lower case.
It means that if a request is to be validated and contains a header like, for example,
x-custom-header
, before the header is validated, the header is converted to follow the canonical formatX-Custom-Header
.As the policy check is case sensitive for REGO policies, your REGO policy that checks request headers must have the header in the canonical format as you can see in the HTTP request check policy example above.
MFA enforcement policy
To write a policy checking the MFA validation status of the user, you can use the following template:
package acp.authz
default allow = false
allow {
input.login.verified_recovery_methods[_] = "mfa"
}
recovery = ["mfa"]
This policy validates the request only if the user has completed MFA. Otherwise, the user is prompted for an OTP code in accordance with the tenant’s MFA setup.
HTTP call status check policy
To write a policy executing an HTTP call and checking the status, you can use the following template:
package acp.authz
default allow = false
allow {
response := http.send({
"method" : "GET",
"url": "https://docs.authorization.cloudentity.com"
})
response.status_code == 200
}
This policy validates the request only if the request returns a given status (200
in the above policy).
Group membership check policy
To write a policy checking for user’s group membership, you can use the following template:
package acp.authz
default allow = false
group := "admins"
allow {
input.authn_ctx.groups[_] == group
}
This policy validates the request only if the admins
value is found in the authn_ctx.group
object inside the
authentication context (i.e. the user is an admin).
Secret check policy
You can retrieve a secret value for comparison via Rego policy. The below policy compares the
secret value from SECRET_NAME
against
the name
parameter passed in the authentication context:
package acp.authz
default allow = false
allow {
input.secrets.SECRET_NAME == input.authn_ctx.name
}
This policy validates the request only if the value of a secret called SECRET_NAME
matches the
value of the name
attribute from the authentication context.
Header injection for Istio policies
Note
The technique described here works for the Istio authorizer only.
When a policy for the Istio authorizer is resolved, all globally defined policy variables are injected as headers. Such a policy can only be assigned to APIs behind the Istio gateway bound to ACP, therefore it must always have the API request type. Considering we have the following policy:
package acp.authz
default allow = false
subject := input.authn_ctx.sub
expiration := input.authn_ctx.exp
issuer := input.authn_ctx.iss
scopes := input.authn_ctx.scp
tenantid := input.authn_ctx.tid
allow {
true
}
Upon policy validation, the authentication context values defined as global variables (outside of
the allow
document) are extracted and injected as headers in
the request received by the target service (the values below are encoded):
X-Output-Issuer: Imh0dHBzOi8vYWNwLmFjcC1zeXN0ZW06ODQ0My9kZWZhdWx0L2RlZmF1bHQi
X-Output-Expiration: MTYzNTk2OTQ2OA==
X-Output-Tenantid: ImRlZmF1bHQi
X-Output-Scopes: WyJlbWFpbCIsImludHJvc3BlY3RfdG9rZW5zIiwibGlzdF9jbGllbnRzX3dpdGhfYWNjZXNzIiwibWFuYWdlX2NvbnNlbnRzIiwib2ZmbGluZV9hY2Nlc3MiLCJvcGVuaWQiLCJwcm9maWxlIiwicmV2b2tlX2NsaWVudF9hY2Nlc3MiLCJyZXZva2VfdG9rZW5zIiwidmlld19jb25zZW50cyJd
X-Output-Subject: InVzZXIi
The Istio sidecar configuration and the default ACP headers (X-Output-Allow
,
X-Auth-Ctx
) are injected as well.
Embedded policies
For REGO policies that are embedded within a Cloudentity policy, if the output contains the same keys, it is merged and the keys are overwritten. The key is set to the key of the last resolved REGO policy.
For example, in your Cloudentity policy there are two embedded REGO policies, A and B. The policy A has headers X and Y, and the B policy has headers Y and Z. The Y header is common for both policies. It’s value is set to the value of Y header of the B policy as B is the last REGO policy embedded in the Cloudentity policy. Both the X and the Z headers remain the same.
Related articles
Having defined a policy, it’s time to assign it to an execution point and test it. Check the following resources for help:
-
Configuring scopes for your new policy
-
Applying your policy to APIs (see Apply a sample policy in Protecting APIs with MicroPerimeter™ Custom Authorizer on AWS API Gateway.