Pomerium Policy Language
Pomerium Policy Language (PPL) is a yaml-based notation for creating easy and flexible authorization policies. This document covers the usage of PPL and provides several example policies.
PPL allows administrators to express authorization policy in a high-level, declarative language that promotes safe, performant, fine-grained controls.
See the Policy setting page to learn how to apply a PPL policy to a route.
At a Glance
Each PPL policy has at the top level a set of allow
or deny
actions, with a list of logical operators, criteria, matchers, and values underneath. For example:
allow:
and:
- domain:
is: example.com
deny:
or:
- email:
is: user2@example.com
- email:
is: user3@example.com
This policy grants access only if the domain portion of a user's email address matches the specified value, example.com
.
It will deny access to users with a user2@example.com
or user3@example.com
email address.
Rules
A PPL document is either an object or an array of objects. The object represents a rule where the action is the key and the value is an object containing the logical operators.
Actions
Only two actions are supported: allow
and deny
. deny
takes precedence over allow
. More precisely: a user will have access to a route if at least one allow
rule matches and no deny
rules match.
Logical Operators
A logical operator combines multiple criteria together for the evaluation of a rule. There are 4 logical operators: and
, or
, not
and nor
.
More on Logical Operators
Given the following example with OPERATOR
replaced:
allow:
OPERATOR:
- domain:
is: example.com
- claim/groups: admin
If and
is used, the user will have access if their email address ends in example.com
and they are a member of the admin group. (A ∧ B)
If or
is used, the user will have access if their email address ends in example.com
or they are a member of the admin group. (A ∨ B)
If not
is used, the user will have access if their email address does not end in example.com
and they are not a member of the admin
group. (¬A ∧ ¬B) = ¬(A ∨ B) (This operation is traditionally called NOR in the Boolean algebra.)
If nor
is used, the user will have access if their email address does not end in example.com
or they are not a member of the admin group. (¬A ∨ ¬B) = ¬(A ∧ B) (This operation is traditionally called NAND in the Boolean algebra.)
Multiple Operators in a Rule
You can add multiple operators under the same rule, and it would be valid PPL.
For example, this policy would grant access to anyone with a family_name: Smith
claim, or users with email addresses ending in domain1
or domain2
:
allow:
and:
- claim/family_name: Smith
or:
- domain:
is: domain1.com
- domain:
is: domain2.com
However, you could write an equivalent policy with multiple allow
blocks:
- allow:
and:
- claim/family_name: Smith
- allow:
or:
- domain:
is: domain1.com
- domain:
is: domain2.com
Although these policies are equally effective, we recommend using just one operator per rule.
Criteria
Criteria in PPL are represented as an object where the key is the name and optional sub-path of the criterion, and the value changes depending on which criterion is used. A sub-path is indicated with a /
in the name:
allow:
and:
- claim/family_name: Smith
deny:
not:
- http_method:
is: GET
Supported PPL Criteria
Below is an exhaustive list of PPL criteria.
Entries marked with *
denote criteria that are only available in the Enterprise Console PPL builder. All other entries are available in both Pomerium Core and Pomerium Enterprise.
Criterion Name | Data Format | Description |
---|---|---|
accept | Anything. Typically true . | Always returns true, thus always allowing access. Equivalent to the allow_public_unauthenticated_access option. |
authenticated_user | Anything. Typically true . | Always returns true for logged-in users. Equivalent to the allow_any_authenticated_user option. |
claim | Anything. Typically a string. | Returns true if a token claim matches the supplied value exactly. The claim to check is determined via the sub-path. For example, claim/family_name: Smith matches if the user's family_name claim is Smith . |
client_certificate | Certificate matcher | Returns true if a client presented a TLS certificate matching the provided condition. |
cors_preflight | Anything. Typically true . | Returns true if the incoming request uses the OPTIONS method and has both the Access-Control-Request-Method and Origin headers. Used to allow CORS pre-flight requests. |
* date | Date Matcher | Returns true if the time of the request matches the constraints. |
* day_of_week | Day of Week Matcher | Returns true if the day of the request matches the constraints. |
device | Device matcher | Returns true if the incoming request includes a valid device ID or type. |
domain | String Matcher | Returns true if the logged-in user's email address domain (the part after @ ) matches the given value. |
email | String Matcher | Returns true if the logged-in user's email address matches the given value. |
* groups | String List Matcher | Returns true if a user's group ID matches the supplied value exactly. groups data is only available after a successful directory sync. See Identity Providers for vendor-specific directory sync steps. |
http_method | String Matcher | Returns true if the HTTP method matches the given value. |
http_path | String Matcher | Returns true if the HTTP path matches the given value. |
invalid_client_certificate | Anything. Typically true . | Returns true if the incoming request does not have a trusted client certificate. By default, a deny rule using this criterion is added to all Pomerium policies when downstream mTLS is configured (but this default can be changed using the Enforcement Mode setting.) |
pomerium_routes | Anything. Typically true . | Returns true if the incoming request is for the special .pomerium routes. A default allow rule using this criterion is added to all Pomerium policies. |
* record | variable | Allows policies to be extended using data from external data sources. See Record Matcher for more information. |
reject | Anything. Typically true . | Always returns false. The opposite of accept . |
* time_of_day | Time of Day Matcher | Returns true if the time of the request (for the current day) matches the constraints. |
user | String Matcher | Returns true if the logged-in user's ID matches the supplied value. (The actual value of the user ID claim depends on how the identity provider sets this value.) |
Entries marked with *
denote criteria that are only available in the Enterprise Console PPL builder. All other entries are available in both Pomerium Core and Pomerium Enterprise.
Matchers
Certificate Matcher
The certificate matcher is a beta feature. The syntax and capabilities are subject to change in a future Pomerium release.
A certificate matcher can be used to allow or deny certain TLS certificates. This matcher is represented as an object that may have the following key/value entries:
Key Name | Value Type | Description |
---|---|---|
fingerprint | string or array of strings | The certificate's SHA-256 fingerprint must match one of the provided values. |
san_dns | String Matcher | The certificate must contain a Subject Alternative Name with a DNS name satisfying the provided condition. |
san_email | String Matcher | The certificate must contain a Subject Alternative Name with an email address satisfying the provided condition. |
san_uri | String Matcher | The certificate must contain a Subject Alternative Name with a URI satisfying the provided condition. |
spki_hash | string or array of strings | The base64-encoded SHA-256 hash of the certificate's Subject Public Key Info must match one of the provided values. |
Notes on certificate fingerprint
The certificate fingerprint is a SHA-256 hash of the entire certificate. You can compute a certificate's fingerprint using the openssl
command:
$ openssl x509 -in path/to/certificate.pem -noout -fingerprint -sha256
sha256 Fingerprint=17:85:92:73:E8:A9:80:63:1D:36:7B:2D:5A:6A:66:35:41:2B:0F:22:83:5F:69:E4:7B:3F:65:62:45:46:A7:04
This is the "long" form of a certificate fingerprint (32 uppercase hexadecimal bytes separated by colons). A "short" form is also acceptable (32 lowercase hexadecimal bytes, without colons). You can also compute this form using the openssl
command:
$ openssl x509 -in path/to/certificate.pem -outform DER | openssl dgst -sha256
SHA2-256(stdin)= 17859273e8a980631d367b2d5a6a6635412b0f22835f69e47b3f65624546a704
Notes on SPKI hash
The SPKI hash is a base64-encoded SHA-256 hash of the Subject Public Key Info section of the certificate. You can compute a certificate's SPKI hash using a sequence of openssl
commands:
$ openssl x509 -in path/to/certificate.pem -noout -pubkey \
| openssl pkey -pubin -outform DER \
| openssl dgst -sha256 -binary \
| openssl enc -base64
FsDbM0rUYIiL3V339eIKqiz6HPSB+Pz2WeAWhqlqh8U=
The advantage of using the SPKI hash rather than the certificate fingerprint is that the SPKI hash may be stable across certificate renewals (if the public/private key pair is the same).
For example, to allow only certificates containing a Subject Alternative Name with an email address ending in @yourdomain.com
(while also requiring the user to sign in with the configured identity provider):
allow:
and:
- authenticated_user: true
- client_certificate:
san_email:
ends_with: '@yourdomain.com'
Or, to allow only one specific trusted certificate (again, while still requiring the user to sign in with the configured identity provider):
allow:
and:
- authenticated_user: true
- client_certificate:
fingerprint: '17859273e8a980631d367b2d5a6a6635412b0f22835f69e47b3f65624546a704'
Or, to enforce an allowlist of trusted certificate key pairs:
allow:
and:
- authenticated_user: true
- client_certificate:
spki_hash:
- 'FsDbM0rUYIiL3V339eIKqiz6HPSB+Pz2WeAWhqlqh8U='
- 'pbdFxDXEtpabt3MZiik71farokMg6ZIn2azvsdXtZYA='
- 'WTu9ETBS1/v/ll20erWcf+TAj7rzrJix/oCUv5GMPtg='
...
Day of Week Matcher
The day of week matcher is a string. The string can either be *
, a comma-separated list of days, or a dash-separated list of days.
-
*
matches all days. -
,
matches either day (e.g.mon,wed,fri
). -
-
matches a range of days. (e.g.mon-fri
). Days can be specified as English full day names, or as 3 character abbreviations. For example:allow:
and:
- day_of_week: tue-fri
Date Matcher
The date matcher is an object with operators as keys. It supports the following operators: after
and before
. The values are ISO-8601 date strings. after
means that the time of the request must be after the supplied date and before
means that the time of the request must be before the supplied date. For example:
allow:
and:
- date:
after: 2020-01-02T16:20:00
before: 2150-01-02T16:20:00
Device Matcher
A device matcher is an object with operators as keys. It supports the following operators:
is
- an exact match of the device ID.approved
- true if the device has been approved. This is an enterprise-only feature.type
- Specifies the type of device to match on. The available types areenclave_only
andany
.enclave_only
will only match platform authenticators. These include TPM modules and hardware-backed keystores built into mobile devices.any
will also match hardware security keys.
For example, a policy to allow any user with a registered device:
- allow:
or:
- device:
type: any
Compare to a policy that only allows a set of specific devices:
- allow:
or:
- device:
is: "5Vn3...C1RS"
- device:
is: "GAtL...doqu"
Users can find their device IDs at the /.pomerium
endpoint from any route.
Record Matcher
The record matcher is an object that uses operators as keys. It points to records collected from an external data source defined in the Enterprise Console. Pomerium matches requests to a specific external data source using a record's foreign key. You can use data stored in a record as external context in an authorization policy.
The record matcher supports all of the String Matcher and String List Matcher operators. However, the following operators are specific to the record matcher:
type
: Identifies the Record Type as it’s defined in the Enterprise Consolefield
: Specifies the field name as defined by the external data source- Exists operator
- Numerical comparison operators (
<
,<=
,=
,>
,>=
)
Exists operator
The “exists” operator is a boolean:
- When set to
true
, it returnsok
if it can find the corresponding external data source record in the Enterprise Console. - When set to
false
, it returnsok
if it can’t find the corresponding external data source record in the Enterprise Console.
The "exists" operator does not require a "field" key.
- Policy Builder
- Policy Editor
allow:
and:
- record:
type: pomerium.io/ExternalDataSource
exists: true
Numerical comparison operators
The numerical comparison operators (<
, <=
, =
, >
, >=
) can be used to express conditions for external data sources with numerical fields.
- Policy Builder
- Policy Editor
allow:
and:
- record:
type: pomerium.io/ExternalDataSource
field: trust_score
'>=': 5
String Matcher
A string matcher is an object with operators as keys. It supports the following operators: contains
, ends_with
, is
and starts_with
.
For example:
allow:
and:
- email:
starts_with: 'admin@'
Or:
allow:
and:
- record:
type: example.com/geoip
field: country
is: 'US'
A string matcher can also be used with an array, a string, a number or a boolean, in which case it is the same as the is
operator.
String List Matcher
A string list matcher is an object that supports a single has
operator as a key. The has
operator checks that a given string is present in a list of strings.
The groups
and record
criteria both support the has
operator.
For example, using the groups
criterion:
allow:
and:
- groups:
has: '00gv40ki4gmtCyl5d4x6'
Using the record
criterion:
- record:
type: example.com/hr_user
field: departments
has: 'engineering'
A string list matcher can also be used with an array, a string, a number or a boolean, in which case it is the same as the has
operator.
Time of Day Matcher
The time of day matcher is an object with operators as keys. It supports the following operators: timezone
, after
, and before
.
timezone
is required and specifies the timezone to use when interpreting the supplied times. It is recommended to use city names (like America/Phoenix
) instead of standard timezone abbreviations because standard timezones change throughout the year (i.e. EST becomes EDT and back again).
after
means the time of the request must be after the supplied time and before
means that the time of the request must be before the supplied time. For example:
allow:
and:
- time_of_day:
timezone: UTC
after: 2:20:00
before: 4:30PM