Documentation
¶
Overview ¶
Package tenancy resolves Mimir tenant IDs for authenticated users.
Two resolution modes are supported, selected via NewResolverForMode:
grafana-organization (ModeGrafanaOrganization): reads GrafanaOrganization CRDs from the Kubernetes API. Each CRD specifies which Dex groups can access which Mimir tenants via spec.rbac and spec.tenants. Results are cached for 60 seconds per unique group set to reduce API-server load.
static (ModeStatic): returns a fixed tenant list or a group→tenants mapping from configuration. No Kubernetes access is required. Two sub-modes exist: all-users (same tenants for every authenticated user) and group-mapping (tenants collected per group and unioned).
All modes implement server.TenancyResolver so they are interchangeable at the call site. SelectOrgID validates or auto-injects the Mimir X-Scope-OrgID value and is shared by all modes.
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func NewResolverForMode ¶ added in v0.0.22
func NewResolverForMode(mode Mode, staticTenants []string, groupMap map[string][]string) (server.TenancyResolver, error)
NewResolverForMode constructs the server.TenancyResolver for the given mode.
For ModeGrafanaOrganization the resolver uses in-cluster Kubernetes credentials; staticTenants and groupMap are ignored.
For ModeStatic a StaticResolver is returned. When groupMap is non-nil and non-empty, it is used for group-based resolution and staticTenants is ignored. Otherwise staticTenants is returned for every authenticated user.
The function name avoids a collision with the existing NewResolver constructor that accepts a dynamic Kubernetes client.
func SelectOrgID ¶
SelectOrgID selects the Mimir X-Scope-OrgID value for a request.
Mimir supports querying multiple tenants simultaneously by joining their names with a pipe character (e.g. "tenant-a|tenant-b"). This function validates any explicit override — including pipe-separated multi-tenant selectors — against the caller's allowed tenant list so that a user cannot reach tenants outside their GrafanaOrganization membership.
Rules:
- len(tenants) == 0 → error (no GrafanaOrganization membership).
- override != "" → each pipe-separated part is validated; error if any part is not in the allowed list.
- override == "" and len(tenants) == 1 → auto-inject the single tenant.
- override == "" and len(tenants) > 1 → auto-inject all allowed tenants joined with "|" (Mimir will query all of them).
Types ¶
type Mode ¶ added in v0.0.22
type Mode string
Mode identifies which tenancy resolution strategy is active.
const ( // ModeGrafanaOrganization resolves tenants via GrafanaOrganization CRDs in the // Kubernetes cluster. Requires in-cluster credentials and a ClusterRole granting // get/list/watch on grafanaorganizations. ModeGrafanaOrganization Mode = "grafana-organization" // ModeStatic resolves tenants from a fixed configuration supplied at startup. // No Kubernetes access is required. // // Two sub-modes are available depending on how [NewResolverForMode] is called: // - All-users: a fixed slice of tenant IDs is returned for every authenticated user. // - Group-mapping: a map of group name → tenant IDs is used to collect tenants // for the user's JWT groups, with results deduplicated and sorted. ModeStatic Mode = "static" )
type Resolver ¶
type Resolver struct {
// contains filtered or unexported fields
}
Resolver resolves Mimir tenant IDs for a user based on their group memberships and the GrafanaOrganization CRDs present in the cluster.
func NewInClusterResolver ¶
NewInClusterResolver creates a Resolver using in-cluster service account credentials. Returns an error when called outside a Kubernetes cluster.
func NewResolver ¶
NewResolver creates a Resolver from an existing dynamic client. Useful for testing or when a client is already available.
func (*Resolver) TenantsForGroups ¶
TenantsForGroups returns the Mimir tenant IDs accessible to a user that belongs to the given Dex groups.
It lists all GrafanaOrganization CRDs, finds those whose spec.rbac fields contain at least one of the user's groups, and collects the spec.tenants[*].name values from matching organisations.
The result is deduplicated and sorted. An empty slice means the user has no access to any tenant (caller should return 403).
Results are cached for [cacheTTL]. Concurrent requests for the same group set are coalesced via singleflight so the Kubernetes API is called at most once per cache miss. If the Kubernetes API is unavailable and a (potentially stale) cached entry exists, the stale entry is returned rather than failing every in-flight tool call.
type StaticResolver ¶ added in v0.0.22
type StaticResolver struct {
// contains filtered or unexported fields
}
StaticResolver implements server.TenancyResolver using a configuration-time tenant list or group→tenants mapping. No Kubernetes or network access is made at runtime.
Two sub-modes are supported:
All-users mode: when groupMap is nil or empty, every authenticated user receives the same fixed tenant list regardless of group membership.
Group-mapping mode: when groupMap is non-nil and non-empty, tenant IDs are collected from every group the user belongs to, deduplicated, and sorted. Users with no matching groups get an empty slice, which SelectOrgID converts to an access-denied error.
func NewStaticResolver ¶ added in v0.0.22
func NewStaticResolver(allTenants []string, groupMap map[string][]string) *StaticResolver
NewStaticResolver returns a StaticResolver.
When groupMap is non-nil and non-empty, group-mapping mode is active and allTenants is ignored. Otherwise every authenticated user receives allTenants.
Both allTenants and each value slice in groupMap are copied to prevent the caller from mutating the resolver's internal state after construction.
func (*StaticResolver) TenantsForGroups ¶ added in v0.0.22
TenantsForGroups implements server.TenancyResolver.
In all-users mode the configured tenant list is returned for every call. In group-mapping mode the tenant IDs for each of the user's groups are collected, deduplicated, and returned sorted. The context is not used.