acton-service 0.23.0

Production-ready Rust backend framework with type-enforced API versioning
Documentation
# Cedar Authorization Example

This example shows how to implement fine-grained authorization using AWS Cedar policies with acton-service. Cedar allows you to define who can do what with which resources using declarative policy files.

**What you'll learn:**
- Policy-based access control (admin vs user roles)
- Resource ownership patterns (users can only access their own documents)
- Custom path normalization for alphanumeric IDs
- JWT authentication + Cedar authorization (layered security)
- Optional Redis caching for policy decisions

**Auto-setup:** The example automatically creates all necessary configuration files on first run.

## Quick Start

```bash
cargo run --example cedar-authz --features cedar-authz,cache
```

That's it! The example automatically creates `~/.config/acton-service/cedar-authz-example/` with:
- `policies.cedar` - Cedar policy definitions
- `jwt-public.pem` - JWT public key for token validation
- `config.toml` - Service configuration

Server starts on `http://localhost:8080`

**Optional:** For faster policy decisions, start Redis for caching:
```bash
docker run -d -p 6379:6379 redis:latest
```
Without Redis, policy decisions take 10-50ms instead of 1-5ms (still perfectly usable).

## Testing

### Step 1: Verify Health Endpoints (No Auth Required)

```bash
# Health check - should return 200 OK
curl http://localhost:8080/health

# Readiness check - should return 200 OK
curl http://localhost:8080/ready
```

### Step 2: Test Without Authentication (Should Fail)

```bash
# Try to access documents without a token - should return 401 Unauthorized
curl http://localhost:8080/api/v1/documents
```

### Step 3: Generate Test JWT Tokens

Install PyJWT (using uv for fast installation):

```bash
# Create virtual environment and install PyJWT
uv venv .venv
source .venv/bin/activate
uv pip install pyjwt cryptography
```

Generate tokens with Python:

```python
import jwt
from datetime import datetime, timedelta, UTC

# Read the JWT private key (included in examples/)
with open("acton-service/examples/jwt-private.pem", "r") as f:
    private_key = f.read()

# Generate USER token (regular user)
user_payload = {
    "sub": "user:123",
    "username": "alice",
    "email": "alice@example.com",
    "roles": ["user"],  # Regular user role
    "perms": ["read:documents", "write:documents"],
    "exp": int((datetime.now(UTC) + timedelta(hours=1)).timestamp()),
    "iat": int(datetime.now(UTC).timestamp()),
    "jti": "test-user-token"
}
user_token = jwt.encode(user_payload, private_key, algorithm="RS256")
print("USER TOKEN:")
print(user_token)
print()

# Generate ADMIN token (admin user)
admin_payload = {
    "sub": "user:456",
    "username": "bob",
    "email": "bob@example.com",
    "roles": ["user", "admin"],  # Admin role
    "perms": ["read:documents", "write:documents", "admin:all"],
    "exp": int((datetime.now(UTC) + timedelta(hours=1)).timestamp()),
    "iat": int(datetime.now(UTC).timestamp()),
    "jti": "test-admin-token"
}
admin_token = jwt.encode(admin_payload, private_key, algorithm="RS256")
print("ADMIN TOKEN:")
print(admin_token)
```

Save the tokens for testing:

```bash
export USER_TOKEN="<paste-user-token-here>"
export ADMIN_TOKEN="<paste-admin-token-here>"
```

### Step 4: Test Cedar Authorization Policies

**Test 1: User can list documents** ✅
```bash
curl -H "Authorization: Bearer $USER_TOKEN" \
     http://localhost:8080/api/v1/documents

# Expected: 200 OK with documents array
# [{"id":"doc1","owner_id":"user123","title":"My Document",...},...]
```

**Test 2: User CANNOT access admin endpoint** ❌
```bash
curl -H "Authorization: Bearer $USER_TOKEN" \
     http://localhost:8080/api/v1/admin/users

# Expected: 403 Forbidden
# {"error":"Access denied by policy","code":"FORBIDDEN","status":403}
```

**Test 3: Admin CAN access admin endpoint** ✅
```bash
curl -H "Authorization: Bearer $ADMIN_TOKEN" \
     http://localhost:8080/api/v1/admin/users

# Expected: 200 OK with users array
# [{"id":"user123","username":"alice","roles":["user"]},...]
```

**Test 4: User can create documents** ✅
```bash
curl -X POST \
     -H "Authorization: Bearer $USER_TOKEN" \
     -H "Content-Type: application/json" \
     -d '{"id":"doc-new","owner_id":"user123","title":"New Document","content":"Test"}' \
     http://localhost:8080/api/v1/documents

# Expected: 200 OK with created document
# {"id":"doc-new","owner_id":"user123",...}
```

**Test 5: Get specific document** (Ownership check)
```bash
curl -H "Authorization: Bearer $USER_TOKEN" \
     http://localhost:8080/api/v1/documents/user123/doc1

# Expected: 200 OK if user:123 matches the user_id in path
```

**Test 6: Update document** (Owner only)
```bash
curl -X PUT \
     -H "Authorization: Bearer $USER_TOKEN" \
     -H "Content-Type: application/json" \
     -d '{"id":"doc1","owner_id":"user123","title":"Updated","content":"New"}' \
     http://localhost:8080/api/v1/documents/user123/doc1

# Expected: 200 OK if user owns the document
```

**Test 7: Delete document** (Owner or admin)
```bash
curl -X DELETE \
     -H "Authorization: Bearer $USER_TOKEN" \
     http://localhost:8080/api/v1/documents/user123/doc1

# Expected: 200 OK if user owns the document
```

### 3. Test with Different Roles

Generate tokens with different roles to test authorization:

**Regular user (can only access own documents):**
```python
payload = {
    "sub": "user:123",
    "roles": ["user"],
    # ... other fields
}
```

**Admin user (can access everything):**
```python
payload = {
    "sub": "user:456",
    "roles": ["user", "admin"],
    # ... other fields
}
```

## Cedar Policy Explanation

The example policies demonstrate common patterns:

### 1. Admin Override
```cedar
permit(principal, action, resource)
when { principal.roles.contains("admin") };
```
Admins can do everything.

### 2. Resource Listing
```cedar
permit(
    principal,
    action == Action::"GET /api/v1/documents",
    resource
);
```
Any authenticated user can list documents.

### 3. Ownership-based Access
```cedar
permit(
    principal,
    action in [Action::"GET /api/v1/documents/{user_id}/{doc_id}", ...],
    resource
)
when { principal.sub == resource.owner_id };
```
Users can only access their own documents.

### 4. Forbid with Unless (Restrictive)
```cedar
forbid(
    principal,
    action == Action::"GET /api/v1/admin/users",
    resource
)
unless { principal.roles.contains("admin") };
```
Only admins can access admin endpoints.

## How It Works

**Request Flow:**
```
Client Request
JWT Authentication (validates token, extracts claims)
Cedar Authorization (evaluates policies)
Your Handler
```

**Cedar evaluates each request using:**

- **Principal** (who): Extracted from JWT claims (`sub`, `roles`, `perms`, etc.)
- **Action** (what): HTTP method + normalized path (e.g., `GET /api/v1/documents/{user_id}/{doc_id}`)
- **Resource** (which): Path parameters or request body attributes (`owner_id`, etc.)
- **Context** (when/where): Request metadata (`ip_address`, `timestamp`, etc.)

**Decision:**
- If any `forbid` policy matches → Deny
- Else if any `permit` policy matches → Allow
- Otherwise → Deny (default)

**Caching (optional):**
Redis caching reduces latency from 10-50ms to 1-5ms by caching policy decisions with a configurable TTL (default 5 minutes).

## Configuration Options

### Cedar Configuration

```toml
[cedar]
enabled = true                      # Enable/disable Cedar authorization
policy_path = "path/to/policies.cedar"  # Path to policy file
hot_reload = false                  # [IN PROGRESS] Automatic policy file watching
hot_reload_interval_secs = 60       # [IN PROGRESS] Check interval for hot-reload
cache_enabled = true                # Enable policy decision caching
cache_ttl_secs = 300                # Cache TTL in seconds
fail_open = false                   # true = allow on errors, false = deny on errors
```

**Note**: Automatic hot-reload is currently in progress. Use the manual reload endpoint (`POST /admin/reload-policies`) to reload policies without restarting the service.

### Fail-Open vs Fail-Closed

**Fail-Closed (Recommended for Production)**:
```toml
fail_open = false
```
- Deny requests if policy evaluation fails
- More secure
- May cause downtime if policies are misconfigured

**Fail-Open (Development Only)**:
```toml
fail_open = true
```
- Allow requests if policy evaluation fails
- Less secure
- Useful for debugging policy issues

## Troubleshooting

### 403 Forbidden

**Symptom**: All requests return 403 Forbidden

**Possible causes**:
1. Cedar is enabled but policies are too restrictive
2. Policy file not found or invalid
3. JWT claims don't match policy conditions

**Solutions**:
- Check logs for Cedar evaluation details
- Verify policy file exists and is valid
- Ensure JWT contains required claims (`roles`, `sub`, etc.)
- Set `fail_open = true` temporarily to debug

### 500 Internal Server Error

**Symptom**: Requests return 500 errors

**Possible causes**:
1. Policy file syntax errors
2. Policy evaluation errors
3. Cache connection issues (if enabled)

**Solutions**:
- Check logs for policy parsing errors
- Validate policy syntax
- Verify Redis is running (if cache enabled)

### Policy Not Reloading

**Symptom**: Policy changes don't take effect

**Current Status**: Automatic hot-reload is in progress. For now, policies must be reloaded manually.

**Solutions**:
- Use the manual reload endpoint: `POST /admin/reload-policies` (requires admin role)
- Restart the service
- Check file permissions on policy file

**Future**: Automatic file watching and hot-reload will be implemented soon.

## Performance Tips

1. **Enable caching**: Reduces latency by 90%
2. **Use simple policies**: Complex policies increase evaluation time
3. **Cache warm-up**: First requests may be slower
4. **Monitor cache hit rate**: Aim for >80%

## Security Best Practices

1. **Always use fail-closed in production**: `fail_open = false`
2. **Validate JWT properly**: Use strong algorithms (RS256, ES256)
3. **Least privilege**: Only grant necessary permissions
4. **Audit policies regularly**: Review and update policies
5. **Use forbid for sensitive operations**: Explicit denials are safer
6. **Secure policy reload endpoint**: Protect `/admin/reload-policies` with admin-only access
7. **Secure policy files**: Restrict file permissions (automatic hot-reload in progress)

## Next Steps

1. **Add more policies**: Extend the example with your use cases
2. **Integrate with database**: Load resource attributes from DB
3. **Add gRPC support**: Use `CedarAuthzLayer` for gRPC services
4. **Implement policy management API**: CRUD operations for policies
5. **Add policy testing**: Unit tests for Cedar policies
6. **Monitor policy decisions**: Track allow/deny metrics

## References

- [Cedar Policy Language Documentation]https://docs.cedarpolicy.com/
- [Cedar Rust Crate Documentation]https://docs.rs/cedar-policy/
- [acton-service Documentation]https://github.com/Govcraft/acton-service