How to Implement Column-Level Security and Dataset-Level Access Control in Apache Superset
Column-level security in Apache Superset is enforced through Row Level Security (RLS) filters that inject SQL clauses into queries, while dataset-level access control uses FAB permissions evaluated via get_dataset_access_filters() in the security manager.
Apache Superset implements granular data protection through two complementary mechanisms living in the security manager layer. This guide explains how to configure both dataset-level access control and column-level security using the native RowLevelSecurityFilter system and permission model. You will learn to leverage the REST API, Python client, and underlying source code in superset/security/manager.py to enforce fine-grained access policies.
Understanding the Two Security Layers
Superset separates data access into dataset-level permissions and column-level (row-level) filters. Both mechanisms are evaluated during query construction in SQL Lab, Explore, and chart rendering to ensure the database never returns unauthorized data.
Dataset-Level Access Control
Dataset-level security protects entire tables or virtual datasets using Flask-AppBuilder (FAB) permissions. The SupersetSecurityManager.can_access_datasource() method (line 649 in superset/security/manager.py) calls raise_for_access() which uses get_dataset_access_filters() from superset/utils/filters.py to generate a SQLAlchemy OR clause. This clause checks if the user owns a database permission, catalog permission, schema permission, or explicit datasource permission string such as "[my_db].[my_table](id:42)".
Column-Level Security via Row Level Security (RLS)
Column-level restrictions are implemented through RowLevelSecurityFilter objects stored in the row_level_security_filters table. During query execution (lines 2728-2849 in superset/security/manager.py), the security manager joins the user’s roles with RLS filters via the rls_filter_roles association table and injects the filter’s clause text into the WHERE clause. The filter_type field (REGULAR or BASE) determines whether the clause is AND-ed with other filters or serves as a default base filter when no regular filter exists.
Implementing Dataset-Level Access Control
Every SqlaTable computes a permission string via get_perm() in superset/connectors/sqla/models.py. To grant access, assign this permission to a role.
- Identify the dataset permission string through the UI at Datasets → List → … → Permissions or programmatically via
DatasetDAO.find_by_id(id).perm. - Grant the permission to a role using the Security → List Roles UI or the REST API.
- Validate access by attempting to query the dataset; unauthorized users receive a
DATASOURCE_SECURITY_ACCESS_ERRORdefined insuperset/exceptions.py.
Granting Dataset Permissions via REST API
# Authenticate and obtain JWT
TOKEN=$(curl -s -X POST http://localhost:8088/api/v1/security/login \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"admin"}' | jq -r .access_token)
# Grant permission for dataset id 42 to role id 5
curl -X POST "http://localhost:8088/api/v1/security/role/5/permissions" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"permissions":["[my_db].[my_table](id:42)"]}'
The helper get_dataset_access_filters() in superset/utils/filters.py (lines 24-44) automatically combines permissions. It grants access if the user holds all_datasource_access, explicit datasource_access, or broader catalog_access, schema_access, or database_access permissions covering the dataset’s location.
Implementing Column-Level Security with RLS
RLS filters allow you to inject arbitrary SQL expressions referencing specific columns into every query. This effectively restricts which rows and column values users can see based on their role memberships.
- Create the RLS filter with a
clausetargeting specific columns (e.g.,department = 'finance'). - Assign to roles via the Security → Row Level Security UI or the REST API endpoint defined in
superset/row_level_security/api.py. - Filter application happens automatically during query building; regular filters are
AND-ed together while base filters apply only when no regular filter exists for a user's roles.
Adding RLS Filters via Python Client
from superset import db
from superset.connectors.sqla.models import RowLevelSecurityFilter, SqlaTable
from superset.security.manager import security_manager
from superset import utils
# Identify target dataset
dataset = db.session.query(SqlaTable).filter_by(id=42).one()
# Create RLS filter restricting to finance department
rls = RowLevelSecurityFilter(
clause="department = 'finance'",
filter_type=utils.RowLevelSecurityFilterType.REGULAR,
tables=[dataset],
)
db.session.add(rls)
db.session.flush()
# Associate with Finance role
finance_role = security_manager.find_role("Finance")
finance_role.row_level_security_filters.append(rls)
db.session.commit()
The RowLevelSecurityFilter model is defined at line 2082 in superset/connectors/sqla/models.py. The security manager queries these filters and the rls_filter_roles association table during query construction to build the final SQL.
Using Low-Level Filter Helpers
For custom integrations or middleware, use the filter utilities directly to respect dataset permissions:
from superset.utils.filters import get_dataset_access_filters
from superset.connectors.sqla.models import SqlaTable
from sqlalchemy import select
def build_secure_query():
table = SqlaTable.find_by_id(42)
base_query = select([table.table])
permission_filter = get_dataset_access_filters(SqlaTable)
return base_query.where(permission_filter)
Summary
- Dataset-level access relies on FAB permission strings evaluated by
get_dataset_access_filters()insuperset/utils/filters.py, checking fordatabase_access,schema_access,catalog_access, or explicitdatasource_access. - Column-level security uses
RowLevelSecurityFilterentries stored inrow_level_security_filters, with SQL clauses injected into queries by the logic insuperset/security/manager.py(lines 2728-2849). - Both mechanisms support UI configuration, REST API management via
superset/row_level_security/api.py, and programmatic Python client access. - RLS filters support
REGULAR(additiveANDclauses) andBASE(default fallback) types for complex security policies. - Permission evaluation occurs during SQL generation, ensuring the database never sees unauthorized rows.
Frequently Asked Questions
What is the difference between dataset-level and column-level security in Superset?
Dataset-level security controls access to entire tables or virtual datasets through FAB permissions like datasource_access or schema_access, evaluated by SupersetSecurityManager.can_access_datasource(). Column-level security uses Row Level Security (RLS) filters that inject SQL WHERE clauses to restrict which rows and column values users see based on their roles.
How does Superset enforce RLS filters during query execution?
When building a query, the security manager (around lines 2728-2849 in superset/security/manager.py) selects applicable RowLevelSecurityFilter objects from the row_level_security_filters table by joining through the rls_filter_roles association table. It concatenates the clause strings with AND operators and injects them into the generated SQL WHERE clause before sending the query to the database.
Can RLS filters restrict access to specific columns rather than just rows?
While RLS filters technically operate on rows, you can achieve column-level restrictions by filtering on column values that only exist in specific rows (e.g., department = 'finance'). The filter clause can reference any column in the table, allowing you to hide entire data segments containing sensitive column values from unauthorized roles. For true column masking, combine RLS with database-level views or calculated columns.
Where is the dataset permission logic implemented in the Superset codebase?
The core permission evaluation resides in superset/security/manager.py in the can_access_datasource() and raise_for_access() methods. The SQLAlchemy filter generation logic is in superset/utils/filters.py inside get_dataset_access_filters(). Permission strings are generated by SqlaTable.get_perm() in superset/connectors/sqla/models.py, which also defines the RowLevelSecurityFilter model at line 2082.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →