Manage Permit.io policies with Terraform

Define your Permit.io authorization model (resources, actions, roles, condition sets, and relations) as Terraform code, and apply it to a Permit environment with the Permit.io Terraform provider. This page is for developers and platform engineers who manage infrastructure with Terraform or OpenTofu and want the same review and version control for their authorization policy.
With the provider, a policy change is a change to a .tf file. You review it in a pull request, see the planned changes with terraform plan, and apply the same configuration to each environment.
If you already configured an environment in the Permit dashboard, the Permit CLI can export the environment to a Terraform file. Start from the exported file instead of writing the configuration by hand.
Prerequisites
- Terraform 1.0 or later, or OpenTofu 1.0 or later.
- A Permit.io account.
- Basic knowledge of Terraform configuration syntax.
1. Get your environment API key
The provider changes the policy of one environment: the environment that the API key belongs to. Copy the API key of the environment you want to manage. See Get your API key.
Anyone with the environment API key can change that environment's policy through the Permit API. Don't commit the key to your repository.
2. Write your first configuration
Create a directory for the Terraform project and add the following files.
main.tf requires the Permit.io provider, configures it, and defines a document resource with three actions and three roles:
terraform {
required_providers {
permitio = {
source = "registry.terraform.io/permitio/permit-io"
version = "~> 0.0.25" # Use the latest version
}
}
}
# Configure the Permit.io provider
provider "permitio" {
api_key = var.permit_api_key
api_url = var.permit_api_url
}
# Define a resource (e.g., documents in your application)
resource "permitio_resource" "document" {
key = "document"
name = "Document"
description = "A confidential document that users can read and write"
actions = {
"read" = {
"name" = "Read"
"description" = "Read a document"
}
"write" = {
"name" = "Write"
"description" = "Write to a document"
}
"delete" = {
"name" = "Delete"
"description" = "Delete a document"
}
}
attributes = {
"title" = {
"description" = "The title of the document"
"type" = "string"
}
"owner" = {
"description" = "The owner of the document"
"type" = "string"
}
}
}
# Define roles with specific permissions
resource "permitio_role" "reader" {
key = "reader"
name = "Reader"
description = "Can read documents but cannot modify them"
permissions = ["document:read"]
depends_on = [permitio_resource.document]
}
resource "permitio_role" "writer" {
key = "writer"
name = "Writer"
description = "Can read and write documents"
permissions = ["document:read", "document:write"]
depends_on = [permitio_resource.document]
}
resource "permitio_role" "admin" {
key = "admin"
name = "Administrator"
description = "Full access to documents including deletion"
permissions = ["document:read", "document:write", "document:delete"]
depends_on = [permitio_resource.document]
}
In main.tf:
version = "~> 0.0.25"accepts any0.0.xrelease from0.0.25on. For the latest release, see the provider page in the Terraform Registry.api_keyandapi_urlread thepermit_api_keyandpermit_api_urlvariables, whichvariables.tfdeclares andterraform.tfvarssets. To pass the API key in an environment variable instead, see Pass the API key in environment variables.
variables.tf declares input variables for the API key and API URL. sensitive = true keeps Terraform from printing the key in plan output:
variable "permit_api_key" {
description = "Permit.io API key"
type = string
sensitive = true
}
variable "permit_api_url" {
description = "Permit.io API URL"
type = string
default = "https://api.permit.io"
}
terraform.tfvars sets the variable values. Add terraform.tfvars to .gitignore, because the file contains your API key:
permit_api_key = "your-actual-api-key-here"
permit_api_url = "https://api.permit.io"
3. Initialize and apply the configuration
Download the provider, review the planned changes, and apply them:
# Initialize Terraform
terraform init
# Plan your changes
terraform plan
# Apply the configuration
terraform apply
terraform apply asks you to confirm the plan. Type yes.
4. Verify the policy in Permit
- Open the Policy Editor of the environment that the API key belongs to.
- Confirm that the
Documentresource has the Read, Write, and Delete actions, and that the Reader, Writer, and Administrator roles have the permissions frommain.tf. - Run
terraform planagain. Terraform reports that no changes are needed, because the environment matches the configuration.
Provider resource reference
Each block type below maps to an object in the Permit policy model. For every argument and attribute, see the provider documentation in the Terraform Registry.
Resources
A permitio_resource is a type of object that users act on in your application, such as a document or a project. The actions map lists what users can do on the resource, and the optional attributes map lists typed attributes for attribute-based access control (ABAC):
resource "permitio_resource" "example" {
key = "unique_key"
name = "Human Readable Name"
description = "Description of what this resource represents"
actions = {
"action_key" = {
"name" = "Human Readable Action"
"description" = "What this action does"
}
}
attributes = {
"attribute_name" = {
"description" = "Description of the attribute"
"type" = "string" # or "number", "boolean", "array", "object"
}
}
}
Roles
A permitio_role grants a set of permissions. For a top-level role, each permission has the format resource_key:action_key. Add depends_on so Terraform creates the resource before the role that references its actions:
resource "permitio_role" "example" {
key = "role_key"
name = "Role Name"
description = "What this role allows users to do"
permissions = ["resource:action"] # Format: "resource_key:action_key"
depends_on = [permitio_resource.resource_name]
}
User sets and resource sets
A permitio_user_set groups users, and a permitio_resource_set groups resource instances, by conditions on their attributes. conditions takes a JSON string, so wrap the conditions in jsonencode(). A resource set also needs the resource it applies to:
# Group users by email domain
resource "permitio_user_set" "admins" {
key = "admins"
name = "Administrators"
conditions = jsonencode({
"allOf" = [
{
"subject.email" = {
contains = "@admin.com"
}
}
]
})
}
# Group resources by attribute
resource "permitio_resource_set" "public_docs" {
key = "public_docs"
name = "Public Documents"
resource = permitio_resource.document.key
conditions = jsonencode({
"allOf" = [
{
"resource.visibility" = {
equals = "public"
}
}
]
})
}
The public_docs condition uses a visibility attribute, which the document resource in main.tf doesn't define. Add visibility to the attributes map of the resource.
Condition set rules
A permitio_condition_set_rule grants a permission to the users in a user set on the resources in a resource set:
resource "permitio_condition_set_rule" "admin_access" {
user_set = permitio_user_set.admins.key
resource_set = permitio_resource_set.public_docs.key
permission = "document:read"
}
Relationship-based access control (ReBAC)
For relationship-based access control (ReBAC), define resource roles (roles with a resource argument), a permitio_relation between two resources, and a permitio_role_derivation that grants a role on one resource from a role on a related resource. In this example, an admin of a folder becomes an admin of each file in the folder:
# Define resources with relationships
resource "permitio_resource" "folder" {
key = "folder"
name = "Folder"
actions = {
"list" = { "name" = "List" }
"create" = { "name" = "Create" }
}
}
resource "permitio_resource" "file" {
key = "file"
name = "File"
actions = {
"read" = { "name" = "Read" }
"write" = { "name" = "Write" }
}
}
# Define the relationship
resource "permitio_relation" "parent" {
key = "parent"
name = "parent of"
subject_resource = permitio_resource.folder.key
object_resource = permitio_resource.file.key
}
# Define roles for each resource
resource "permitio_role" "folder_admin" {
key = "admin"
name = "Folder Administrator"
permissions = ["list", "create"]
resource = permitio_resource.folder.key
}
resource "permitio_role" "file_admin" {
key = "admin"
name = "File Administrator"
permissions = ["read", "write"]
resource = permitio_resource.file.key
}
# Derive file permissions from folder permissions
resource "permitio_role_derivation" "folder_to_file" {
resource = permitio_resource.file.key
role = permitio_role.file_admin.key
on_resource = permitio_resource.folder.key
to_role = permitio_role.folder_admin.key
linked_by = permitio_relation.parent.key
}
For the concepts, see ReBAC policies.
User attributes
A permitio_user_attribute defines a typed attribute on users, for use in user set conditions:
resource "permitio_user_attribute" "department" {
key = "department"
description = "The department the user belongs to"
type = "string"
}
resource "permitio_user_attribute" "security_level" {
key = "security_level"
description = "User's security clearance level"
type = "number"
}
Examples in the provider repository
The Permit.io Terraform provider repository has complete configurations:
- Basic example: resources with actions and attributes, roles, user sets, resource sets, condition set rules, and a proxy configuration.
- ReBAC example: relations between resources and role derivations.
Pass the API key in environment variables
When the provider "permitio" block doesn't set api_key or api_url, the provider reads the PERMITIO_API_KEY and PERMITIO_API_URL environment variables. Set them in your shell or CI job:
export PERMITIO_API_KEY="your-api-key"
export PERMITIO_API_URL="https://api.permit.io" # Optional
Then leave both arguments out of the provider block:
provider "permitio" {
# Terraform will automatically use the environment variables
}
Recommended practices
Use input variables for values that change per environment
variable "environment" {
description = "Environment name (dev, staging, prod)"
type = string
}
resource "permitio_resource" "document" {
key = "document"
name = "Document"
description = "Documents for ${var.environment} environment"
# ... rest of configuration
}
Reference existing objects with data sources
Use a data source to read an object that Terraform doesn't manage, such as a resource created in the dashboard.:
data "permitio_resource" "existing_document" {
key = "document"
}
resource "permitio_role" "reader" {
key = "reader"
name = "Reader"
permissions = ["${data.permitio_resource.existing_document.key}:read"]
}
Output values you need elsewhere
output "resource_id" {
value = permitio_resource.document.id
}
output "role_permissions" {
value = permitio_role.admin.permissions
}
Keep a separate state per environment
Terraform workspaces keep a separate state for each workspace. Create one workspace per Permit environment:
# Create separate workspaces for different environments
terraform workspace new dev
terraform workspace new staging
terraform workspace new prod
# Switch between workspaces
terraform workspace select dev
A workspace doesn't change the API key. If every workspace uses the same PERMITIO_API_KEY, every workspace applies to the same Permit environment, and each state file tracks the same objects. Set the API key of the matching environment before you run terraform plan or terraform apply in a workspace.
Use the provider with OpenTofu
OpenTofu is an open-source fork of Terraform. The Permit.io provider and the configuration files on this page work with OpenTofu without changes.
Install OpenTofu
Install OpenTofu with one of these methods. The apt-get and dnf commands need the OpenTofu package repository set up first. See the OpenTofu installation guide.
# Using Homebrew (macOS/Linux)
brew install opentofu
# Using the official installer
curl -fsSL https://get.opentofu.org/install.sh | sh
# Using package managers
# Ubuntu/Debian
sudo apt-get install opentofu
# CentOS/RHEL/Fedora
sudo dnf install opentofu
Run OpenTofu commands
Use tofu in place of terraform:
# Initialize OpenTofu
tofu init
# Plan your changes
tofu plan
# Apply the configuration
tofu apply
# Show current state
tofu show
# Destroy resources
tofu destroy
tofu destroy (and terraform destroy) deletes every Permit object in the state from the environment. Check the plan it prints before you confirm.
The workspace commands also use tofu:
tofu workspace new dev
tofu workspace select dev
Configuration compatibility
The terraform block and provider source are the same for OpenTofu:
terraform {
required_providers {
permitio = {
source = "registry.terraform.io/permitio/permit-io"
version = "~> 0.0.25"
}
}
}
provider "permitio" {
api_key = "YOUR_API_KEY"
}
# All your resources remain the same
resource "permitio_resource" "document" {
key = "document"
name = "Document"
description = "A confidential document"
# ... rest of configuration
}
Troubleshooting
| Problem | Cause | Fix |
|---|---|---|
Authentication or permission errors on plan or apply | The API key isn't an environment API key, or belongs to a different environment. | Use the API key of the environment you manage. See Get your API key. |
| Creating a role fails because its resource or action isn't found | Terraform created the role before the resource it references. | Add depends_on with the resource to the role. |
| A permission isn't found | The permission string doesn't match the resource and action keys. | Use resource_key:action_key for top-level roles, with keys that match exactly. |
For provider bugs, open an issue in the provider repository. For questions, ask in the Permit Slack community.