Security and Policy
Fluree provides comprehensive security features including authentication, fine-grained access control through policies, and transparent encryption of data at rest.
Authentication
Authentication
Fluree's authentication model, covering:
- Identity vs transport (DIDs, signed requests, Bearer tokens)
- Three auth modes: decentralized did:key, standalone server tokens, OIDC/OAuth2
- Bearer token claim set and scope definitions
- Replication vs query access boundary
- Token verification paths (Ed25519 + OIDC/JWKS)
Data Encryption
Storage Encryption
Protect data at rest with AES-256-GCM encryption:
- Transparent encryption/decryption
- Environment variable key configuration
- Portable ciphertext format
- Key rotation support
Commit Integrity
Commit Signing and Attestation
Cryptographic proof of which node wrote a commit:
- Ed25519 signatures over domain-separated commit digests
- Embedded signature blocks in commit files
- did:key signer identities
- Future: detached attestations and consensus policies
Policy System
Policy Model and Inputs
Understanding Fluree's policy architecture:
- Policy structure and syntax
- Subject, action, resource model
- Policy evaluation order
- Input data for policy decisions
- Default allow vs default deny
Policy in Queries
How policies affect query execution:
- Query-time filtering
- Result set restrictions
- Pattern-based filtering
- Performance considerations
- Policy debugging for queries
Policy in Transactions
How policies affect transaction operations:
- Transaction validation
- Authorization checks
- Entity-level permissions
- Property-level permissions
- Policy-based retractions
Programmatic Policy API (Rust)
Using policies in Rust applications:
wrap_identity_policy_view- Identity-based policy lookup viaf:policyClasswrap_policy_view- Inline policies withQueryConnectionOptions- Policy precedence rules
- Transaction-side policy enforcement
- Historical views with policy
Key Concepts
Data-Level Security
Fluree enforces security at the data level, not just the application level:
- Users see only authorized data
- Policies applied during query execution
- No unauthorized data leakage
- Transparent to applications
Policy as Data
Policies are stored as RDF triples in the database:
- Version controlled with data
- Query policies like any data
- Time travel for policy history
- Policies can reference other data
Identity-Based Access
Policies use decentralized identifiers (DIDs):
- did:key for cryptographic identity
- did:web for organization identity
- Signed requests link to DID
- Policies grant/deny based on DID
Policy Structure
Basic policy format:
{
"@context": {
"f": "https://ns.flur.ee/db#",
"ex": "http://example.org/ns/"
},
"@id": "ex:read-policy",
"@type": "f:Policy",
"f:subject": "did:key:z6Mkh...",
"f:action": "query",
"f:resource": {
"@type": "schema:Person"
},
"f:allow": true
}
Subject: Who (DID, role, group) Action: What operation (query, transact) Resource: Which data (type, predicate, specific entities) Allow/Deny: Grant or deny access
Policy Enforcement Points
Query Time
Policies filter query results:
SELECT ?name
WHERE {
?person schema:name ?name .
}
Policy filters results to only show authorized people.
Transaction Time
Policies validate transaction operations:
{
"@graph": [
{ "@id": "ex:alice", "schema:age": 31 }
]
}
Policy checks if user can modify ex:alice.
Common Policy Patterns
Allow All (Development)
{
"@id": "ex:allow-all",
"f:subject": "*",
"f:action": "*",
"f:allow": true
}
Role-Based Access
{
"@id": "ex:admin-policy",
"f:subject": { "ex:role": "admin" },
"f:action": "*",
"f:allow": true
}
Resource-Type Based
{
"@id": "ex:public-data-policy",
"f:subject": "*",
"f:action": "query",
"f:resource": { "@type": "ex:PublicData" },
"f:allow": true
}
Property-Level Access
{
"@id": "ex:sensitive-property-policy",
"f:subject": { "ex:role": "hr" },
"f:action": "query",
"f:resource": {
"f:predicate": "ex:salary"
},
"f:allow": true
}
Owner-Based Access
{
"@id": "ex:owner-policy",
"f:subject": "?user",
"f:action": ["query", "transact"],
"f:resource": {
"ex:owner": "?user"
},
"f:allow": true
}
Policy Evaluation
Evaluation Order
- Collect applicable policies based on subject, action, resource
- Evaluate each policy against request context
- Combine results using policy combining algorithm
- Apply default if no policies match
Combining Algorithms
Deny Overrides (default):
- If any policy denies, access denied
- Otherwise, allow if any policy allows
- Default: deny if no matches
Allow Overrides:
- If any policy allows, access granted
- Otherwise, deny if any policy denies
- Default: deny if no matches
Policy Context
Policies have access to runtime context:
Request Context:
- Subject DID
- Action being performed
- Target resource/entity
- Timestamp
Data Context:
- Entity properties
- Related entities
- Graph structure
- Historical data
Example using context:
{
"f:subject": "?user",
"f:resource": {
"ex:department": "?dept"
},
"f:condition": "?user ex:department ?dept",
"f:allow": true
}
Allows access if user is in same department as resource.
Multi-Tenant Policies
Isolate data by tenant:
{
"@id": "ex:tenant-isolation-policy",
"f:subject": "?user",
"f:action": "*",
"f:resource": {
"ex:tenant": "?tenant"
},
"f:condition": "?user ex:tenant ?tenant",
"f:allow": true
}
Users can only access data from their tenant.
Policy Performance
Efficient Policies
Good (specific):
{
"f:resource": { "@type": "ex:PublicData" },
"f:allow": true
}
Less efficient (broad):
{
"f:resource": { "?pred": "?value" },
"f:condition": "complex graph pattern",
"f:allow": true
}
Query Optimization
Policies are optimized during query planning:
- Type-based filters pushed down
- Property filters optimized
- Complex patterns may impact performance
Policy Management
Creating Policies
Policies are created via transactions:
curl -X POST "http://localhost:8090/v1/fluree/upsert?ledger=policies:main" \
-H "Content-Type: application/json" \
-d '{
"@graph": [
{
"@id": "ex:new-policy",
"@type": "f:Policy",
"f:subject": "did:key:z6Mkh...",
"f:action": "query",
"f:allow": true
}
]
}'
Updating Policies
Update using WHERE/DELETE/INSERT:
{
"where": [
{ "@id": "ex:policy-1", "f:allow": "?oldValue" }
],
"delete": [
{ "@id": "ex:policy-1", "f:allow": "?oldValue" }
],
"insert": [
{ "@id": "ex:policy-1", "f:allow": false }
]
}
Policy Versioning
Policies are versioned with data:
- Time travel to see historical policies
- Audit who changed policies when
- Rollback policies if needed
Security Best Practices
1. Principle of Least Privilege
Grant minimum necessary permissions:
// Good: Specific permissions
{
"f:subject": "did:key:z6Mkh...",
"f:action": "query",
"f:resource": { "@type": "ex:PublicData" },
"f:allow": true
}
// Bad: Overly broad
{
"f:subject": "did:key:z6Mkh...",
"f:action": "*",
"f:allow": true
}
2. Default Deny
Start with deny-all, add specific allows:
// Default policy
{
"@id": "ex:default",
"f:subject": "*",
"f:action": "*",
"f:allow": false
}
// Specific allows
{
"@id": "ex:public-read",
"f:subject": "*",
"f:action": "query",
"f:resource": { "@type": "ex:PublicData" },
"f:allow": true
}
3. Use Roles
Define roles, not individual permissions:
{
"@id": "ex:admin-role",
"@type": "ex:Role",
"ex:permissions": ["read", "write", "admin"]
}
{
"@id": "ex:role-policy",
"f:subject": { "ex:hasRole": "ex:admin-role" },
"f:action": "*",
"f:allow": true
}
4. Audit Policy Changes
Track who changes policies:
{
"@id": "ex:policy-audit",
"ex:changedBy": "did:key:z6Mkh...",
"ex:changedAt": "2024-01-22T10:00:00Z",
"ex:reason": "Added read access for contractors"
}
5. Test Policies
Test policies before deploying:
async function testPolicy(policy, testCases) {
for (const testCase of testCases) {
const result = await evaluatePolicy(policy, testCase);
assert.equal(result.allowed, testCase.expected);
}
}
Related Documentation
- Verifiable Data - Cryptographic signatures
- Signed Requests - Request authentication
- Signed Transactions - Transaction signing
- Commit Signing and Attestation - Commit-level signatures