Policy Creation

Last modified on September 25, 2024

Overview

A policy consists of policy statements that enforce fine-grained access control for the users of an organization. Each policy statement declares what is forbidden or permitted for users to do with particular resources. The Policy Editor is where admins can build custom policy statements and save them to a policy.

Policies are managed in the Admin UI in Access > Policies > Policy Library. The Policy Library contains a listing and brief description of each policy that has been created in your organization. Clicking either the name or the Details button of any policy opens the policy for editing in the Policy Editor tab.

The Policy Editor allows you to build policy statements by either setting contextual controls to construct a policy statement in the Cedar policy language, or by typing policy statements in Cedar syntax directly into the editing area. When policy statements are saved to a policy, the policy enforces them immediately.

This article describes how to use the Policy Editor to build policy statements in Cedar syntax, modify them, and save them to a policy. By the end of this article, you will have learned how policy statements are constructed, enabling you to build and save your own policies to suit your organization.

For more information about policies in general, please see the Policies section.

Policy Editor Usage

The Policy Editor consists of an editing area and a set of builder controls. The editing area displays policy statements saved to a policy. You may type policy statements in Cedar syntax manually, or you may use the builder controls to create policy statements and inject them into the policy.

Builder controls

Builder controls help you to define all the conditions and requirements that must be satisfied in order for a policy statement to evaluate to true and either permit or forbid access.

These controls are:

  • Access: Designates the policy statement as either a Forbid or Permit statement
  • Who: Specifies the principal(s), which may be one or more users or roles, that are the subject of the statement
  • What: Specifies resource(s) and/or actions taken on the resource that are the subject of the statement
  • When: Specifies contextual elements, such as the location of the user, the user’s device trust status, or the IP address of the user or resource being accessed
  • With: Adds other options that must be used (such as multi-factor authentication (MFA), justification for access, or workflow approval)

All these elements form the context of user access that a policy can enforce.

Policy Statement Structure

A policy statement may be either a Permit statement or a Forbid statement. Permit statements allow specific principals (which are users or roles) to perform specific actions on resources. In contrast, Forbid statements stop principals from performing specific actions on specific resources.

Permit statements

Permit statements allow user actions to be completed if all of the following are true:

  • The user’s username or role matches the principal set in the policy statement.
  • The resource the user is interacting with matches the resource set in the policy statement.
  • Any contextual elements set in the policy statement match the context of the user.
  • The user completes any further requirements that are configured in the policy statement.

Forbid statements

Forbid statements stop user actions if all of the following are true:

  • The user’s username or role matches the principal set in the policy statement.
  • The resource the user is interacting with matches the resource set in the policy statement.
  • Any contextual elements set in the policy statement match the context of the user.

Statement structure

Every policy statement follows the same general structure, where the policy statement:

  • Begins with the “access” type (permit or forbid)
  • Includes principal for defining “who” is permitted or denied access
  • Includes action for specifying “what” user actions may or may not be performed in StrongDM or on a resource
  • Includes resource for specifying “what” resources the user may or may not access
  • Ends with a semicolon (;)
permit (
  principal,
  action,
  resource
);

// or

forbid (
  principal,
  action,
  resource
);

Principal

The principal in a policy statement indicates specifically who is permitted or forbidden access. The principal may be a user (person or service account) or a role in the organization.

The principal is set as either the user ID or role ID in the format principal == StrongDM::Account::"<USER_ID>" or principal == StrongDM::Role::"<ROLE_ID>".

In the example shown, user Alice Glick with user ID a-70254fee63ea6fbe is permitted to take all actions on all resources.

permit (
  principal == StrongDM::Account::"a-70254fee63ea6fbe",
  action,
  resource
);

Similarly, in the following example, only users assigned the role called Marketing (which has role ID r-1caa595464152e78) are forbidden access:

permit (
  principal in StrongDM::Role::"r-1caa595464152e78",
  action,
  resource
);

When no principal is specified, the policy statement applies to all principals. In the following example Permit statement, access is permitted for all users and roles.

permit (
  principal,
  action,
  resource
);

Action

The action in a policy statement indicates the specific action, including StrongDM actions (such as connecting to a resource) and database actions (such as Create, Read, Update, and Delete) that a user is permitted or forbidden to perform on the specified resource(s).

For more information about PostgreSQL actions, please see Context-Based Policy.

How to specify actions

StrongDM actions

In a policy statement, StrongDM actions are set in the format StrongDM::Action::"<ACTION>", where <ACTION> is the name of the action (for example, StrongDM::Action::"connect").

When an action is for a particular resource, the resource in the policy statement also must be set.

In the Policy Editor, if you are typing into the editing area directly, enter the action (and resource) in the following way.

forbid (
  principal == StrongDM::Account::"a-3c931809650c7041",
  action == StrongDM::Action::"connect",
  resource == StrongDM::Resource::"rs-123d456789e12e34"
);
Actions for PostgreSQL resources

In a policy statement, for a PostgreSQL resource, the action is set in the format SQL::Action::"<ACTION_NAME>", where SQL is the resource type and <ACTION_NAME> is the action itself (for example, SQL::Action::"alterAggregateFunction").

When an action is for a PostgreSQL resource, the resource in the policy statement also must be set in one of the following ways:

  • resource == Postgres::Database::"<RESOURCE_ID>/database-name"
  • resource in StrongDM::Resource::"<RESOURCE_ID>"

This particular example shows that the user is forbidden from altering databases for the specified Postgres resource.

forbid (
  principal == StrongDM::Account::"a-3c931809650c7041",
  action == SQL::Action::"select",
  resource == Postgres::Database::"rs-521d183862e19e48/database-name"
);

// or

forbid (
  principal == StrongDM::Account::"a-3c931809650c7041",
  action == SQL::Action::"update",
  resource in StrongDM::Resource::"rs-521d183862e19e48"
);

When no action is specified, the policy statement applies to all actions. In the following example, the user is allowed to perform all actions on the specified Postgres resource.

permit (
  principal == StrongDM::Account::"a-3c931809650c7041",
  action,
  resource == StrongDM::Resource::"rs-521d183862e19e48"
);

If you want all members of a role to be able to perform all database actions against all Postgres resources, you could use the following example policy statement:

permit (
  principal in StrongDM::Role::"r-1caa595464152e78",
  action,
  resource == Postgres::Database
);

This statement allows all actions against Postgres databases for users with the specified role, and could be followed by other statements that forbid particular sensitive actions if necessary.

Resource

The resource indicated in a policy statement indicates the specific resource that a user is permitted or forbidden to access.

In a policy statement, a resource is set in the format resource == StrongDM::Resource::"<RESOURCE_ID>", as in the following example.

permit (
  principal,
  action,
  resource in StrongDM::Resource::"rs-28acd881623b7f19"
);

Context

Context is set as a when condition in the policy statement. The when condition specifies any additional constraints, along with any other attributes of the principals, actions, and resources, that the policy must evaluate in order to permit or forbid the user action.

In a policy statement, you can set context for location, Device Trust status, and IP address.

This section discusses the following contextual elements:

Location context

You can set context in a policy statement to forbid or permit access based on the geographical location (country) of a user or resource that the user is attempting to access.

In a policy statement, location context is set as a condition in the when clause, in the format context.location in Location::Country::"<COUNTRY_NAME>". The country name is set as an ISO-3166-1 code.

In the example shown, the user is permitted to access the resource only when doing so in the United States.

permit (
  principal == StrongDM::Account::"a-12354fee63ea6fbe",
  action,
  resource == StrongDM::Resource::"rs-12324fdc60b00166"
) when {
  context has location && context.location in Location::Country::"US"
};

For a Permit statement, if the location is set, the policy allows user access when (in addition to the other options) the user’s location is either in or not in a specified country.

For a Forbid statement, if the location is set, the policy prohibits user access when (in addition to the other options) the user’s location is either in or not in a specified country.

For example, the following Forbid statement stops all user access when the user is located in Sweden.

forbid (
  principal,
  action,
  resource
) when {
  context has location && context.location in Location::Country::"SE"
};
Location calculation

When a connection or query is performed by a StrongDM user against a resource, their IP is used to calculate an approximate geographic location. The context property used to determine this public IP address is network.clientIp, and if it is not present, a location will not be available.

The client IP is always a public IP address. It is the source of the client connection to the StrongDM control plane.

If a client is connecting to the StrongDM control plane via a VPN or proxy, their client IP will be the public IP address of the VPN proxy server, and so will the calculated location.

Network information context

You can set context in a policy statement to forbid or permit access based on the network information involved in the request. This can allow you to ensure that users are connecting from the correct IPs, to correct ports, or even to forbid access to a group of resources unless the resource IP is at a particular IP.

  • Client IP address: As detailed in the location calculation section, the client IP address is the public address from which a client connects to the StrongDM control plane. If the client is behind a VPN, it will be the address of the proxy server for the VPN. This context can be set in a policy statement in the following format:
    context.network.clientIp == ip("101.111.121.131")
    
  • Request IP address: The request IP address is the IP address of the client as seen by the ingress point into the StrongDM network (either the control plane or a gateway). If the client is behind a VPN, this address will be the address of the VPN client. Despite not being the public IP of the user’s device, this could still be useful in policy if, for example, clients are assigned static IP addresses on the VPN. Policies could then restrict access that authorized access based on the assigned VPN address being the same as the request IP.
    context.network.requestIp == ip("102.112.122.132")
    
  • Destination IP address: IP address of the resource being acted against
    context.network.destinationIp == ip("103.113.123.133")
    
  • Target hostname: Hostname of the resource being acted against
    context.network.target.hostname == "db.example.com"
    
  • Target port: Port of the resource being acted against
    context.network.target.port == 1234
    

In the example shown, the user is permitted to access when particular network information is present in the request context.

permit (
  principal,
  action,
  resource
) when {
  context.network.clientIp == ip("101.111.121.131") && 
  context.network.requestIp == ip("102.112.122.132") && 
  context.network.destinationIp == ip("103.113.123.133") && 
  context.target.hostname == "db.example.com" && 
  context.target.port == "1234"
};
IP ranges

You can set context in a policy statement to forbid or permit access based on the IP address range of a user’s machine.

In a policy statement, IP range context is set as a condition in the when clause, in the format context.network.clientIp.isInRange(ip("<STARTING_IP_ADDRESS>/<SUBNET_MASK>")).

In the example shown, the user is denied access when their IP address range doesn’t match 1.2.3.0/24.

forbid (
  principal,
  action,
  resource
) when {
  !context.network.clientIp.isInRange(ip("1.2.3.0/24"))
};

Considerations when checking for IP ranges:

  • If using the builder controls to set IP range context, under When, click Always (to change it), and click IP Range. Select either is or is not, enter the Starting IP Address, and enter the Subnet Mask (if any).
  • When IP range context is set, the policy permits user access when the user’s IP address range is or is not the specified range. If set to Always, the policy is enforced no matter what the user’s IP address is.
  • When the policy statement has no context for IP range, the user’s IP address is not considered during policy evaluation, and the policy always permits or forbids, regardless of the user’s IP address.

Device Trust context

You can set context in a policy statement to forbid or permit access based on the Device Trust status of a user’s machine.

This option uses StrongDM’s Device Trust feature, which allows you to integrate device trust scores from an endpoint management software provider (such as CrowdStrike or SentinelOne) into your authentication flow for StrongDM. This feature must be set up and working for this policy option to work.

In a policy statement, Device Trust context is set as a condition in the when clause, in the format context.trust.status == "<TRUST_STATUS>".

Possible statuses are:

  • High Trust: good
  • Low Trust: bad
  • Exempt: exempt
  • Unknown: unknown

When Device Trust context is added to a policy statement and a status value is set, the policy statement evaluates to true if, in addition to the other options, the user’s device trust score matches the one specified.

In the example shown, the user is permitted to access the resource when their Device Trust status is “High Trust.”

permit (
  principal == StrongDM::Account::"a-3c931809650c7041",
  action,
  resource == StrongDM::Resource::"rs-75fe719e62ec28d6"
) when {
  context.trust.status == "good"
};

Additionally, you may check for Device Trust values of “good” or “exempt” by checking for a true value of context.trust.ok and values of “bad” or “unknown” for a false value.

// When the device trust status is good or exempt
when {
  context.trust.ok
}

// When the device trust status is bad or unknown
when {
  !context.trust.ok
}

When Device Trust is set, the policy permits user access when the user’s Device Trust status is or is not the specified status. If set to Always, the policy is enforced no matter what the user’s Device Trust status is.

Resource tags context

You can require particular tags to be set on a resource in order for the policy statement to apply to it. For example, you can have users with a particular development role be permitted to perform actions against resources if the resources are tagged with env=dev. The following is an example of this policy statement:

permit (
  principal in StrongDM::Role::"r-1caa595464152e78",
  action,
  resource
) when {
  resource.tags has env && resource.tags.env == "dev"
};

In the when clause in this example, you can check that the resource has the tag at all (resource.tags has env), and then verify the value of the particular tag in question (resource.tags.env == "dev").

Annotations

Annotations are optional constraints that may be added to policy statements. An annotation takes the form of the following string: @annotationname("value").

The following table lists all possible annotations.

AnnotationDescriptionValue formatPolicy statement type
@approve(“<WORKFLOW_ID>")Uses Approval Workflows and requires approvers to approve user access or actions; the action is forbidden until access is approvedStringPermit
@disconnect("true")Disconnects the user from the resourceTruthy valueForbid
@error("<REASON>")Overrides the default error message, that the client application may display, that is returned in the protocol response when an operation is forbiddenStringForbid
@justify("<PROMPT>")Requires users to provide justification for their action; justification is logged with the requestStringPermit
@logout("<REASON>")Forcefully logs out users when their action is forbiddenStringForbid
@maxrows("<NUMBER>")Restricts queries to returning no more than a defined number of rowsStringPermit
@mfa("<PROMPT>")Requires users to complete an MFA prompt in order to complete an actionStringPermit
@notify("<VALUE>")Provides a notification message to the user in StrongDM DesktopStringForbid or Permit

You can place annotations only at the very top of the policy statement before permit or forbid. Note that some annotations only have an effect for Permit statements or Forbid statements and as such, they should be used only for those policy statement types.

A policy statement may include multiple annotations. For example, the following Permit statement allows all users to perform all actions on the specified resource only when they complete an MFA prompt and provide justification for their action.

@mfa("")
@justify("Please provide a reason to do this.")
permit (
  principal,
  action,
  resource == StrongDM::Resource::"rs-55580513635ae326"
);

Cache timeout

Some annotations, such as @mfa and @justify, allow you to set a cache timeout value with the annotation. The cache timeout is the amount of time that the system waits before prompting the user again for MFA or justification. Setting a cache timeout allows you to control how frequently a prompt or challenge is presented to the user when the user attempts to connect to a resource.

A short cache timeout provides the strictest security requirement for authentication, but if you need to relax that for usability reasons, you have the option to set a longer cache timeout. The maximum cache timeout value is 24 hours, and the minimum value is 1 minute. If not specified, the cache timeout defaults to one minute. Setting a cache timeout higher than the default is optional, but all cache length specifications must be explicitly written in policy.

Let’s say, for example, that your policy requires the user to complete an MFA challenge in order to connect to a resource, and that the MFA challenge has a cache timeout of one hour. In such a scenario, the user completes the MFA challenge when prompted, connects to the resource if the challenge is successful, and if still using the resource an hour later, is prompted with the MFA challenge again.

If more than one annotation of the same type with a cache timeout is collected from policies, the request with the smallest cache length is used. For example, if a policy has two policy statements with MFA requirements, and those requirements have a cache timeout of 10 minutes and 30 minutes, the 10-minute cache timeout is used.

Different types of requirements (MFA or justify) do not affect each other’s cache length.

Please note that a change in policy may result in cache invalidation (for example, changing cache=5m to cache=60m).

Format

A cache timeout may be formatted as a query string or a simple string in the annotation’s prompt value. The format that you choose depends on whether or not you need to send other parameters with the annotation, such as a reason or prompt value, or an amount of time.

Use query string format (for example, ?reason=reason+text&cache=60m) to set a cache timeout value and send a reason or prompt value. The query string format is valuable primarily when you need to pass multiple parameters, such as with the MFA cache. The first item in the query string is ?key=value, the parameters that follow are &key=value, and normal query string syntax is used for all other parts (for example, + for spaces and character codes for punctuation).

You may use a simple string if you only want to send a reason or prompt with the annotation (for example, Please provide a reason to do this.).

To set a cache timeout other than the default, specify the cache parameter and timeout length as a query string in the format "?cache=00h00m". Valid values for cache are amounts of time in minutes “m” and/or hours “h”, such as 6m, 03h, 20h30m, 2h15m, and so forth.

Put the query string in the annotation’s prompt value, as in the examples shown.

Example cache timeout settings
Require MFA challenge every 2 hours
@mfa("?cache=2h0m")
permit (
    principal,
    action,
    resource
);
Require MFA challenge every 1 hour and include a reason in the MFA challenge
@mfa("?reason=reason+text&cache=60m")
permit (
    principal,
    action,
    resource
);
Require MFA challenge and use the default cache timeout, without sending a reason
@mfa("")
permit (
    principal,
    action,
    resource
);
Require MFA challenge, use the default cache timeout, and send a reason
@mfa("MFA is required to continue.")
permit (
    principal,
    action,
    resource
);
Require justification every 4 hours without sending a prompt message
@justify("?cache=4h")
permit (
    principal,
    action,
    resource
);
Require justification every 24 hours and include a justification prompt message
@justify("?prompt=Please provide a reason to do this.&cache=24h")
permit (
    principal,
    action,
    resource
);
Require justification, include a prompt message, and use the default cache timeout
@justify("Please provide a reason to do this.")
permit (
    principal,
    action,
    resource
);

Approve

The @approve annotation is used to require workflow approval for user access or actions.

This option uses StrongDM’s Approval Workflows feature. Approval workflows provide notification for selected approvers to approve access or actions. You must have an approval workflow set up and working for this policy option to work.

In a Permit statement, the workflow approval requirement is set as an annotation in the format @approve("<WORKFLOW_ID"), where <WORKFLOW_ID> is the ID of the workflow. The annotation is placed at the top of the Permit statement, immediately before permit, as in the following example.

@approve("af-3dc23d7965fae03c")
permit (
  principal == StrongDM::Account::"a-1c2a83a9623b8021",
  action,
  resource == StrongDM::Resource::"rs-128613d2623261e3"
);

When a Permit statement includes @approve, the policy statement evaluates to true if, in addition to the other options, the user’s action is approved via the selected approval workflow. This is the only item that may occur asynchronously, depending on the approval workflow in question. The approval, once granted, is valid for a pre-set amount of time which defaults to four hours and can be changed in the Admin UI under Settings > Workflows > Policy Requests. During this period of time when the approval is valid, the user may retry the actions they were attempting to take on the resource.

Disconnect

The @disconnect annotation is used to disconnect a user from a resource.

In a Forbid statement, the annotation is set in the format @disconnect("true") with a case-insensitive truthy value (for example, true, yes, on, t, y, or 1). When set, the user’s connection to the resource is terminated when the policy statement is evaluated.

The annotation is placed at the top of a Forbid statement, immediately before forbid.

Error

The @error annotation is used to display an error message to the user.

In a Forbid statement, the annotation is set in the format @error("<REASON>"), where <REASON> is the text displayed to the user when the error occurs. The reason value may be any string.

The annotation is placed at the top of a Forbid statement, immediately before forbid.

Justify

The @justify annotation is used to prompt users to justify their actions, and then have their written explanation logged.

In a Permit statement, the annotation is set in the format @justify("<PROMPT>"), where <PROMPT> is the your custom text that is displayed to the user when asked to provide justification for their action. The annotation is placed at the top of the Permit statement, immediately before permit.

In this example, all users are permitted create roles in the specified database if they provide justification.

@justify("Please explain the need to create a table.")
permit (
  principal,
  action == SQL::Action::"SQL::Action::"createTable"",
  resource == StrongDM::Resource::"rs-12c943ba60afff59"
);

When a Permit statement includes the Justification option, the policy statement evaluates to true if, in addition to the other options, the user records a text justification for their action. The prompt value (for example, Why do you want to do this?) is shown to the user to provide context for what you wish them to record, why, or for other contextual information that you wish to provide. The reason they provide is not assessed in any way, but it is logged for later review.

Logout

The @logout annotation is used to log out the user forcefully if their action is forbidden.

In a Forbid statement, the annotation is set in the format @logout("<REASON>"), where <REASON> is an optional message to be shown to the user when they are logged out. The annotation is placed at the top of the Forbid statement, immediately before forbid.

In the following example, all users are logged out if they attempt to perform an unknown action on any resource.

@logout("Access denied")
forbid (
  principal,
  action == SQL::Action::"unknown",
  resource
);

When the Forbid statement includes the logout option, the policy evaluates to false, the action is forbidden, the user is logged out, and your defined reason (if set) is provided to the user.

Max rows

The @maxrows annotation is used to limit queries to a maximum number of rows.

In a Permit statement, the annotation is set in the format @maxrows("<NUMBER>"), where <NUMBER> is the maximum number of table rows for the user’s queries. The annotation is placed at the top of a Permit statement, immediately before permit.

To provide a notification message to the user when the user reaches this limit, use @notify with the desired message as the value, as in the following example.

@maxrows("100")
@notify("queries are limited to 100 rows")
permit (
	principal in StrongDM::Role::"regularuser",
	action,
	resource
);

MFA

The @mfa annotation is used to require users to complete a multi-factor authentication (MFA) prompt in order to be allowed to complete their action.

StrongDM’s MFA Authentication feature must be configured and working for this policy option to work.

In a Permit statement, the annotation is set in the format @mfa("<PROMPT>"), where the value of <PROMPT> is the action that causes the user to be prompted to complete MFA. The annotation is placed at the top of a policy statement, immediately before permit.

@mfa("query secrets")
permit (
  principal == StrongDM::Account::"a-70254fee63ea6fbe",
  action,
  resource
);

When the MFA option is added to a policy statement, the policy statement evaluates to true if, in addition to the other options, the user successfully completes the MFA prompt that is generated upon their action. The MFA prompt remains valid for five minutes before timing out and denying access, if the user’s client (such as their terminal or database access client) does not time out sooner. Users attempting actions that match this policy are prompted again for MFA if one minute or more has elapsed since their last action against the resource.

Notify

The @notify annotation is used to provide a notification message to the user in StrongDM Desktop. It can be used in any policy statement.

In a policy statement, the annotation is set in the format @notify("<VALUE>"), where <VALUE> is the text to be displayed to the user in the notification message. The annotation is placed at the top of a policy statement, immediately before permit or forbid.

Policy Management via CLI

Policies also can be created, updated, or deleted via the CLI. The commands are:

Policy creation via CLI

When creating a policy, first write the policy in the Cedar policy language and save it to a text file, similar to this example:

// cedarfile.txt
permit (
  principal == StrongDM::Account::"a-70254fee63ea6fbe",
  action,
  resource
);

Then use the sdm admin policies create command to create the policy in StrongDM in the format sdm admin policies create --policy-file <FILEPATH/FILENAME> --description <DESCRIPTION> <POLICY_NAME>. For example:

sdm admin policies create --policy-file Desktop/cedarfile.txt --description "An example policy that allows Bob all access" "Example Policy"
  • The value of the --policy-file option is the file name and path of the text file containing the Cedar policy statements you wish to include in the policy (for example, Desktop/cedarfile.txt).
  • The value of the --description option is optional and would typically describe the policy statements contained within the policy.
  • The last parameter is the name of the policy (for example, “Example Policy”).

Policy update via CLI

When updating a policy, first write the updated policy in a text file saved on your local device, similar to this example:

// cedarfile.txt
permit (
  principal == StrongDM::Account::"a-70254fee63ea6fbe",
  action,
  resource == StrongDM::Resource::"rs-12c943ba60afff59"
);

Then use the sdm admin policies update command to update the policy in StrongDM in the format sdm admin policies update --name <NEW_POLICY_NAME> --policy-file <FILEPATH/FILENAME> --description <DESCRIPTION> <POLICY_NAME>.

sdm admin policies update --name "Example Policy v2" --policy-file Desktop/cedarfile.txt --description "An example policy that allows Bob all access to the dev Postgres" "Example Policy"
  • The value of the --name option is optional and is the new name you wish to give to the policy.
  • The value of the --policy-file option is the file name and path of the text file containing the Cedar policy statements you wish to include in the policy (for example, Desktop/cedarfile.txt).
  • The value of the --description option is optional and would typically describe the policy statements contained within the policy.
  • The last parameter is the name of the policy (for example, “Example Policy”).
Top