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.

screenshot

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 declarations on job::/dev and job::/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 or permit 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 for fqnMatch. Although they are synonyms and match on the same thing, if you need to match on the FQN for a resource, you should use fqnMatch 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  
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  
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.
--kerb Use Kerberos authentication.
--crowd Use Atlassian Crowd 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.

Crowd

auth_server@apcera.me->authType = CrowdAuth
Crowd->name = [name field from Crowd]
Crowd->email = [email field from Crowd]

See Using Crowd Auth for details.

Google

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.

Kerberos

auth_server@apcera.me->authType = KerberosV5
Kerberos->principal = [user principal name, typ. username@realm]
[realm]->principal = [user principal name, typ. username@realm]

See Using Kerberos 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.