Row-Level Security (RLS) in Geode provides fine-grained access control at the node and relationship level, allowing you to restrict which graph elements users can access based on flexible policy rules. This is essential for multi-tenant applications, data privacy compliance, and protecting sensitive information within shared graph databases.
Understanding Row-Level Security
Traditional database access control operates at the table or column level, granting or denying access to entire datasets. Row-Level Security goes further by controlling access to individual rows (in Geode’s case, nodes and relationships) based on the context of the query and the user executing it.
In graph databases, RLS enables:
- Multi-tenant isolation: Different customers see only their data
- Data compartmentalization: Users access only data relevant to their role
- Privacy compliance: Automatic filtering of sensitive information
- Hierarchical access: Organizational structures reflected in data access
- Attribute-based access: Access based on node properties and relationships
RLS Policy Basics
RLS policies in Geode consist of:
- Target: The node labels or relationship types the policy applies to
- Operation: SELECT, INSERT, UPDATE, or DELETE
- Role: Which users/roles the policy applies to
- Condition: A GQL expression that determines access
Creating Basic Policies
-- Only allow users to see their own data
CREATE POLICY user_isolation
ON Person
FOR SELECT
TO authenticated_user
USING (person_id = current_user());
-- Allow managers to see their team's data
CREATE POLICY manager_team_access
ON Employee
FOR SELECT
TO manager
USING (manager_id = current_user());
-- Allow admins to see everything
CREATE POLICY admin_full_access
ON Person
FOR ALL
TO admin
USING (true);
Policy Evaluation
When a query executes, Geode:
- Identifies the user’s roles
- Finds all applicable RLS policies
- Combines policy conditions with AND logic for restrictive policies
- Combines policy conditions with OR logic for permissive policies
- Automatically adds conditions to query WHERE clauses
-- User executes this query
MATCH (p:Person)
RETURN p.name, p.email;
-- With user_isolation policy, Geode executes
MATCH (p:Person)
WHERE p.person_id = current_user() -- Automatically added by RLS
RETURN p.name, p.email;
Multi-Tenant Applications
RLS is ideal for SaaS applications serving multiple customers:
Tenant Isolation
-- Create tenant isolation policy
CREATE POLICY tenant_isolation
ON (n) -- Applies to all node types
FOR ALL
TO tenant_user
USING (n.tenant_id = current_tenant_id());
-- Optionally, restrict at relationship level too
CREATE POLICY tenant_relationship_isolation
ON ()-[r]-()
FOR ALL
TO tenant_user
USING (start_node(r).tenant_id = current_tenant_id()
AND end_node(r).tenant_id = current_tenant_id());
Every node includes a tenant_id property:
-- Create customer data with tenant ID
CREATE (:Customer {
name: 'Acme Corp',
tenant_id: 'tenant_123',
email: 'contact@acme.com'
});
-- User from tenant_123 can see this
-- Users from other tenants cannot
Shared Reference Data
Some data may be shared across tenants:
-- Allow all tenants to see shared reference data
CREATE POLICY shared_data_access
ON ReferenceData
FOR SELECT
TO tenant_user
USING (is_shared = true OR tenant_id = current_tenant_id());
-- Create shared data
CREATE (:ReferenceData {
type: 'country',
name: 'United States',
is_shared: true
});
Hierarchical Access Control
Model organizational hierarchies with RLS:
-- Employees can see their own data
CREATE POLICY employee_self_access
ON Employee
FOR SELECT
TO employee
USING (employee_id = current_user());
-- Managers can see their direct reports
CREATE POLICY manager_reports_access
ON Employee
FOR SELECT
TO manager
USING (
manager_id = current_user()
OR employee_id = current_user()
);
-- Directors can see entire department
CREATE POLICY director_department_access
ON Employee
FOR SELECT
TO director
USING (
department IN get_managed_departments(current_user())
OR employee_id = current_user()
);
-- Executives can see everything
CREATE POLICY executive_all_access
ON Employee
FOR SELECT
TO executive
USING (true);
Graph-Based Hierarchy
Use the graph structure itself for hierarchical access:
-- Access based on organizational graph structure
CREATE POLICY org_hierarchy_access
ON Employee
FOR SELECT
TO employee
USING (
EXISTS {
MATCH path = (current:Employee {id: current_user()})-[:MANAGES*0..]->(e:Employee)
WHERE e = this -- 'this' refers to the current Employee node being checked
}
);
Attribute-Based Access Control
Define access based on node properties:
-- Access based on security clearance level
CREATE POLICY clearance_based_access
ON Document
FOR SELECT
TO employee
USING (
required_clearance <= get_user_clearance(current_user())
);
-- Access based on data classification
CREATE POLICY classification_access
ON DataAsset
FOR SELECT
TO analyst
USING (
classification IN ['public', 'internal']
OR (classification = 'confidential' AND user_has_approval(current_user(), this.id))
);
-- Time-based access
CREATE POLICY time_limited_access
ON TemporaryData
FOR SELECT
TO user
USING (
valid_from <= current_timestamp()
AND valid_until >= current_timestamp()
);
Column-Level Security with RLS
Restrict access to specific properties:
-- Policy that only exposes certain properties
CREATE POLICY employee_limited_view
ON Employee
FOR SELECT
TO public
WITH COLUMNS (name, title, department, email)
USING (is_public_directory_enabled = true);
-- Sensitive properties like salary not included
MATCH (e:Employee)
RETURN e.name, e.salary; -- salary is NULL for public role
Conditional Insert/Update Policies
Control data modifications with RLS:
-- Users can only insert data for their tenant
CREATE POLICY tenant_insert_restriction
ON (n)
FOR INSERT
TO tenant_user
USING (n.tenant_id = current_tenant_id());
-- Users can only update their own data
CREATE POLICY self_update_only
ON Person
FOR UPDATE
TO authenticated_user
USING (person_id = current_user());
-- Prevent deletion of archived records
CREATE POLICY prevent_archive_deletion
ON Record
FOR DELETE
TO user
USING (status != 'archived');
Policy Functions
Create reusable functions for complex policies:
-- Define helper function
CREATE FUNCTION is_accessible_to_user(node_id STRING, user_id STRING)
RETURNS BOOLEAN
AS $$
MATCH (u:User {id: $user_id})
MATCH (n {id: $node_id})
RETURN EXISTS {
MATCH (u)-[:HAS_ACCESS|MANAGES*1..3]->(n)
}
$$;
-- Use in policy
CREATE POLICY complex_access_policy
ON Document
FOR SELECT
TO user
USING (is_accessible_to_user(this.id, current_user()));
Performance Optimization
RLS policies can impact query performance if not carefully designed:
Index Filtered Properties
-- Create index on frequently filtered property
CREATE INDEX tenant_id_index ON (n) FOR (n.tenant_id);
-- Policy uses indexed property
CREATE POLICY tenant_isolation
ON (n)
FOR SELECT
TO tenant_user
USING (n.tenant_id = current_tenant_id()); -- Uses index
Avoid Complex Subqueries
-- Inefficient: Complex EXISTS subquery for every node
CREATE POLICY slow_policy
ON Document
FOR SELECT
TO user
USING (
EXISTS {
MATCH (u:User {id: current_user()})-[:BELONGS_TO]->(g:Group)-[:CAN_ACCESS]->(this)
}
);
-- More efficient: Pre-compute accessible documents
CREATE POLICY fast_policy
ON Document
FOR SELECT
TO user
USING (
this.id IN get_accessible_documents(current_user()) -- Cached function
);
Policy Caching
# Enable policy evaluation caching
geode serve --rls-cache-enabled=true \
--rls-cache-size=100MB \
--rls-cache-ttl=300s
Testing RLS Policies
Verify policies work correctly:
-- Test as specific user
SET ROLE 'employee';
SET SESSION current_user = 'alice@example.com';
MATCH (e:Employee)
RETURN count(e); -- Should only see accessible employees
-- Test as admin
SET ROLE 'admin';
MATCH (e:Employee)
RETURN count(e); -- Should see all employees
-- Verify policy enforcement
EXPLAIN MATCH (e:Employee) RETURN e;
-- Plan should show RLS filter: WHERE e.employee_id = current_user()
Automated Policy Testing
# Run RLS policy test suite
geode test-rls --policy=employee_access \
--test-users=alice,bob,admin \
--expected-results=test-cases.json
Policy Management
Viewing Policies
-- List all policies
SHOW POLICIES;
-- Show policies for specific label
SHOW POLICIES ON Employee;
-- Show policy details
DESCRIBE POLICY employee_self_access;
Modifying Policies
-- Drop policy
DROP POLICY employee_self_access;
-- Recreate with updated condition
CREATE POLICY employee_self_access
ON Employee
FOR SELECT
TO employee
USING (employee_id = current_user() OR is_manager = true);
-- Disable policy temporarily
ALTER POLICY employee_self_access DISABLE;
-- Re-enable policy
ALTER POLICY employee_self_access ENABLE;
Best Practices
- Start with deny-all: Create restrictive policies by default, then grant access
- Use indexed properties: Filter on indexed properties for performance
- Test thoroughly: Verify policies with different user roles and scenarios
- Keep policies simple: Complex policies are hard to maintain and slow to evaluate
- Document policies: Clearly document the intent and scope of each policy
- Monitor performance: Track query performance impact of RLS policies
- Use policy functions: Extract complex logic into reusable functions
- Regular audits: Periodically review and update policies
- Principle of least privilege: Grant minimum necessary access
- Combine with RBAC: Use RLS together with role-based access control
Common Patterns
Department-Based Access
CREATE POLICY department_isolation
ON Employee
FOR SELECT
TO department_user
USING (department = get_user_department(current_user()));
Time-Based Access
CREATE POLICY business_hours_access
ON SensitiveData
FOR SELECT
TO employee
USING (
extract_hour(current_timestamp()) BETWEEN 9 AND 17
AND extract_dow(current_timestamp()) BETWEEN 1 AND 5
);
Geographic Restrictions
CREATE POLICY geographic_restriction
ON CustomerData
FOR SELECT
TO analyst
USING (region IN get_user_regions(current_user()));
Data Owner Access
CREATE POLICY owner_access
ON Document
FOR ALL
TO user
USING (created_by = current_user() OR owner = current_user());
Troubleshooting
Policy Not Applied
Check that:
- User has correct role assigned
- Policy target matches query pattern
- Policy is enabled:
SHOW POLICIES - No conflicting policies
Performance Issues
- Add indexes on filtered properties
- Simplify policy conditions
- Use policy evaluation caching
- Monitor with
EXPLAINto see added filters
Access Denied Unexpectedly
- Review all applicable policies with
SHOW POLICIES - Test policy condition manually
- Check if multiple policies conflict
- Verify user role and session context
Related Topics
- Compliance - Regulatory compliance with RLS
- Audit Logging - Track RLS policy enforcement
- Authentication - User authentication and identity
- Authorization - Role-based access control
- Performance - Optimizing RLS policy performance
- Security Overview - Security best practices