Policy Language and Syntax
You use the policy language to encode your business rules as policies and govern the resources in your cluster. The policy language provides a declarative rule syntax based on logical if/then expressions. The policy engine automatically enforces the rules on the realms you specify in your policies.
This section describes the policy language and syntax. Refer to the Policy Authoring Tutorial for a walkthrough of the policy language and authoring using an example policy.
For a quick introduction to the policy language, check out the following video:
Policy documents
Policies are stored in policy documents. Policy documents are text files that have the *.pol
file extension and conform to the policy language syntax. Policy documents contain an arbitrary number of policies.
Policy documents are stored in the root namespace of the cluster and have their own resource type (policydoc::/
). You can have an unlimited number of policy documents in your cluster. The size of each policy document is limited to 512 KB.
Policy elements
A policy is a block of text in a policy document. A policy is headed by a realm declaration and contains one or more rules that apply to resources in that realm.
A policy can include an arbitrary number of rules. The building blocks of rules are claims which implement your business logic. The policy engine enforces the rules on resources in the realm.
The following example highlights the policy structure.
The above policy checks if Tom has been authenticated, and if so grants Tom CRUD permissions on jobs in his sandboxed namespace.
Policy syntax
Policy rules include claims that implement if/then logical constructs. The antecedent claim is a boolean evaluation. If the antecedent claim is true or omitted, the policy engine asserts the consequent claims against all resources in the realm.
// REALM
<on> ResType::/Namespace[/Namespace...]::LocalName[/LocalName...]
{
// RULE 1
if ([issuer] = antecedent_claim <comparator> antecedent_value)
{
<consequent_claim> <consequent_value>
}
// RULE 2
if ([issuer] = antecedent_claim <comparator> antecedent_value)
{
<consequent_claim> <consequent_value>
<consequent_claim> <consequent_value>
}
// RULE N
if ([issuer] = antecedent_claim_a <comparator> antecedent_value_a <logical combinator> antecedent_claim_b <comparator> antecedent_value_b)
{
<consequent_claim> <consequent_value>
}
}
Realm declaration
The policy realm is a namespace that has three parts, each separated by double-colons (::
).
<on> ResType::/Namespace[/Namespace...]::LocalName[/LocalName...]
-
The ResourceType is a single token that is one of the supported resource types.
-
The Namespace can have multiple tokens separated by forward slashes. The namespace string is limited to 512 bytes.
-
The LocalName is the user-defined name of the resource. The resource name string is limited to 512 bytes.
For example, here is a realm declaration that includes the resource type, namespace, and resource name (local name):
on job::/sandbox/user::node-red-app
Namespaces form a hierarchy starting at root (/
). At a minimum the root namespace for the resource type (such as job::/
) must be declared for the realm to be valid. If the local name is left out, the realm matches everything in the namespace. In the context of policy realms, the FQN is used as a pattern matcher with support for wildcard and escape characters. See the FQN documentation for examples.
A resource type such as job
can have an arbitrary number of realms, for example: job::/sanbox/bob
, job::/dev
, job::/test
, job::/prod
, etc.
The realm keyword
on
is optional. For example, realm declarationson job::/dev
andjob::/dev
are equivalent.
Claim issuers
The building blocks of policy rules are claims, of which there are two types: antecedent and consequent.
A claim is a name-value triple in the following form:
[issuer]->[claim name] == [claim value]
The claim issuer is the asserter of the claim. The policy engine (ENGINE
) is the default claim issuer and implicitly applied. If the policy engine is the issuer, the issuer is omitted and only the claim type and value are specified for the claim. The policy engine is always the issuer of consequent claims. The policy engine may be the issuer of antecedent claims (questions), or it may be some sort of authority, such as the Auth Server or Google Auth.
For example:
-
If the Auth Server is the issuer, it is specified in the claim:
auth_server@apcera.me->name == "tom"
-
If the policy engine is the issuer, it is omitted from the claim:
role == developer
orpermit bind
Antecedent claims
The antecedent claim is a condition check. The issuer of an antecedent claim can be the policy engine, or it can be an authority such as the Auth Server or Google Auth.
The antecedent claim is optional. If omitted, the consequent claim(s) is issued by the policy engine as if the antecedent claim is true.
Antecedent claims support boolean operations.
Consequent claims
The consequent claim is a permission grant. The policy engine is the sole issuer of consequent claims. A valid policy rule must have at least one consequent claim. A rule can also have multiple consequent claims.
Note that consequent claim values are resource-specific.
Antecedent claim comparators
The antecedent <comparator>
is a boolean operator of the following type:
Comparator | Description |
---|---|
|
No operation |
== , equals |
Equals |
~= |
Like |
> |
Greater than |
>= |
Greater than or equals |
< |
Less than |
<= |
Less than or equals |
endsWith |
String that ends with the specified suffix |
beginsWith |
String that begins with the specified prefix |
fqnMatch |
Fully qualified name that matches the specified pattern that is the fqn value |
nameMatch |
See fqnMatch ; the name value is an alias for the fqn value |
before |
current time is before the specified time where time is in RFC822 format |
after |
current time is after the specified time where time is in RFC822 format |
Note that
nameMatch
is an alias forfqnMatch
. Although they are synonyms and match on the same thing, if you need to match on the FQN for a resource, you should usefqnMatch
to be consistent.
Boolean antecedent claims
Antecedent claims support the use of the logical combination operators AND (&&
) and OR (||
). If antecedent claims are anded, both must evaluate to true for the consequent claims to execute. If antecedent claims are related by an OR, this is equivalent to two rules, each rule with one of the antecedents and the same consequents.
The syntax for a boolean antecedent claim is as follows:
<antecedent> = (antecedent_type_1 <comparator> antecedent_value_1
<logical comb> antecedent_type_2 <comparator> antecedent_value_2)
See Boolean Antecedent Policy Examplee for guidance.
List of input and output claims
The policy language has built-in support for the following claim types and values.
NOTE: Refer to Granting Permissions for additional details on all claim output types and values.
Resource Type | Operation(s) | Input Claim(s) | Output claim type | Output claim value(s) | |
---|---|---|---|---|---|
audit | Read | nil | permit | read | |
auth | Default namespace | nil | defaultNamespace | user defined, such as /sandbox/user-name/ | |
Default namespace prefix | nil | defaultNamespacePrefix | user defined, such as /sandbox/developers/ | ||
Issue access token | nil | permit | issue | ||
cluster | Read, Update | nil | permit | read, update (update is currently a no operation claim) | |
gateway | Create service from gateway | service = service FQN | permit | use | |
Service parameter | nil | serviceParam | database, ipnet, ipnets, protocol, portrange, ip, persistence_provider (see Services > Options) | ||
Promote job | job = job FQN | permit | promote | ||
job | Service binding | service = service FQN | permit | bind | |
Job links | targetJob = FQN, sourceJob = FQN | permit | link | ||
Create, Read, Update, Delete | nil | permit | create, read, update, delete | ||
Start, Stop, SSH | nil | permit | start, stop, ssh | ||
Allowed Docker registries | nil | nil | docker.allow | URL(s) of allowed Docker registries separated by commas. Use * to allow all Docker registries. See Docker claim for jobs. | |
Allowed packages | package.allow | ||||
Default package | dependency = type.name | Default package | package.default | ||
Locked package | dependency = type.name | package.lock | |||
Promote to gateway | gateway = gateway FQN | permit | promote | ||
Map to route | route = route FQN | permit | map | ||
Join Network | nil | permit | join | ||
Hard scheduling tags | nil | nil | schedulingTag.hard | ||
Soft scheduling tags | nil | nil | schedulingTag.soft | ||
User defined job label. | nil | nil | label name=[value] | ||
Disable Semantic Pipeline | nil | sp.disable | service type, service FQN, and/or provider FQN | ||
Enable encryption at rest | nil | initial.params | encrypt | ||
network | Create, Read, Join, Delete | nil | permit | create, read, join, delete | |
package | Create, Read, Update, Delete, Use | nil | permit | create, read, update, delete, use | |
Hard staging scheduling tags | nil | nil | staging.schedulingTag.hard | ||
policy | Update Realm, Read Realm | ResType = resource type | permit | update, read | |
Create, Read, Update, Delete | nil | permit | create, read, update, delete | ||
principal | Create, Read, Update, Delete | nil | permit | create, read, update, delete | |
provider | Create, Read, Update, Delete | nil | permit | create, read, update, delete | |
quota | nil | nil | nil | See quota permissions for complete list. | |
route | Map to Job | job = job FQN | permit | map | |
semipiperule | Create, Read, Update, Delete | nil | permit | create, read, update, delete | |
service | Create, Read, Update, Delete, Use | nil | permit | create, read, update, delete, use | |
Job Binding | job = job FQN | permit | bind | ||
Create Service from Gateway | gateway = gateway FQN | permit | create | ||
Enable encryption at rest | nil | initial.params | encrypt | ||
stagpipe | Create, Read, Update, Delete, Use | nil | permit | create, read, update, delete, use |
In addition to the above claims, any claim to be referenced by other rules can be the name of any arbitrary string. The string claim type allows rules to be chained using whatever type identifiers you want, that is, any string set by the consequent. See the policy examples.
User authentication
Users are authenticated by the Auth Server (auth_server@apcera.me
) based on an assertion of identity from the identity provider, such as Google Auth. The antecedent claim type email
and its string value is used to identify the user to the Auth Server. Other identity providers are also supported.
If a valid identity is asserted, the policy engine signals the Auth Server to issue a token that is valid for a period specified by policy (the default is 24 hours). The name
claim type and value is a user-defined string that the Auth Server sets as the subject of the token.
When permitted, the Auth Server issues an access token to the requester that is good for a policy-configurable time limit (default is 24 hours). The name
value is assigned as the subject of the token, which then can be used as a condition check to apply policy.
Identity providers
Apcera supports the following identity providers:
Auth | Description |
---|---|
--basic |
Use the built-in provider for basic authentication. |
--google |
Use Google authentication. |
--ldap-basic |
Use LDAP authentication, including Active Directory. |
--keycloak |
Use Keycloak authentication. |
Claim types and values
Claim types and values for supported identity providers:
Basic
auth_server@apcera.me->authType = basicAuth
auth_server@apcera.me->name = [principalName from principal store]
See Using Basic Auth for details.
auth_server@apcera.me->authType = googleUserInfo
Google->name = [user’s full name]
Google->email = [user’s email]
Google->link = [URL of user’s profile page]
See Using Google Auth for details.
Keycloak
See Using Keycloak Auth for details.
LDAP
auth_server@apcera.me->authType = LDAPBasic
LDAP->name = [cn from directory]
LDAP->email = [email from directory]
LDAP->group = [group from directory]
See Using LDAP Auth for details.
Name value
Note the following about the name
value:
-
It is required and must be unique within the cluster.
-
It can be any ASCII character in the set A-Z, a-z, 0-9, “-”, “.”, “_”.
-
It must contain at least one supported character, except
.
by itself. -
It can be templated.
-
It is used to construct the initial namespace.
-
You can use an email address for the name value:
auth::/oauth2/http { if (Google->email == "apcera.hcos@gmail.com") { permit issue name "apcera.hcos@gmail.com"
In this case, to set the initial namespace, the system will truncate the value to
/sandbox/apcera.hcos
.
Use of quotes
For the policy language, quotes are not required for claim values without separators or special characters. However, it does not hurt to use quotes.
Thus, in the following example, for the question (antecedent) claim type and value (email == "tom@gmail.com"), the value for the email must be in quotes because it has special characters. But, the consequent (decision) claim values do not have to be in quotes because there are no separators or special characters. In the example we are using quotes for "tom" to illustrate that you can use them even though not required for this value.
auth::/oauth2/http {
if (Google->email == "tom@gmail.com")
{
permit issue
name "tom"
}
}
Role assignment
Roles can be created by the policy engine using the role
claim type and a user-defined string value. For example, default policy rolePermissions.pol creates an admin
role with all
permissions on each resource type (except auth::/
).
The role
is an arbitrary user-defined string. We use role
because that is the common terminology for that type of policy. However, you can use any string name to create a role, such as "team-x".
job::/dev {
if (team == "team-x")
{
permit all
}
}
Templating policy
You can template the name
variable and auto-populate its value. Typically policy templating is used for granting role membership to users.
To templatize policy, you surround the realm endpoint token and antecedent claim value in brackets ([...]
). For example:
job::/sandbox/[name] {
if (auth_server@apcera.me->name==[name]) {
role admin
}
}
With this example, each authenticated user is granted admin role permissions for jobs in his or her namespace.
The templated value is obtained from the realm and passed to the claim value. Thus, you cannot use policy templating if the realm endpoint is not templated, such as on a root realm like auth::/
.
See Templating Policy Examples for guidance.
Sealing permissions
You can seal a policy permission grant for a particular realm. Sealing a permisison is designed to prevent elevation of privileges.
To do this, you precede the policy rule with the !seal
keyword, claim type, and optionally, claim value. For example:
job::/{
!seal permit all
if (role == “admin”) {
permit all
}
}
The above policy seals permit all
at the root job level for any user assigned to the admin
role. The effect is that any deeper policy statements (such as on realm job::/sandbox/user
) cannot be set to permit all
. Note, however, that deeper statements could still set permit to permit read, write, create, ...
and so on.
See Sealing Policy Example for guidance.