# How INFINI Console Implements Role-Based Access Control (RBAC) for Index and API-Level Permissions

> Learn how INFINI Console implements RBAC for secure index and API access. Discover how roles, user permissions, and validation checks ensure granular control over your data.

- Repository: [INFINI Labs/console](https://github.com/infinilabs/console)
- Tags: deep-dive
- Published: 2026-03-04

---

**INFINI Console implements RBAC by storing role definitions in [`core/security/role.go`](https://github.com/infinilabs/console/blob/main/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`](https://github.com/infinilabs/console/blob/main/core/security/role.go). The `Role` struct encapsulates both platform-wide permissions and Elasticsearch-specific privileges.

```go
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`](https://github.com/infinilabs/console/blob/main/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)](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`](https://github.com/infinilabs/console/blob/main/core/security/enum/const.go). This indirection layer allows high-level permission IDs to resolve to multiple low-level operations.

```go
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)](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`](https://github.com/infinilabs/console/blob/main/core/security/validate.go). This function merges multiple role definitions into a single effective permission set.

```go
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)](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`:

```go
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)](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)](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`](https://github.com/infinilabs/console/blob/main/modules/security/realm/authc/native/load.go)), the system registers the Elasticsearch API router:

```go
rbac.RegisterAPIPermissionRouter("elasticsearch", esAPIRouter)

```

The router storage and lookup mechanisms are defined in [`core/security/permission.go`](https://github.com/infinilabs/console/blob/main/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:**  
- Router registration – [[`modules/security/realm/authc/native/load.go`](https://github.com/infinilabs/console/blob/main/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)](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:

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 Authorization** – `ValidateIndex` 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`](https://github.com/infinilabs/console/blob/main/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`](https://github.com/infinilabs/console/blob/main/core/security/enum/const.go) to translate high-level permission IDs into concrete action strings.
- **Role aggregation** via `CombineUserRoles` in [`core/security/validate.go`](https://github.com/infinilabs/console/blob/main/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`](https://github.com/infinilabs/console/blob/main/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`](https://github.com/infinilabs/console/blob/main/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.