Open Policy Agent access control
Open Policy Agent access control (OPA 접근 제어)
Open Policy Agent(OPA)를 인가 엔진으로 사용해 Trino의 카탈로그·스키마·테이블 등에 세밀한 접근 제어를 적용하는 방법을 설명해요. 정책은 OPA에서 정의하고 Trino가 권한을 OPA에 확인해요.
출처: 문서
본문
Open Policy Agent 접근 제어 플러그인은 Trino의 카탈로그, 스키마, 테이블 등에 대한 세밀한 접근 제어를 위해 Open Policy Agent(OPA)를 인가 엔진으로 사용할 수 있게 해줘요. 정책은 OPA에서 정의되고, Trino는 OPA에서 접근 제어 권한을 확인해요.
요구 사항 (Requirements)
- 실행 중인 OPA 배포
- Trino 클러스터에서 OPA 서버로의 네트워크 연결
요구 사항을 충족하면 원하는 접근 제어 구성으로 Trino와 OPA를 설정할 수 있어요.
Trino 구성 (Trino configuration)
접근 제어에 OPA만 사용하려면 다음 최소 구성으로 etc/access-control.properties 파일을 만드세요:
access-control.name=opa
opa.policy.uri=https://opa.example.com/v1/data/trino/allow
OPA 접근 제어를 파일 기반 또는 다른 접근 제어 시스템과 결합하려면 Multiple access control systems에 대한 지침을 따르세요.
다음 표는 OPA 접근 제어의 구성 속성을 나열해요:
OPA 접근 제어 구성 속성 (OPA access control configuration properties)
| Name | Description |
|---|---|
opa.policy.uri |
필수. OPA 엔드포인트의 URI. 예: https://opa.example.com/v1/data/trino/allow. |
opa.policy.row-filters-uri |
선택. 행 필터를 가져오는 URI. 설정하지 않으면 행 필터링이 적용되지 않아요. 예: https://opa.example.com/v1/data/trino/rowFilters. |
opa.policy.column-masking-uri |
선택. 컬럼 마스크를 가져오는 URI. 설정하지 않으면 마스킹이 적용되지 않아요. 예: https://opa.example.com/v1/data/trino/columnMask. |
opa.policy.batch-column-masking-uri |
선택. 컬럼 마스크를 배치로 가져오는 URI. opa.policy.column-masking-uri와 함께 사용하면 안 돼요. 예: http://opa.example.com/v1/data/trino/batchColumnMasks. |
opa.policy.batched-uri |
선택. 배치가 적용 가능한 특정 인가 쿼리에 대해 배치 모드를 활성화하는 URI. 예: https://opa.example.com/v1/data/trino/batch. 배치 모드는 Batch mode에 설명돼 있어요. |
opa.log-requests |
요청 세부 사항(URI, 헤더, 전체 본문 포함)이 OPA에 보내기 전에 로그로 기록되는지 구성. 기본값은 false. |
opa.log-responses |
OPA 응답 세부 사항(URI, 상태 코드, 헤더, 전체 본문 포함)이 로그로 기록되는지 구성. 기본값은 false. |
opa.allow-permission-management-operations |
권한 관리 연산이 허용되는지 구성. 자세한 내용은 Permission management 참고. 기본값은 false. |
opa.http-client.* |
Trino에서 OPA로의 연결을 위한 선택적 HTTP 클라이언트 구성. 예: HTTP 프록시 구성용 opa.http-client.http-proxy. 자세한 내용은 HTTP client properties 참고. |
opa.context-file |
선택. OPA 쿼리 컨텍스트에 포함할 사용자 정의 속성(예: 테넌트 네임스페이스, 티어, 클러스터)을 담은 속성 파일. |
로깅 (Logging)
요청 또는 응답 로깅이 활성화되면 io.trino.plugin.opa.OpaHttpClient 로거 아래 DEBUG 수준으로 세부 사항이 기록돼요. 로그 항목이 생성되도록 Trino 로깅 구성에 이 클래스를 포함해야 해요.
이 옵션을 활성화하면 매우 많은 양의 로그 데이터가 생성된다는 점에 주의하세요.
권한 관리 (Permission management)
다음 연산은 opa.allow-permission-management-operations 설정에 따라 허용되거나 거부돼요. true로 설정하면 허용되고, false로 설정하면 거부돼요. 두 경우 모두 OPA에 요청이 보내지지 않아요.
- GrantSchemaPrivilege, DenySchemaPrivilege, RevokeSchemaPrivilege
- GrantTablePrivilege, DenyTablePrivilege, RevokeTablePrivilege
- CreateRole, DropRole, GrantRoles, RevokeRoles
이 설정은 OPA와 함께 SQL 스타일의 부여(grants)와 롤을 쓰는 것이 복잡하고 예상치 못한 결과를 낳을 수 있기 때문에 기본값이 false예요.
Trino의 다른 커스텀 보안 시스템이 부여 관리(grant management)를 할 수 있고 OPA 접근 제어와 함께 쓰인다면 권한 관리를 반드시 활성화해야 해요.
추가로 이 설정과 무관하게 사용자는 항상 롤 정보를 볼 수 있어요(SHOW ROLES). 다음 연산은 항상 허용돼요:
- ShowRoles, ShowCurrentRoles, ShowRoleGrants
OPA 구성 (OPA configuration)
Trino의 OPA 접근 제어는 각 쿼리에 대해 OPA에 연락해 인가 요청을 발행해요. OPA는 연산이 허용되는지 여부를 결정하는 boolean allow 필드를 담은 응답을 반환해야 해요.
OPA의 정책은 목적에 맞게 만들어진 정책 언어인 Rego로 정의돼요. 자세한 정보는 상세 문서에서 확인할 수 있어요. Trino에서 초기 설치·구성 후에는 이 정책들이 접근 제어 설정의 주요 구성 측면이 돼요.
Trino의 OPA 접근 제어에서 OPA로의 쿼리는 최상위 필드로 context와 action을 담아요.
context 객체는 쿼리에 대한 다른 모든 상황 정보를 담아요:
identity: 연산을 수행하는 사용자의 신원. 다음 두 필드를 담아요:user: 사용자 이름groups: 이 사용자가 속한 그룹 목록
queryId: 쿼리 IDsoftwareStack: OPA에 요청을 보내는 소프트웨어 스택에 대한 정보. 다음 정보가 포함돼요:trinoVersion: 사용된 Trino 버전
action 객체는 어떤 리소스에 대해 어떤 연산이 수행되는지에 대한 정보를 담아요. 다음 필드가 제공돼요:
operation: 수행되는 연산. 예:SelectFromColumns.resource: 접근된 객체에 대한 정보targetResource: 적용 가능한 경우 새로 생성된 객체에 대한 정보grantee: 부여 연산의 수취인
특정 연산에 적용되지 않는 필드는 null로 설정돼요. 예를 들어 테이블이나 스키마·카탈로그를 수정하지 않으면 targetResource가 비어 있고, 권한을 부여하지 않으면 grantee가 비어 있어요. 어떤 null 필드든 action 객체에서 완전히 생략돼요.
OPA로의 예시 요청 (Example requests to 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"
}
}
}
}
행 필터링 (Row filtering)
행 필터링을 사용하면 Trino가 결과를 호출자에게 반환하기 전에 일부 행을 제거할 수 있어, 서로 다른 사용자가 볼 수 있는 데이터를 제어할 수 있어요. 플러그인은 opa.policy.row-filters-uri로 행 필터 처리를 위한 OPA 엔드포인트를 구성해 OPA에서 필터 정의를 가져오는 것을 지원해요.
예를 들어 행 필터링을 위한 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 절처럼 동작해요. 스크립트는 단일 OPA 요청에 대해 여러 행 필터를 반환할 수도 있으며, 이후 모든 필터가 적용돼요.
각 객체는 identity 필드를 포함할 수 있어요. identity 필드는 Trino가 이 행 필터를 다른 신원으로 평가할 수 있게 해줘요. 그래서 필터가 요청 사용자가 볼 수 없는 컬럼을 대상으로 할 수 있죠.
컬럼 마스킹 (Column masking)
컬럼 마스킹을 사용하면 특정 사용자에 대해 결과 집합의 하나 이상의 컬럼 데이터를 노골적으로 접근 거부하지 않고 가릴 수 있어요. 플러그인은 opa-plugin 구성에서 opa.policy.column-masking-uri로 컬럼 마스크 처리를 위한 OPA 엔드포인트를 구성해 OPA에서 컬럼 마스크를 가져오는 것을 지원해요.
예를 들어 컬럼 마스킹을 구성하는 정책은 다음 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"
}
행 필터링과 달리 주어진 컬럼에 대해 단일 컬럼 마스크만 반환될 수 있어요.
같은 identity 필드가 반환되어 컬럼 마스크를 다른 신원으로 평가할 수도 있어요.
배치 컬럼 마스킹 (Batch column masking)
컬럼 마스킹이 활성화되면 기본적으로 플러그인은 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(선택): 표현식을 평가할 신원(문자열)
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가 제공하는 매우 강력한 기능 중 하나는 인가 쿼리에 true/false boolean 값보다 더 복잡한 답으로 응답할 수 있다는 것이에요.
Trino의 많은 기능은 사용자가 접근 권한을 가진 리소스를 결정하기 위한 필터링을 요구해요. 이런 리소스는 카탈로그, 스키마, 쿼리, 뷰, 기타 객체들이에요.
opa.policy.batched-uri가 구성되지 않으면 Trino는 각 객체에 대해 OPA에 하나의 요청을 보낸 다음 허용된 객체의 필터링된 목록을 만들어요.
opa.policy.batched-uri를 구성하면 Trino가 action.filterResources 노드 아래의 리소스 목록과 함께 한 번의 요청으로 배치 엔드포인트에 요청을 보낼 수 있어요.
요청의 다른 모든 필드는 비배치 엔드포인트와 동일해요.
배치 연산을 지원하는 OPA 정책은 인가가 부여된 항목의 인덱스를 담은 목록을 반환해야 해요. null 값이나 빈 목록을 반환하는 것은 동등하며 어떤 접근도 거부해요.
배치를 지원하지 않는 정책에 배치 지원을 추가할 수 있어요:
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]
}
더 알아보기 (Learn more)
OPA를 다른 접근 제어 방식과 결합하는 방법은 System access control의 여러 접근 제어 시스템 섹션을 참고해 보세요.