Контроль доступа Open Policy Agent#
Плагин контроля доступа Open Policy Agent позволяет использовать Open Policy Agent (OPA) как движок авторизации для тонко настраиваемого контроля доступа к каталогам, схемам, таблицам и другим объектам в Trino. Политики определяются в OPA, а Trino проверяет права доступа через OPA.
Требования#
Запущенное развертывание OPA
Сетевая связность от кластера Trino к серверу OPA
После выполнения требований можно перейти к настройке Trino и OPA с нужной вам конфигурацией контроля доступа.
Конфигурация Trino#
Чтобы использовать только OPA для контроля доступа, создайте файл
etc/access-control.properties со следующей минимальной конфигурацией:
access-control.name=opa
opa.policy.uri=https://opa.example.com/v1/data/trino/allow
Чтобы объединить OPA-контроль доступа с file-based или другими системами контроля доступа, следуйте инструкциям в Несколько систем контроля доступа.
В следующей таблице перечислены свойства конфигурации контроля доступа OPA:
Name |
Description |
|---|---|
|
Обязательный URI endpoint OPA, например
|
|
Необязательный URI для получения row filters — если не задан, фильтрация
строк не применяется. Например,
|
|
Необязательный URI для получения column masks — если не задан,
маскирование не применяется. Например,
|
|
Необязательный URI для получения масок столбцов пакетно; не должен
использоваться вместе с |
|
Необязательный URI для включения batch-режима для некоторых
запросов авторизации, где пакетная обработка применима, например
|
|
Настройка, логировать ли детали запросов (включая URI, заголовки и все тело)
перед отправкой в OPA. По умолчанию |
|
Настройка, логировать ли детали ответов OPA (включая URI, статус-код,
заголовки и все тело). По умолчанию |
|
Настройка, разрешены ли операции управления разрешениями. Подробнее в
Управление разрешениями. По умолчанию |
|
Необязательные настройки HTTP-клиента для соединения Trino с OPA,
например |
|
Необязательный файл свойств, содержащий пользовательские свойства (например, tenant namespace, tier или cluster), которые нужно включить в контекст запроса к OPA. |
Логирование#
Когда логирование запросов или ответов включено, детали логируются на уровне
DEBUG в логгере io.trino.plugin.opa.OpaHttpClient. Конфигурация
логирования Trino должна быть обновлена с учетом этого класса, чтобы записи
в логах создавались.
Обратите внимание, что включение этих опций создает очень большой объем лог-данных.
Управление разрешениями#
Следующие операции разрешаются или запрещаются в зависимости от значения
opa.allow-permission-management-operations. Если установлено true, эти
операции разрешены. Если установлено false, они запрещены. В обоих случаях
запрос в OPA не отправляется.
GrantSchemaPrivilegeDenySchemaPrivilegeRevokeSchemaPrivilegeGrantTablePrivilegeDenyTablePrivilegeRevokeTablePrivilegeCreateRoleDropRoleGrantRolesRevokeRoles
По умолчанию используется false из-за сложности и возможных неожиданных
последствий одновременного использования SQL-style grants и ролей вместе с OPA.
Вы должны включить управление разрешениями, если в Trino используется другая кастомная система безопасности, поддерживающая управление grants, вместе с OPA-контролем доступа.
Дополнительно пользователям всегда разрешено показывать информацию о ролях (SHOW ROLES) независимо от этой настройки. Следующие операции всегда разрешены:
ShowRolesShowCurrentRolesShowRoleGrants
Конфигурация OPA#
OPA-контроль доступа в Trino обращается к OPA для каждого запроса и отправляет
запрос авторизации. OPA должен вернуть ответ, содержащий boolean-поле allow,
которое определяет, разрешена операция или нет.
Политики в OPA определяются на Rego — специализированном policy language. Подробнее в подробной документации. После первоначальной установки и настройки в Trino эти политики становятся основным аспектом конфигурации вашей схемы контроля доступа.
Запрос из OPA-контроля доступа Trino к OPA содержит поля верхнего уровня
context и action.
Объект context содержит всю прочую контекстную информацию о запросе:
identity: идентичность пользователя, выполняющего операцию, с двумя полями:user: имя пользователяgroups: список групп, к которым принадлежит пользователь
queryId: id запросаsoftwareStack: информация о программном стеке, отправляющем запрос в OPA. Включается следующая информация:trinoVersion: используемая версия Trino
Объект action содержит информацию о том, какое действие выполняется и над
какими ресурсами. Предоставляются следующие поля:
operation: выполняемая операция, напримерSelectFromColumns.resource: информация о доступе к объектамtargetResource: информация о любом новом создаваемом объекте, если применимоgrantee: получатель прав в операции grant.
Поля, неприменимые для конкретной операции, устанавливаются в null.
Например, пустой targetResource, если таблица/схема не модифицируется,
или пустой grantee, если операция не связана с выдачей прав.
Любое null-поле полностью опускается из объекта action.
Примеры запросов к OPA#
Доступ к таблице приводит к запросу, похожему на следующий пример:
{
"context": {
"identity": {
"user": "foo",
"groups": ["some-group"]
},
"queryId": "20250718_081710_03427_trino",
"softwareStack": {
"trinoVersion": "434"
}
},
"action": {
"operation": "SelectFromColumns",
"resource": {
"table": {
"catalogName": "example_catalog",
"schemaName": "example_schema",
"tableName": "example_table",
"columns": [
"column1",
"column2",
"column3"
]
}
}
}
}
targetResource используется в случаях, когда создается новый ресурс,
отличный от ресурса в resource. Например, при переименовании таблицы.
{
"context": {
"identity": {
"user": "foo",
"groups": ["some-group"]
},
"queryId": "20250718_081710_03427_trino",
"softwareStack": {
"trinoVersion": "434"
}
},
"action": {
"operation": "RenameTable",
"resource": {
"table": {
"catalogName": "example_catalog",
"schemaName": "example_schema",
"tableName": "example_table"
}
},
"targetResource": {
"table": {
"catalogName": "example_catalog",
"schemaName": "example_schema",
"tableName": "new_table_name"
}
}
}
}
Фильтрация строк#
Фильтрация строк позволяет Trino удалять часть строк из результата перед
возвратом вызывающей стороне, управляя тем, какие данные видят разные
пользователи. Плагин поддерживает получение определений фильтров из OPA через
настройку endpoint OPA для row filter processing с
opa.policy.row-filters-uri.
Например, OPA-политика фильтрации строк может быть задана следующим rego- скриптом:
package trino
import future.keywords.in
import future.keywords.if
import future.keywords.contains
default allow := true
table_resource := input.action.resource.table
is_admin {
input.context.identity.user == "admin"
}
rowFilters contains {"expression": "user_type <> 'customer'"} if {
not is_admin
table_resource.catalogName == "sample_catalog"
table_resource.schemaName == "sample_schema"
table_resource.tableName == "restricted_table"
}
Ожидаемый плагином ответ — это массив объектов, каждый в формате
{"expression":"clause"}. Каждое выражение фактически работает как
дополнительный WHERE clause. Скрипт также может возвращать несколько row
filters для одного OPA-запроса, и затем применяются все фильтры.
Каждый объект может содержать поле identity. Поле identity позволяет Trino вычислять row filters под другой идентичностью — так что фильтр может обращаться к столбцу, который запрашивающий пользователь не видит.
Маскирование столбцов#
Маскирование столбцов позволяет Trino скрывать данные в одном или нескольких
столбцах результата для определенных пользователей, без полного запрета
доступа. Плагин поддерживает получение column masks из OPA через настройку
endpoint OPA для column mask processing с
opa.policy.column-masking-uri в конфигурации opa-plugin.
Например, политика маскирования столбцов может быть задана следующим rego- скриптом:
package trino
import future.keywords.in
import future.keywords.if
import future.keywords.contains
default allow := true
column_resource := input.action.resource.column
is_admin {
input.context.identity.user == "admin"
}
columnMask := {"expression": "NULL"} if {
not is_admin
column_resource.catalogName == "sample_catalog"
column_resource.schemaName == "sample_schema"
column_resource.tableName == "restricted_table"
column_resource.columnName == "user_phone"
}
columnMask := {"expression": "'****' || substring(user_name, -3)"} if {
not is_admin
column_resource.catalogName == "sample_catalog"
column_resource.schemaName == "sample_schema"
column_resource.tableName == "restricted_table"
column_resource.columnName == "user_name"
}
В отличие от фильтрации строк, для данного столбца можно вернуть только одну column mask.
То же поле identity может быть возвращено для вычисления column masks под другой идентичностью.
Пакетное маскирование столбцов#
Если маскирование столбцов включено, по умолчанию плагин запрашивает каждую маску столбца отдельно в OPA. При работе с очень широкими таблицами это может привести к деградации производительности.
Настройка opa.policy.batch-column-masking-uri позволяет Trino получать маски
для нескольких столбцов одним запросом. Список запрошенных столбцов включается
в запрос в action.filterResources.
Если задано opa.policy.batch-column-masking-uri, оно переопределяет значение
opa.policy.column-masking-uri, чтобы плагин использовал пакетное
маскирование столбцов.
OPA-политика, поддерживающая пакетное маскирование столбцов, должна возвращать список объектов, каждый из которых содержит:
viewExpression:expression: выражение, применяемое к столбцу, как строкаidentity(optional): идентичность, от имени которой вычисляется выражение, как строка
index: индекс столбца в запросе, к которому применяется эта маска
Например, политика пакетного маскирования столбцов может быть задана следующим rego-скриптом:
package trino
import future.keywords.in
import future.keywords.if
import future.keywords.contains
default allow := true
batchColumnMasks contains {
"index": i,
"viewExpression": {
"expression": "NULL"
}
} if {
some i
column_resource := input.action.filterResources[i]
column_resource.catalogName == "sample_catalog"
column_resource.schemaName == "sample_schema"
column_resource.tableName == "restricted_table"
column_resource.columnName == "user_phone"
}
batchColumnMasks contains {
"index": i,
"viewExpression": {
"expression": "'****' || substring(user_name, -3)",
"identity": "admin"
}
} if {
some i
column_resource := input.action.filterResources[i]
column_resource.catalogName == "sample_catalog"
column_resource.schemaName == "sample_schema"
column_resource.tableName == "restricted_table"
column_resource.columnName == "user_name"
}
Запрос пакетного маскирования столбцов выглядит примерно так:
{
"context": {
"identity": {
"user": "foo",
"groups": ["some-group"]
},
"queryId": "20250718_081710_03427_trino",
"softwareStack": {
"trinoVersion": "434"
}
},
"action": {
"operation": "GetColumnMask",
"filterResources": [
{
"column": {
"catalogName": "sample_catalog",
"schemaName": "sample_schema",
"tableName": "restricted_table",
"columnName": "user_phone",
"columnType": "VARCHAR"
}
},
{
"column": {
"catalogName": "sample_catalog",
"schemaName": "sample_schema",
"tableName": "restricted_table",
"columnName": "user_name",
"columnType": "VARCHAR"
}
}
]
}
}
Соответствующий ответ OPA показан в следующем примере:
[
{
"index": 0,
"viewExpression": {
"expression": "NULL"
}
},
{
"index": 1,
"viewExpression": {
"expression": "'****' || substring(user_name, -3)",
"identity": "admin"
}
}
]
Batch mode#
Очень мощная возможность OPA — отвечать на запросы авторизации более
сложными данными, чем просто boolean-значение true или false.
Во многих возможностях Trino требуется фильтрация для определения, к каким ресурсам пользователь имеет доступ. Такими ресурсами являются каталоги, схемы, запросы, представления и другие объекты.
Если opa.policy.batched-uri не настроен, Trino отправляет в OPA отдельный
запрос для каждого объекта, а затем формирует отфильтрованный список
разрешенных объектов.
Настройка opa.policy.batched-uri позволяет Trino отправить запрос
на batch endpoint со списком ресурсов в одном запросе через
узел action.filterResources.
Все остальные поля запроса идентичны non-batch endpoint.
OPA-политика, поддерживающая batch-операции, должна возвращать список
индексов элементов, для которых авторизация разрешена. Возврат null
или пустого списка эквивалентен и означает запрет любого доступа.
Можно добавить поддержку batching в политики, которые изначально ее не поддерживают:
package foo
import future.keywords.contains
# ... rest of the policy ...
# this assumes the non-batch response field is called "allow"
batch contains i {
some i
raw_resource := input.action.filterResources[i]
allow with input.action.resource as raw_resource
}
# Corner case: filtering columns is done with a single table item, and many columns inside
# We cannot use our normal logic in other parts of the policy as they are based on sets
# and we need to retain order
batch contains i {
some i
input.action.operation == "FilterColumns"
count(input.action.filterResources) == 1
raw_resource := input.action.filterResources[0]
count(raw_resource["table"]["columns"]) > 0
new_resources := [
object.union(raw_resource, {"table": {"column": column_name}})
| column_name := raw_resource["table"]["columns"][_]
]
allow with input.action.resource as new_resources[i]
}