Skip to main content

Unix Filesystem Permissions

This example demonstrates how to implement classic Unix-style permission bits using MPE's annotation system. Just like Unix files have rwxrwxrwx permissions for owner, group, and other, this PolicyDomain models read/write access for three permission classes.

Overview

Overview

In Unix filesystems, every file has:

  • An owner (a specific user)
  • A group (a named group of users)
  • Permission bits for owner, group, and other (everyone else)

We'll model this using:

  • resource.owner — The principal who owns the resource
  • resource.annotations.group — The MRN of the owning group
  • resource.annotations.mode — Permission bits encoded as an object
  • principal.mgroups — Groups the principal belongs to

Design

Permission Model​

Each resource has a mode annotation containing permission bits:

{
"owner": { "read": true, "write": true },
"group": { "read": true, "write": false },
"other": { "read": true, "write": false }
}

This is equivalent to Unix mode 0644 (owner: rw, group: r, other: r).

Access Decision Flow​

Complete PolicyDomain

apiVersion: iamlite.manetu.io/v1beta1
kind: PolicyDomain
metadata:
name: unix-filesystem
spec:
# ============================================================
# Policy Libraries - Reusable helper functions
# ============================================================
policy-libraries:
- mrn: &lib-utils "mrn:iam:library:utils"
name: utils
description: "Common utility functions"
rego: |
package utils

import rego.v1

# Check if request has a valid principal (authenticated)
has_principal if {
input.principal != {}
input.principal.sub != ""
}

- mrn: &lib-unix-perms "mrn:iam:library:unix-perms"
name: unix-perms
description: "Unix permission checking utilities"
rego: |
package unix_perms

import rego.v1

# Determine which permission class applies to this principal
# Returns: "owner", "group", or "other"
permission_class(principal, resource) := "owner" if {
principal.sub == resource.owner
}

permission_class(principal, resource) := "group" if {
principal.sub != resource.owner
resource.annotations.group in principal.mgroups
}

permission_class(principal, resource) := "other" if {
principal.sub != resource.owner
not resource.annotations.group in principal.mgroups
}

# Check if the permission class has the required permission
has_permission(mode, class, permission) if {
mode[class][permission] == true
}

# Map operations to required permissions
required_permission(operation) := "read" if {
some suffix in {":read", ":list", ":get"}
endswith(operation, suffix)
}

required_permission(operation) := "write" if {
some suffix in {":write", ":create", ":update", ":delete"}
endswith(operation, suffix)
}

# ============================================================
# Policies
# ============================================================
policies:
# Operation phase - require authentication for all file operations
- mrn: &policy-require-auth "mrn:iam:policy:require-auth"
name: require-auth
description: "Require authentication for file operations"
dependencies:
- *lib-utils
rego: |
package authz

import rego.v1
import data.utils

# Tri-level: negative=DENY, 0=GRANT, positive=GRANT Override
# Default deny - only grant if authenticated
default allow = -1

# Grant authenticated requests
allow = 0 if utils.has_principal

# Identity phase - any authenticated user can attempt file operations
# The actual permission check happens in the resource phase
- mrn: &policy-authenticated-user "mrn:iam:policy:authenticated-user"
name: authenticated-user
description: "Allow any authenticated user to proceed to resource phase"
dependencies:
- *lib-utils
rego: |
package authz

import rego.v1
import data.utils

default allow = false

# Grant if authenticated
allow if utils.has_principal

# Resource phase - Unix permission bit checking
- mrn: &policy-unix-permissions "mrn:iam:policy:unix-permissions"
name: unix-permissions
description: "Check Unix-style permission bits"
dependencies:
- *lib-unix-perms
rego: |
package authz

import rego.v1
import data.unix_perms

default allow = false

# Main permission check
allow if {
# Get the permission mode from the resource
mode := input.resource.annotations.mode

# Determine which class this principal falls into
class := unix_perms.permission_class(input.principal, input.resource)

# Get the required permission for this operation
required := unix_perms.required_permission(input.operation)

# Check if the permission is granted
unix_perms.has_permission(mode, class, required)
}

# Superuser bypass - if principal has superuser role, always allow
allow if {
"mrn:iam:role:superuser" in input.principal.mroles
}

# ============================================================
# Roles
# ============================================================
roles:
# Regular user - relies on file permissions
- mrn: &role-user "mrn:iam:role:user"
name: user
description: "Regular filesystem user"
policy: *policy-authenticated-user

# Superuser - bypasses permission checks (like root)
- mrn: &role-superuser "mrn:iam:role:superuser"
name: superuser
description: "Superuser with full access (like root)"
policy: *policy-authenticated-user

# ============================================================
# Groups - User groups for file permissions
# ============================================================
groups:
- mrn: "mrn:iam:group:developers"
name: developers
description: "Development team"
roles:
- *role-user

- mrn: "mrn:iam:group:admins"
name: admins
description: "System administrators"
roles:
- *role-superuser

- mrn: "mrn:iam:group:finance"
name: finance
description: "Finance department"
roles:
- *role-user

- mrn: "mrn:iam:group:hr"
name: hr
description: "Human resources"
roles:
- *role-user

# ============================================================
# Resource Groups
# ============================================================
resource-groups:
- mrn: &rg-files "mrn:iam:resource-group:files"
name: files
description: "All filesystem resources"
default: true
policy: *policy-unix-permissions

# ============================================================
# Operations
# ============================================================
operations:
- name: file-operations
selector:
- "file:.*"
policy: *policy-require-auth

Test Cases

Test 1: Owner Read Access ​

The owner can read their own file with owner read permission:

{
"principal": {
"sub": "alice",
"mroles": ["mrn:iam:role:user"],
"mgroups": ["mrn:iam:group:developers"]
},
"operation": "file:document:read",
"resource": {
"id": "mrn:fs:home:alice:document.txt",
"owner": "alice",
"group": "mrn:iam:resource-group:files",
"annotations": {
"group": "mrn:iam:group:developers",
"mode": {
"owner": { "read": true, "write": true },
"group": { "read": true, "write": false },
"other": { "read": false, "write": false }
}
}
}
}

Expected:

GRANT
(alice is owner, owner has read permission)

mpe test decision -b policydomain.yml -i input.json | jq .decision
# "GRANT"

Test 2: Owner Write Access ​

The owner can write to their own file:

{
"principal": {
"sub": "alice",
"mroles": ["mrn:iam:role:user"],
"mgroups": ["mrn:iam:group:developers"]
},
"operation": "file:document:write",
"resource": {
"id": "mrn:fs:home:alice:document.txt",
"owner": "alice",
"group": "mrn:iam:resource-group:files",
"annotations": {
"group": "mrn:iam:group:developers",
"mode": {
"owner": { "read": true, "write": true },
"group": { "read": true, "write": false },
"other": { "read": false, "write": false }
}
}
}
}

Expected:

GRANT
(alice is owner, owner has write permission)

Test 3: Group Read Access ​

A group member can read a file with group read permission:

{
"principal": {
"sub": "bob",
"mroles": ["mrn:iam:role:user"],
"mgroups": ["mrn:iam:group:developers"]
},
"operation": "file:document:read",
"resource": {
"id": "mrn:fs:home:alice:shared.txt",
"owner": "alice",
"group": "mrn:iam:resource-group:files",
"annotations": {
"group": "mrn:iam:group:developers",
"mode": {
"owner": { "read": true, "write": true },
"group": { "read": true, "write": false },
"other": { "read": false, "write": false }
}
}
}
}

Expected:

GRANT
(bob is in developers group, group has read permission)

Test 4: Group Write Denied ​

A group member cannot write to a file without group write permission:

{
"principal": {
"sub": "bob",
"mroles": ["mrn:iam:role:user"],
"mgroups": ["mrn:iam:group:developers"]
},
"operation": "file:document:write",
"resource": {
"id": "mrn:fs:home:alice:shared.txt",
"owner": "alice",
"group": "mrn:iam:resource-group:files",
"annotations": {
"group": "mrn:iam:group:developers",
"mode": {
"owner": { "read": true, "write": true },
"group": { "read": true, "write": false },
"other": { "read": false, "write": false }
}
}
}
}

Expected:

DENY
(bob is in group, but group lacks write permission)

Test 5: Other User Denied ​

A user not in the file's group falls back to "other" permissions:

{
"principal": {
"sub": "charlie",
"mroles": ["mrn:iam:role:user"],
"mgroups": ["mrn:iam:group:finance"]
},
"operation": "file:document:read",
"resource": {
"id": "mrn:fs:home:alice:private.txt",
"owner": "alice",
"group": "mrn:iam:resource-group:files",
"annotations": {
"group": "mrn:iam:group:developers",
"mode": {
"owner": { "read": true, "write": true },
"group": { "read": true, "write": false },
"other": { "read": false, "write": false }
}
}
}
}

Expected:

DENY
(charlie is not owner or in developers group, other has no read)

Test 6: World-Readable File ​

Any authenticated user can read a world-readable file:

{
"principal": {
"sub": "charlie",
"mroles": ["mrn:iam:role:user"],
"mgroups": ["mrn:iam:group:finance"]
},
"operation": "file:document:read",
"resource": {
"id": "mrn:fs:public:readme.txt",
"owner": "system",
"group": "mrn:iam:resource-group:files",
"annotations": {
"group": "mrn:iam:group:admins",
"mode": {
"owner": { "read": true, "write": true },
"group": { "read": true, "write": true },
"other": { "read": true, "write": false }
}
}
}
}

Expected:

GRANT
(other has read permission)

Test 7: Superuser Override ​

A superuser can access any file regardless of permissions:

{
"principal": {
"sub": "root",
"mroles": ["mrn:iam:role:superuser"],
"mgroups": ["mrn:iam:group:admins"]
},
"operation": "file:document:write",
"resource": {
"id": "mrn:fs:home:alice:locked.txt",
"owner": "alice",
"group": "mrn:iam:resource-group:files",
"annotations": {
"group": "mrn:iam:group:developers",
"mode": {
"owner": { "read": true, "write": false },
"group": { "read": false, "write": false },
"other": { "read": false, "write": false }
}
}
}
}

Expected:

GRANT
(superuser bypasses all permission checks)

Test 8: Unauthenticated Access Denied ​

An unauthenticated request is denied at the operation phase:

{
"principal": {},
"operation": "file:document:read",
"resource": {
"id": "mrn:fs:public:readme.txt",
"owner": "system",
"group": "mrn:iam:resource-group:files",
"annotations": {
"group": "mrn:iam:group:admins",
"mode": {
"owner": { "read": true, "write": true },
"group": { "read": true, "write": true },
"other": { "read": true, "write": false }
}
}
}
}

Expected:

DENY
(no authentication)

Key Concepts Demonstrated

1. Policy Libraries for Reusable Logic​

The unix-perms library contains helper functions that can be imported by any policy. This keeps the main policy clean and makes the logic testable in isolation.

2. Annotation-Based Permission Storage​

Instead of hardcoding permissions in policies, we store them as resource annotations. This allows:

  • Different files to have different permissions
  • Permission changes without policy updates
  • Standard Unix permission semantics

3. Three-Level Permission Hierarchy​

The permission_class function implements the Unix model:

  1. Check if principal is owner
  2. Check if principal is in the file's group
  3. Fall back to "other" permissions

4. Role-Based Superuser Bypass​

The superuser role bypasses all permission checks, similar to Unix root. This is implemented cleanly in the resource policy with an additional allow rule.

5. Operation-to-Permission Mapping​

The required_permission function maps operations like :read, :write, :delete to the corresponding permission bits. This makes it easy to add new operations that map to existing permissions.

Extending This Example

Adding Execute Permission​

To support execute permission (for running scripts):

required_permission(operation) := "execute" if {
some suffix in {":execute", ":run"}
endswith(operation, suffix)
}

Update the mode annotations to include execute:

{
"mode": {
"owner": { "read": true, "write": true, "execute": true },
"group": { "read": true, "write": false, "execute": true },
"other": { "read": true, "write": false, "execute": false }
}
}

Adding Sticky Bit / SetUID Semantics​

For special bits, add additional annotations:

{
"sticky": true,
"setuid": false,
"setgid": true
}

Then check these in the policy before allowing certain operations.

ACL Extension​

For Access Control Lists beyond owner/group/other:

{
"acl": [
{ "principal": "bob", "read": true, "write": true },
{ "principal": "mrn:iam:group:auditors", "read": true, "write": false }
]
}

Add a check in the policy that evaluates ACL entries before falling back to standard permissions.