How INFINI Console Implements Role-Based Access Control (RBAC) for Index and API-Level Permissions
INFINI Console implements RBAC by storing role definitions in core/security/role.go, aggregating user permissions via CombineUserRoles, and validating API access through ValidatePermission while enforcing Elasticsearch index and cluster-level security through ValidateIndex and ValidateCluster.
INFINI Console provides a unified management interface for Elasticsearch clusters, requiring robust security controls to protect both platform APIs and data indices. The open-source repository infinilabs/console implements a comprehensive role-based access control (RBAC) system that governs user actions at both the API and Elasticsearch index levels. This architecture separates platform permissions from Elasticsearch privileges, allowing administrators to define granular access policies that span console functionality and cluster data operations.
Role Definition and Storage
Roles in INFINI Console are persisted as structured objects defined in core/security/role.go. The Role struct encapsulates both platform-wide permissions and Elasticsearch-specific privileges.
type Role struct {
orm.ORMObjectBase
Name string
Type string // platform / elasticsearch
Description string
Builtin bool
Privilege RolePrivilege
}
type RolePrivilege struct {
Platform []string // platform permission IDs
Elasticsearch ElasticsearchPrivilege // ES‑level privileges
}
type ElasticsearchPrivilege struct {
Cluster ClusterPrivilege
Index []IndexPrivilege
}
The Platform field contains permission identifiers such as "system.user:read", while the Elasticsearch field defines cluster-wide and per-index access patterns. The system automatically creates a builtin administrator role during initialization (lines 91-117 in role.go) that receives enum.AdminPrivilege, effectively granting all permissions.
Source: [core/security/role.go](https://github.com/infinilabs/console/blob/main/core/security/role.go)
Permission Mapping and Enumeration
Permission identifiers are mapped to concrete action strings through enum.PermissionMap in core/security/enum/const.go. This indirection layer allows high-level permission IDs to resolve to multiple low-level operations.
var PermissionMap = map[string][]string{
UserRead: UserReadPermission,
// …
}
For example, the key "system.user:read" might expand to ["user:read", "user:list"], enabling fine-grained control over which specific actions a role can perform within a module.
Source: [core/security/enum/const.go](https://github.com/infinilabs/console/blob/main/core/security/enum/const.go)
Aggregating User Permissions
When a user authenticates, INFINI Console aggregates all permissions from their assigned roles using CombineUserRoles in core/security/validate.go. This function merges multiple role definitions into a single effective permission set.
func CombineUserRoles(roleNames []string) RolePermission { … }
The merging process handles:
- Cluster privileges: Combining cluster-wide permissions, including wildcard
"*"patterns that grant universal cluster access. - Index privileges: Merging per-index permission maps while preserving individual index patterns and their associated privileges.
This aggregation occurs before any authorization check, ensuring that subsequent validation operations work against a unified view of the user's capabilities.
Source: [core/security/validate.go](https://github.com/infinilabs/console/blob/main/core/security/validate.go)
Platform API-Level Permission Checks
For platform API endpoints, INFINI Console uses middleware-based permission validation. Handlers declare required permissions via handler.RequirePermission(...), which extracts the necessary permission list from the request metadata.
The core validation logic resides in ValidatePermission:
func ValidatePermission(claims *UserClaims, permissions []string) error {
// builds userPermissionMap from RoleMap → Privilege.Platform → PermissionMap
// returns error if any required permission is missing
}
This function:
- Retrieves the user's roles from
UserClaims - Expands each role's platform permissions using
enum.PermissionMap - Validates that all required permissions from the API metadata are present in the user's effective permission set
If validation fails, the system returns a "permission denied" error before the business logic executes.
Source: [core/security/validate.go](https://github.com/infinilabs/console/blob/main/core/security/validate.go)
Elasticsearch Index and Cluster Permission Validation
When proxying requests to Elasticsearch clusters, INFINI Console constructs a request descriptor containing the target cluster ID, index name (if applicable), and required ES API privileges (e.g., ["indices:data/read/*"]).
The system then invokes two specialized validators:
ValidateIndex(req IndexRequest, userRole RolePermission)– validates index-level permissionsValidateCluster(req ClusterRequest, userRole RolePermission)– validates cluster-wide permissions
Both functions:
- Populate an
apiPrivilegesmap with the required privileges from the request descriptor - Look up the caller's Elasticsearch privileges in
userRole.ElasticPrivilege(produced byCombineUserRoles) - Apply wildcard handling (
*) for universal access and pattern matching viaradix.Matchto support index name globbing - Return an authorization error if any required privilege remains unmatched after pattern expansion
This design allows administrators to define roles with patterns like "logs-*" that grant read access to all indices matching that prefix while denying access to "security-*" indices.
Source: [core/security/validate.go](https://github.com/infinilabs/console/blob/main/core/security/validate.go)
API Permission Router Registration
To determine which Elasticsearch privileges are required for a given HTTP request, INFINI Console maintains an API permission router that maps HTTP methods and paths to privilege sets.
During native authentication initialization (modules/security/realm/authc/native/load.go), the system registers the Elasticsearch API router:
rbac.RegisterAPIPermissionRouter("elasticsearch", esAPIRouter)
The router storage and lookup mechanisms are defined in core/security/permission.go, which provides:
RegisterAPIPermissionRouter(name string, router *APIPermissionRouter)– stores the router for a given domainGetAPIPermissionRouter(name string)– retrieves the router for privilege lookupSearchAPIPermission(method, path string)– returns the required privilege set for a specific HTTP request
When an Elasticsearch request arrives, SearchAPIPermission identifies the required privileges based on the HTTP method and path, which are then passed to ValidateIndex or ValidateCluster for authorization.
Sources:
- Router registration – [
modules/security/realm/authc/native/load.go](https://github.com/infinilabs/console/blob/main/modules/security/realm/authc/native/load.go) - Router storage – [
core/security/permission.go](https://github.com/infinilabs/console/blob/main/core/security/permission.go)
End-to-End Request Flow
Understanding the complete authorization lifecycle helps administrators debug permission issues and developers extend the system:
- Authentication – User logs in and receives a JWT containing
UserClaimswith assigned role IDs. - Role Aggregation – Middleware invokes
CombineUserRolesto merge all assigned roles into a unifiedRolePermissionobject containing platform permissions and Elasticsearch privileges. - Platform API Check – For console API endpoints,
ValidatePermissionexpands role permissions usingenum.PermissionMapand verifies the user possesses all required permissions fromhandler.RequirePermission. - Elasticsearch Request Parsing – For ES proxy requests,
SearchAPIPermissionlooks up the HTTP method and path in the registered API router to determine required ES privileges. - Elasticsearch Authorization –
ValidateIndexorValidateClustercompares required privileges against the user'sElasticPrivilegeusing wildcard matching and radix pattern matching for index names. - Result – Success allows the request to proceed; failure returns HTTP 403 with a permission denied error.
This architecture separates platform concerns from Elasticsearch operations while maintaining consistent RBAC semantics across both domains.
Summary
- Role definitions in
core/security/role.gosupport both platform permissions and Elasticsearch cluster/index privileges through theRolePrivilegestruct. - Permission expansion uses
enum.PermissionMapincore/security/enum/const.goto translate high-level permission IDs into concrete action strings. - Role aggregation via
CombineUserRolesincore/security/validate.gomerges multiple assigned roles into a unified effective permission set. - Platform API security relies on
ValidatePermissionto checkhandler.RequirePermissiondeclarations against the user's platform permissions. - Elasticsearch access control uses
ValidateIndexandValidateClusterwith wildcard and radix pattern matching to enforce index and cluster-level privileges. - API routing registers Elasticsearch privilege mappings via
RegisterAPIPermissionRouterand resolves them throughSearchAPIPermission.
Frequently Asked Questions
How does INFINI Console distinguish between platform and Elasticsearch permissions?
INFINI Console uses the Type field in the Role struct and separate fields within RolePrivilege to distinguish domains. The Platform field contains console-specific permission IDs like "system.user:read", while the Elasticsearch field contains ClusterPrivilege and IndexPrivilege structures for ES operations. This separation allows administrators to grant console access without necessarily granting data access, and vice versa.
What pattern matching capabilities exist for index-level permissions?
The system supports glob-style pattern matching through the radix library. When ValidateIndex processes a request, it uses radix.Match to compare the requested index name against patterns stored in the user's IndexPrivilege entries. This allows role definitions to use wildcards like "logs-*" to match multiple indices or "*" to match all indices, providing flexible access control for dynamic index naming schemes common in time-series data.
How are builtin roles initialized in the system?
Builtin roles are created during package initialization in core/security/role.go (lines 91-117). The init() function automatically generates an administrator role with Builtin set to true and assigns enum.AdminPrivilege, which expands to encompass all platform and Elasticsearch permissions. This ensures that fresh installations have at least one superuser account capable of managing the system without manual database seeding.
Can permissions be checked programmatically outside of HTTP middleware?
Yes, the validation functions in core/security/validate.go are designed for direct programmatic use. Developers can call ValidatePermission with a UserClaims object and a slice of required permissions to check platform access, or use ValidateIndex and ValidateCluster with a RolePermission object to verify Elasticsearch operations. This allows background jobs, CLI tools, or custom endpoints to reuse the same authorization logic that protects HTTP routes.
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 →