Liking cljdoc? Tell your friends :D

sidebar_position: 2

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](./assets/unix-filesystem.svg)

<SectionHeader icon="version" level={2}>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

<SectionHeader icon="settings" level={2}>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

flowchart TD
    A[Request arrives] --> B{Principal == Owner?}
    B -->|Yes| C[Use owner permissions]
    B -->|No| D{Principal in group?}
    D -->|Yes| E[Use group permissions]
    D -->|No| F[Use other permissions]
    C --> G{Has required permission?}
    E --> G
    F --> G
    G -->|Yes| H[GRANT]
    G -->|No| I[DENY]

<SectionHeader icon="security" level={2}>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

<SectionHeader icon="test" level={2}>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: (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: (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: (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: (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: (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: (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: (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: (no authentication)

<SectionHeader icon="version" level={2}>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.

<SectionHeader icon="build" level={2}>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.

Can you improve this documentation?Edit on GitHub

cljdoc builds & hosts documentation for Clojure/Script libraries

Keyboard shortcuts
Ctrl+kJump to recent docs
Move to previous article
Move to next article
Ctrl+/Jump to the search field
× close