Packages

AWS IAM-style policy evaluation for Ash Framework

Current section

Files

Jump to
ash_iam README.md
Raw

README.md

# AshIAM
AWS IAM-style policy evaluation for Ash Framework.
This extension provides IAM-style authorization for Ash resources using AWS IAM-like policy documents. It supports wildcard matching, deny precedence, configurable policy sources, multiple policy documents, and both CRUD and generic actions.
## Features
- **AWS IAM-compatible policy evaluation** - Uses the same logic as AWS IAM
- **High-performance authorization** - Sub-microsecond evaluation with regex caching
- **Multiple policy documents** - Support for both single and multiple policy documents
- **Deny precedence** - Explicit deny statements override allow statements
- **Wildcard matching** - Support for wildcard patterns in resources and actions
- **Configurable policy sources** - Get policies from actor attributes or custom fetchers
- **Complete Ash integration** - Supports both CRUD actions (with filters) and generic actions (with simple checks)
- **Flexible action mapping** - Map Ash actions to custom IAM verbs for cleaner policies
## Performance
AshIam is optimized for production use with much Claude README enthusiasm!
- **~2μs average evaluation time** for simple policies
- **100x+ performance improvement** over basic implementations
- **Regex pattern caching** to avoid recompilation
- **Early termination** on explicit deny statements
- **ETS-based caching** for compiled patterns
See [PERFORMANCE.md](PERFORMANCE.md) for detailed benchmarks and optimization guide.
## Installation
The package can be installed by adding `ash_iam` to your list of dependencies in `mix.exs`:
```elixir
def deps do
[
{:ash_iam, "~> 1.1.0"}
]
end
```
## Quick Start
1. Add the extension to your resource:
```elixir
defmodule MyApp.User do
use Ash.Resource,
domain: MyApp.Domain,
data_layer: Ash.DataLayer.Ets,
authorizers: [Ash.Policy.Authorizer],
extensions: [AshIam]
# ... your resource definition
iam do
permission_base "myapp:user"
end
end
```
2. Provide IAM policy documents in your actor:
```elixir
actor = %{
iam_policy: %{
"Statement" => [
%{"Effect" => "Allow", "Action" => ["*"], "Resource" => ["myapp:user:*"]},
%{"Effect" => "Deny", "Action" => ["destroy"], "Resource" => ["myapp:user:5"]}
]
}
}
# Policies are automatically evaluated by Ash
MyApp.User |> Ash.read(actor: actor)
```
## Policy Format
Policies follow AWS IAM JSON format:
```elixir
%{
"Statement" => [
%{
"Effect" => "Allow" | "Deny",
"Action" => ["action1", "action2", "*"],
"Resource" => ["resource:pattern:*", "*"]
}
]
}
```
Multiple policy documents are also supported:
```elixir
[
%{"Statement" => [...]},
%{"Statement" => [...]}
]
```
## Configuration
### Resource Configuration
- `permission_base` - The base resource identifier (required)
- `action_to_iam_mapping` - Maps Ash actions to IAM verbs
- `policy_key` - Actor attribute containing the policy (default: `:iam_policy`)
- `policy_fetcher` - Custom function to fetch policies
### Application Configuration
```elixir
config :ash_iam, iam_stem: "production"
```
This adds a prefix to all permission bases during evaluation.
## Generic Actions
AshIAM fully supports Ash generic actions alongside traditional CRUD operations. Generic actions use simple authorization checks (not filters) since they don't operate on record sets.
### Basic Generic Action Example
```elixir
defmodule MyApp.ReportResource do
use Ash.Resource,
domain: MyApp.Domain,
data_layer: Ash.DataLayer.Ets,
authorizers: [Ash.Policy.Authorizer],
extensions: [AshIam]
actions do
# Regular CRUD actions work as before
defaults [:create, :read, :update, :destroy]
# Generic actions are now supported
action :export_to_xlsx, :string do
argument :format, :string, allow_nil?: false
argument :include_headers, :boolean, default: true
run fn input, _context ->
format = input.arguments.format
headers = input.arguments.include_headers
result = "Exported data in #{format} format (headers: #{headers})"
{:ok, result}
end
end
action :send_notification do
argument :message, :string, allow_nil?: false
argument :recipient, :string, allow_nil?: false
run fn input, _context ->
# Send notification logic here
:ok
end
end
end
iam do
permission_base "myapp:report"
action_to_iam_mapping create: :create,
read: :read,
update: :update,
delete: :delete,
export_to_xlsx: :export,
send_notification: :notify
end
end
```
### Using Generic Actions with IAM Policies
```elixir
# Actor with permission to export but not send notifications
actor = %{
iam_policy: %{
"Statement" => [
%{"Effect" => "Allow", "Action" => ["export"], "Resource" => ["myapp:report:*"]},
%{"Effect" => "Deny", "Action" => ["notify"], "Resource" => ["myapp:report:*"]}
]
}
}
# This will work
{:ok, result} =
MyApp.ReportResource
|> Ash.ActionInput.for_action(:export_to_xlsx, %{format: "xlsx"})
|> Ash.run_action(actor: actor)
# This will be denied with Ash.Error.Forbidden
try do
MyApp.ReportResource
|> Ash.ActionInput.for_action(:send_notification, %{message: "Hi", recipient: "user@example.com"})
|> Ash.run_action!(actor: actor)
rescue
Ash.Error.Forbidden -> # Authorization failed
end
```
### How It Works
- **CRUD actions** (create, read, update, destroy) use **filter checks** for query-level authorization
- **Generic actions** use **simple checks** for straightforward allow/deny decisions
- **Same IAM policies** work for both action types using your `action_to_iam_mapping`
- **Automatic detection** - AshIAM automatically detects action types and applies the correct check
Documentation can be found at [https://hexdocs.pm/ash_iam](https://hexdocs.pm/ash_iam).