UNIX-Style File Permissions & ACL Implementation¶
Overview¶
This document describes the implementation of UNIX-style file permissions and Access Control Lists (ACLs) for Nexus (Issue #84).
Features Implemented¶
1. UNIX-Style Permissions¶
- Owner, Group, Mode: Standard POSIX permission model
- Permission Bits: Read ®, Write (w), Execute (x) for Owner/Group/Other
- Mode Support: Both octal (0o755) and symbolic (rwxr-xr-x) formats
2. Access Control Lists (ACLs)¶
- Fine-grained Access Control: Per-user and per-group permissions
- Deny Entries: Explicit access denial (takes priority)
- Flexible Entry Types: user, group, mask, other
3. CLI Commands¶
nexus chmod <mode> <path>- Change file permissionsnexus chown <owner> <path>- Change file ownernexus chgrp <group> <path>- Change file groupnexus getfacl <path>- Display ACL entriesnexus setfacl <entry> <path>- Add/remove ACL entries
Implementation Details¶
Core Modules¶
1. /src/nexus/core/permissions.py¶
UNIX-style permission implementation: - Permission - Permission flags (READ, WRITE, EXECUTE) - FileMode - Permission bits management (0o755, etc.) - FilePermissions - Complete permission information (owner, group, mode) - PermissionChecker - Permission validation logic - parse_mode() - Parse mode from string (octal/symbolic)
Example Usage:
from nexus.core.permissions import FileMode, FilePermissions
# Create permissions
perms = FilePermissions.default("alice", "developers") # rw-r--r--
perms = FilePermissions.default_directory("alice", "developers") # rwxr-xr-x
# Check permissions
can_read = perms.can_read("bob", ["developers"]) # True
can_write = perms.can_write("bob", ["developers"]) # False
# Parse mode
mode = parse_mode("755") # 0o755
mode = parse_mode("rwxr-xr-x") # 0o755
2. /src/nexus/core/acl.py¶
Access Control List implementation: - ACLPermission - ACL permissions (read, write, execute) - ACLEntryType - Entry types (user, group, mask, other) - ACLEntry - Single ACL entry - ACL - Complete ACL with multiple entries - ACLManager - High-level ACL operations
Example Usage:
from nexus.core.acl import ACL, ACLEntry, ACLEntryType, ACLPermission, ACLManager
# Create ACL
acl = ACL.empty()
manager = ACLManager()
# Grant permissions
manager.grant_user(acl, "alice", read=True, write=True)
manager.grant_group(acl, "developers", read=True, execute=True)
# Deny access
manager.deny_user(acl, "bob")
# Check permissions
result = acl.check_permission("alice", [], ACLPermission.READ)
# True = allowed, False = denied, None = no match (use UNIX permissions)
# Parse from string
entry = ACLEntry.from_string("user:alice:rw-")
acl = ACL.from_strings(["user:alice:rw-", "group:developers:r-x"])
Database Schema¶
1. File Permissions (added to file_paths table)¶
ALTER TABLE file_paths ADD COLUMN owner VARCHAR(255);
ALTER TABLE file_paths ADD COLUMN group VARCHAR(255);
ALTER TABLE file_paths ADD COLUMN mode INTEGER; -- Permission bits (e.g., 0o644)
CREATE INDEX idx_file_paths_owner ON file_paths(owner);
CREATE INDEX idx_file_paths_group ON file_paths(group);
2. ACL Entries (new acl_entries table)¶
CREATE TABLE acl_entries (
acl_id VARCHAR(36) PRIMARY KEY,
path_id VARCHAR(36) NOT NULL, -- FK to file_paths
entry_type VARCHAR(20) NOT NULL, -- user, group, mask, other
identifier VARCHAR(255), -- username/groupname (NULL for mask/other)
permissions VARCHAR(10) NOT NULL, -- rwx format
deny BOOLEAN NOT NULL DEFAULT FALSE, -- Deny entry flag
created_at TIMESTAMP NOT NULL,
FOREIGN KEY (path_id) REFERENCES file_paths(path_id) ON DELETE CASCADE
);
CREATE INDEX idx_acl_entries_path_id ON acl_entries(path_id);
CREATE INDEX idx_acl_entries_type_id ON acl_entries(entry_type, identifier);
Database Migration¶
Migration File: /alembic/versions/777350ff28ce_add_unix_permissions_and_acl_support.py
To apply the migration:
To rollback:
Updated Data Models¶
FileMetadata (/src/nexus/core/metadata.py)¶
@dataclass
class FileMetadata:
path: str
backend_name: str
physical_path: str
size: int
# ... existing fields ...
# UNIX-style permissions
owner: str | None = None
group: str | None = None
mode: int | None = None # Permission bits (e.g., 0o644)
FilePathModel (/src/nexus/storage/models.py)¶
class FilePathModel(Base):
# ... existing fields ...
# UNIX-style permissions
owner: Mapped[str | None] = mapped_column(String(255), nullable=True)
group: Mapped[str | None] = mapped_column(String(255), nullable=True)
mode: Mapped[int | None] = mapped_column(Integer, nullable=True)
# Relationships
acl_entries: Mapped[list["ACLEntryModel"]] = relationship(
"ACLEntryModel", back_populates="file_path", cascade="all, delete-orphan"
)
ACLEntryModel (/src/nexus/storage/models.py)¶
class ACLEntryModel(Base):
__tablename__ = "acl_entries"
acl_id: Mapped[str] = mapped_column(String(36), primary_key=True)
path_id: Mapped[str] = mapped_column(String(36), ForeignKey("file_paths.path_id"))
entry_type: Mapped[str] = mapped_column(String(20), nullable=False)
identifier: Mapped[str | None] = mapped_column(String(255), nullable=True)
permissions: Mapped[str] = mapped_column(String(10), nullable=False)
deny: Mapped[bool] = mapped_column(default=False, nullable=False)
created_at: Mapped[datetime] = mapped_column(DateTime, nullable=False)
# Relationship
file_path: Mapped["FilePathModel"] = relationship("FilePathModel", back_populates="acl_entries")
CLI Usage Examples¶
Change File Permissions¶
# Using octal notation
nexus chmod 755 /workspace/script.sh
nexus chmod 0o644 /workspace/data.txt
# Using symbolic notation
nexus chmod rwxr-xr-x /workspace/file.txt
nexus chmod rw-r--r-- /workspace/config.yaml
Change Owner and Group¶
# Change owner
nexus chown alice /workspace/file.txt
# Change group
nexus chgrp developers /workspace/code/
# Both can be used together
nexus chown alice /workspace/file.txt
nexus chgrp developers /workspace/file.txt
View ACL Entries¶
nexus getfacl /workspace/file.txt
# Output:
# file: /workspace/file.txt
# owner: alice
# group: developers
# mode: 0o644 (rw-r--r--)
#
# ACL entries:
# user:bob:r--
# group:admins:r-x
# deny:user:charlie:---
Manage ACL Entries¶
# Grant user read+write
nexus setfacl user:alice:rw- /workspace/file.txt
# Grant group read+execute
nexus setfacl group:developers:r-x /workspace/code/
# Deny user access
nexus setfacl deny:user:bob /workspace/secret.txt
# Remove ACL entry
nexus setfacl user:alice:rwx /workspace/file.txt --remove
Testing¶
Unit Tests¶
Test Files: - /tests/unit/test_permissions.py - 46 tests for UNIX permissions - /tests/unit/test_acl.py - 45 tests for ACL functionality
Run Tests:
# Run all permission tests
pytest tests/unit/test_permissions.py tests/unit/test_acl.py -v
# Run with coverage
pytest tests/unit/test_permissions.py tests/unit/test_acl.py --cov=nexus.core.permissions --cov=nexus.core.acl
Test Coverage: - permissions.py: 95% coverage - acl.py: 90% coverage - Total: 91 passing tests
Permission Evaluation Order¶
When checking file access, Nexus uses the following order:
- ACL Deny Entries - Explicit denials (highest priority)
- ACL Allow Entries - Explicit permissions
- UNIX Permissions - Owner/Group/Other based on mode
- Default - Deny if no match (for security)
Example:
# File permissions: rw-r--r-- (owner=alice, group=developers)
# ACL: user:bob:rw-, deny:user:charlie:---
# Alice (owner): Can read+write (UNIX permissions)
# Bob: Can read+write (ACL allows)
# Charlie: Denied (ACL denies)
# Dave (in developers): Can read (UNIX group permissions)
# Eve (not in group): Can read (UNIX other permissions)
Backward Compatibility¶
- Existing Files: Files without permissions work as before (all access allowed)
- Optional Fields: owner/group/mode fields are nullable in database
- Permission Checker: Returns True for None permissions (backward compatible)
- No Breaking Changes: Existing code continues to work
Future Enhancements¶
The current implementation provides the foundation for:
- ReBAC (Relationship-Based Access Control) - Zanzibar-style authorization
- Permission Inheritance - Automatic permission assignment based on paths
- Permission Policies - Default policies per namespace
- Permission Checking in Operations - Integrate into filesystem operations
- Web UI - Graphical permission management interface
References¶
- Issue: https://github.com/nexi-lab/nexus/issues/84
- POSIX Permissions: https://en.wikipedia.org/wiki/File-system_permissions#Unix_permissions
- POSIX ACLs: https://www.usenix.org/legacy/publications/library/proceedings/usenix03/tech/freenix03/full_papers/gruenbacher/gruenbacher_html/main.html
Migration Guide¶
For Existing Installations¶
-
Backup Database
-
Apply Migration
-
Set Default Permissions (optional)
from nexus import connect from nexus.core.permissions import FilePermissions, FileMode nx = connect() # Set permissions for existing files for path in nx.list("/", recursive=True): meta = nx.metadata.get(path) if meta and not meta.owner: meta.owner = "root" meta.group = "root" meta.mode = 0o644 # Default: rw-r--r-- nx.metadata.put(meta) -
Verify
Contributors¶
Implementation by Claude (Anthropic AI Assistant) based on requirements from issue #84.
License¶
Apache 2.0 - See LICENSE file for details.