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:

  1. Retrieves the user's roles from UserClaims
  2. Expands each role's platform permissions using enum.PermissionMap
  3. 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 permissions
  • ValidateCluster(req ClusterRequest, userRole RolePermission) – validates cluster-wide permissions

Both functions:

  1. Populate an apiPrivileges map with the required privileges from the request descriptor
  2. Look up the caller's Elasticsearch privileges in userRole.ElasticPrivilege (produced by CombineUserRoles)
  3. Apply wildcard handling (*) for universal access and pattern matching via radix.Match to support index name globbing
  4. 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 domain
  • GetAPIPermissionRouter(name string) – retrieves the router for privilege lookup
  • SearchAPIPermission(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:

End-to-End Request Flow

Understanding the complete authorization lifecycle helps administrators debug permission issues and developers extend the system:

  1. Authentication – User logs in and receives a JWT containing UserClaims with assigned role IDs.
  2. Role Aggregation – Middleware invokes CombineUserRoles to merge all assigned roles into a unified RolePermission object containing platform permissions and Elasticsearch privileges.
  3. Platform API Check – For console API endpoints, ValidatePermission expands role permissions using enum.PermissionMap and verifies the user possesses all required permissions from handler.RequirePermission.
  4. Elasticsearch Request Parsing – For ES proxy requests, SearchAPIPermission looks up the HTTP method and path in the registered API router to determine required ES privileges.
  5. Elasticsearch AuthorizationValidateIndex or ValidateCluster compares required privileges against the user's ElasticPrivilege using wildcard matching and radix pattern matching for index names.
  6. 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.go support both platform permissions and Elasticsearch cluster/index privileges through the RolePrivilege struct.
  • Permission expansion uses enum.PermissionMap in core/security/enum/const.go to translate high-level permission IDs into concrete action strings.
  • Role aggregation via CombineUserRoles in core/security/validate.go merges multiple assigned roles into a unified effective permission set.
  • Platform API security relies on ValidatePermission to check handler.RequirePermission declarations against the user's platform permissions.
  • Elasticsearch access control uses ValidateIndex and ValidateCluster with wildcard and radix pattern matching to enforce index and cluster-level privileges.
  • API routing registers Elasticsearch privilege mappings via RegisterAPIPermissionRouter and resolves them through SearchAPIPermission.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →