본문으로 건너뛰기

Trino JWT 인증

Trino coordinator 가 Keycloak 발급 JWT 를 검증해 GenD API 요청을 인가합니다. 본 문서는 활성화 구성, Keycloak client 설정, 토큰 발급 방법, 장애 대응을 다룹니다.

개요

GenD API 는 기동 후 Keycloak 의 trino client 로 service-account 토큰을 발급받아 메모리 캐시(만료 60 초 전 재발급)에 두고, 모든 Trino DB-API / REST 요청에 Authorization: Bearer <token> 헤더로 첨부합니다. Trino coordinator 는 JWKS 엔드포인트에서 공개키를 받아 RS256 서명을 검증합니다.

활성화 구성

Trino coordinator

infra/helm/trino/values.yamlserver.coordinatorExtraConfigcoordinator 전용 JWT 속성을 주입합니다. worker 는 JWT 속성을 받으면 Configuration property 'X' was not used 로 crash 하므로 additionalConfigProperties 에 넣지 마십시오.

server:
coordinatorExtraConfig: |
http-server.authentication.type=JWT
http-server.authentication.allow-insecure-over-http=true
http-server.authentication.jwt.key-file=https://gend.genon.ai/auth/realms/gend/protocol/openid-connect/certs
http-server.authentication.jwt.principal-field=preferred_username
http-server.authentication.jwt.required-issuer=https://gend.genon.ai/auth/realms/gend

allow-insecure-over-http=true 이유: 클러스터 내부 pod-to-pod 통신은 HTTP 이고, process-forwarded=true + X-Forwarded-Proto: https 로 HTTPS 위장 시 Trino 가 응답 nextUrihttps://trino.svc:8080 으로 rewrite 해 후속 polling 이 SSL 오류로 실패합니다. 네트워크 경계는 AKS NetworkPolicy 로 통제하므로 HTTP 위 JWT 를 허용합니다. 외부 노출 시에는 Ingress TLS 종단 또는 mTLS sidecar 를 함께 쓰십시오.

GenD API

환경변수:

GEND_TRINO_JWT_ENABLED=true
GEND_TRINO_JWT_ISSUER_URL=https://gend.genon.ai/auth/realms/gend
GEND_TRINO_JWT_CLIENT_ID=trino
GEND_TRINO_JWT_CLIENT_SECRET=<keycloak trino client secret>
GEND_TRINO_HOST=trino.gend.svc.cluster.local
GEND_TRINO_PORT=8080
과도기 운영 상태

현재 GEND_TRINO_JWT_CLIENT_SECRET 은 Deployment spec 에 평문 value: 로 주입되어 있습니다 (kubectl set env 로 #533 활성화 시 투입). 기존 trino-secrets SealedSecret 은 Trino 내부 통신용(shared-secret, keystore-password) 이라 JWT client_secret 전용 키가 없습니다. 프로덕션 완성도를 위해 gend-api 전용 SealedSecret 또는 trino-secretskeycloak-client-secret 키 추가가 필요합니다 (별도 이슈로 추적 예정).

Keycloak 설정

trino client 는 service accounts 를 활성화한 confidential client 로 구성합니다.

항목
Client IDtrino
Access Typeconfidential
Service Accounts EnabledON
Direct Access GrantsOFF (프로덕션)
Valid Redirect URIs불필요

토큰 수동 발급 (운영/디버깅용):

# client_secret 조회 (현재는 Deployment spec 에서 — 과도기)
GEND_TRINO_JWT_CLIENT_SECRET=$(kubectl --context aks-genos-prod get deploy gend-api -n gend \
-o jsonpath='{.spec.template.spec.containers[0].env[?(@.name=="GEND_TRINO_JWT_CLIENT_SECRET")].value}')

TRINO_TOKEN=$(curl -s -X POST \
"https://gend.genon.ai/auth/realms/gend/protocol/openid-connect/token" \
-d "grant_type=client_credentials" \
-d "client_id=trino" \
-d "client_secret=${GEND_TRINO_JWT_CLIENT_SECRET}" | jq -r .access_token)

Trino API 검증:

curl -v -X POST "http://trino.gend.svc.cluster.local:8080/v1/statement" \
-H "Authorization: Bearer ${TRINO_TOKEN}" \
-H "X-Trino-User: gend-api" \
--data "SELECT 1"
# → 200 OK, query id 반환

client_secret 회전 절차

과도기 경로 (현재)

GEND_TRINO_JWT_CLIENT_SECRET 이 Deployment 평문 env 로 있는 동안은 다음처럼 처리합니다. 전용 SealedSecret 이 생기면 Keycloak 회전 → SealedSecret 재봉인 → ArgoCD 자동 반영 흐름으로 교체 예정.

  1. Keycloak admin 콘솔 → ClientstrinoCredentials 탭 → Regenerate secret
  2. Deployment env 갱신 (현재 과도기):
    kubectl --context aks-genos-prod set env deploy/gend-api -n gend \
    GEND_TRINO_JWT_CLIENT_SECRET='<new_secret>'
    kubectl --context aks-genos-prod rollout status deploy/gend-api -n gend --timeout=120s
  3. (목표 아키텍처) gend-api 전용 SealedSecret 으로 이전 후에는 다음으로 대체:
    kubectl create secret generic gend-api-trino-jwt \
    --from-literal=client-secret='<new_secret>' \
    -n gend --dry-run=client -o yaml \
    | kubeseal -o yaml > infra/sealed-secrets/sealed-secrets/gend-api-trino-jwt.yaml
    # PR 머지 → ArgoCD 가 Secret 업데이트 → gend-api 자동 재기동
  4. 토큰 캐시는 expires_in (Keycloak 기본 5 분) 기준으로 만료되며 만료 60 초 전 자동 재발급됩니다. 즉시 회전이 필요하면 Pod 재기동으로 캐시 비우기.

트러블슈팅

증상원인처치
401 Unauthorized on /v1/statementBearer 미첨부 또는 만료GEND_TRINO_JWT_* env 확인, gend-api Pod 재시작
Configuration property 'http-server.authentication.type' was not used (worker crash)JWT 속성이 additionalConfigProperties 에 들어감server.coordinatorExtraConfig 로 이동
SSLError: record layer failure polling 중allow-insecure-over-http=false 기본값 + client 가 HTTP 스킴values.yaml 에 allow-insecure-over-http=true 추가, coordinator rollout
Token is not valid — issuer mismatchrequired-issuer 와 Keycloak 실제 issuer 불일치Keycloak realm 의 tokenIssuer 확인 후 values.yaml 수정
Unable to fetch JWKcoordinator 가 key-file URL 접근 불가외부 URL 접근 권한/DNS 확인, 내부 Keycloak URL 로 override
gend-api 로그 Trino JWT is enabled but Keycloak service-account token is emptyKeycloak 다운 또는 client_secret 불일치Keycloak health 확인, secret 재봉인

로그 확인

다중 레플리카 환경에서는 레이블 셀렉터를 쓰면 모든 Pod 로그가 합쳐져 조회됩니다 (kubectl logs deploy/X 는 단일 Pod). 컨텍스트/네임스페이스 플레이스홀더를 환경별로 치환하세요.

GenD API 측

kubectl --context <CONTEXT> logs -n <NAMESPACE> -l app=gend-api -c gend-api --tail=200 \
| grep -iE "keycloak|jwt|trino"

Trino coordinator 측

kubectl --context <CONTEXT> logs -n <NAMESPACE> -l app.kubernetes.io/component=coordinator --tail=200 \
| grep -iE "Authentication|JWT|Unauthorized|signature"

외부 JDBC / 파이썬 접속 (trino.gend.genon.ai) — 폐기됨

:::danger 이 경로는 더 이상 존재하지 않습니다 (2026-08-08 기준) gend-ingress-trino-external의도적으로 제거됐습니다 — PR #2693 (이슈 #2683 "prod Trino 접근제어 전면 미적용"). allow-insecure-over-http=true 가 평문 요청을 insecure authenticator 로 폴백시켜 X-Trino-User 헤더만으로 임의 신원 행세가 가능했고, 노출면을 좁히는 것이 그 대응의 Step 0 이었습니다.

이후 ingress-nginx 자체가 EOL 로 해체(ADR-0028)되어 호스트가 응답하지 않습니다. 고아 DNS 레코드도 정리했습니다(#3160).

지금 데이터를 조회하려면: GenD UI 의 SQL 편집기, 또는 코드스페이스에서 in-cluster trino:8080 을 사용하세요. 외부 BI 도구 직결이 필요하면 관리자에게 문의하세요 — 재개하려면 Trino 인증 자세 전환(allow-insecure-over-http=false)이 선행돼야 합니다.

아래 내용은 이력 참조용이며 그대로 따라 해도 연결되지 않습니다. :::

2026-07-29 부로 외부 라우트를 제거했습니다 (#2683)

아래 절차는 지금 적용하면 안 됩니다. gend-ingress-trino-external 은 prod 에서 삭제됐고, infra/ingress/trino-external.yaml 에는 적용 금지 표시가 붙어 있습니다.

제거 사유 — 이 절이 원래 "rules.json 이 catalog/table authz 를 시행한다" 고 기술했으나 그 전제가 성립한 적이 없습니다. 2026-07-28 실측 결과 prod coordinator 에 access-control.properties 자체가 없어 Trino 가 기본 정책(임퍼소네이션만 거부, 그 외 전부 허용)으로 동작했습니다. 즉 이 Ingress 는 인가가 전혀 없는 SQL 엔드포인트를 인터넷에 노출하고 있었습니다. 원인은 Helm chart 가 무시하는 values 키 + 3개월간 실패한 helm upgrade 였습니다 (docs/DESIGN_TRINO_ENGINE_ACL.md §2.5).

추가로 required-audience 가 없어 gend realm 의 임의 토큰이 통과했습니다 — 일반 사용자의 UI 로그인 토큰으로 전 테이블 조회가 가능한 구성이었습니다.

현재 상태 확인 — 외부 라우트가 실제로 닫혔는지 다음으로 검증합니다.

# Ingress 부재 확인
kubectl --context aks-genos-prod -n gend get ingress | grep -i trino # 출력 없음

# 외부 도메인에서 SQL 실행 시도 — Trino 가 아닌 다른 백엔드가 응답한다
curl -s -o /dev/null -w "%{http_code}\n" -X POST \
-H "X-Trino-User: admin" --data "SELECT 1" \
https://trino.gend.genon.ai/v1/statement
# → 405 (nginx). Trino 라면 200 + JSON(nextUri) 이 온다.
도메인이 200 을 반환할 수 있습니다

Ingress 를 제거하면 해당 호스트가 다른 백엔드로 폴백GET / 에 200(HTML)을 돌려줄 수 있습니다. 상태 코드만 보고 "아직 열려 있다" 고 판단하지 마세요 — 위처럼 실제 SQL 실행을 시도해 응답이 Trino JSON 인지 확인해야 합니다.

재개 조건ADR-0030 의 Step 3 (파일 기반 ACL 활성화) 완료 후. 그때 required-audience 설정과 소스 IP 제한도 함께 검토합니다. 재개 전까지 외부 SQL 접근이 필요하면 SQL 에디터·API·MCP 를 쓰십시오. :::

클러스터 밖(노트북·BI 도구·ETL)에서 Trino coordinator 에 직접 접속하는 경로입니다 (#2376). kubectl port-forward 없이 공개 TLS Ingress 로 붙습니다.

클라이언트 ──HTTPS(443, TLS)──▶ nginx ingress ──HTTP(8080)──▶ trino coordinator
(Authorization: Bearer JWT) (Let's Encrypt 종단) (JWKS 로 JWT 검증)

인증은 UI 와 동일하게 Keycloak JWT 입니다. oauth2-proxy 를 거치지 않으므로 클라이언트가 직접 Bearer 토큰을 첨부합니다 (Trino coordinator 가 issuer/서명 검증).

상태 (#2683 — 2026-08-05 활성화 완료)

과거 이 문서는 "access-control.name=file 이 배포된 적 없어 인가가 시행되지 않는다"고 정정했었습니다. 2026-08-05 부로 파일 ACL 이 prod 에 활성화됐습니다 (helm rev 24): coordinator 에 access-control.name=file + file group provider (refresh-period=60s) 라이브, 서비스 계정의 gendpg 접근 차단 실측.

rules.json 은 이제 수작업 산출물이 아닙니다trino-acl-sync CronJob (매 5분)이 DataGrant/AccessPolicy 에서 자동 생성해 발행 전 불변식 (_assert_invariants — 서비스 규칙 존재·gendpg 차단)을 통과한 것만 cm/trino-access-control 에 patch 합니다 (직전본은 rules.json.bak 키 보존, coordinator 는 60초 주기로 자동 반영). 수동 규칙 편집은 다음 동기화에 덮어써집니다 — 권한 변경은 DataGrant(API/UI)로 하십시오.

자동 반영은 security.refresh-period 에 달려 있다 (v1.1+)

coordinator 의 access-control.propertiessecurity.refresh-period 가 없으면 Trino 는 rules.json 을 기동 시 한 번만 파싱합니다. 이 경우 CronJob 이 ConfigMap 을 갱신하고 TRINO_ACL_SYNC_APPLIED 성공 로그를 남기며 파드 안 파일까지 바뀌어도 권한 판정은 전혀 바뀌지 않습니다(2026-08-05 prod 실측 — 동기화가 도입 이후 엔진에 반영된 적이 없었음).

# 진단 — 이 3줄이 모두 보여야 자동 반영이 성립한다
kubectl -n gend exec deploy/trino-coordinator -- cat /etc/trino/access-control.properties
# access-control.name=file
# security.config-file=/etc/trino/access-control/rules.json
# security.refresh-period=60s ← 없으면 재기동 전까지 무반영

없다면 infra/helm/trino/values-prod.yamlcoordinator.additionalConfigFiles.access-control.properties 에 추가하고 helm upgrade → coordinator 재기동. 회귀 가드는 scripts/test_cross_pr_consistency.py::test_acl_sync_requires_trino_refresh_period (acl-sync CronJob 이 있으면 이 값이 필수).

규칙 모양 불변식 — 생성기·검증기가 함께 고정합니다(어긋나면 패리티 테스트 실패):

대상요구
서비스 계정(gend-api·gend-batch·dagster 등)catalogs/schemas/tables 세 섹션 모두에 규칙 존재 (한 섹션만 빠져도 그 단계에서 전면 거부)
쓰기 카탈로그(iceberg·hive·nessie)서비스 계정 첫 매칭 규칙에 OWNERSHIP·GRANT_SELECT 포함 — 없으면 CREATE TABLE/VIEW 가 거부돼 데이터마트 생성·파이프라인 첫 적재가 막힙니다(v1.1+ 에서 시행)
gendpg서비스 계정 규칙의 catalog 정규식이 gendpg 에 매칭되면 발행 거부

★ 파일 ACL 은 첫 매칭 규칙만 적용합니다 — 뒤에 넉넉한 규칙이 있어도 앞의 좁은 규칙이 이깁니다. 규칙을 손으로 추가할 때 순서를 반드시 확인하십시오.

수동 즉시 반영·상태 확인:

kubectl -n gend create job --from=cronjob/trino-acl-sync acl-sync-manual
kubectl -n gend logs job/acl-sync-manual # TRINO_ACL_SYNC_APPLIED 또는 "변경 없음"

배포 전 정적 게이트: python scripts/validate_trino_rules.py <rules.json|cm.yaml> — 스테일/수작업 규칙(서비스 규칙 부재)을 배포 전에 잡습니다 (2026-08-04 사고의 재발 방지 불변식 포함). :::

선행 조건

  • DNS: trino.gend.genon.ai A레코드삭제됨(#3160). 구 ingress LB 20.214.90.38 은 nginx 해체로 소멸.
  • Ingress 배포: infra/ingress/trino-external.yaml 를 대상 클러스터에 kubectl apply.
  • coordinator process-forwarded=true: infra/helm/trino/values.yaml 반영 후 helm upgrade + 재시작 (결과 폴링 nextUrihttps://trino.gend.genon.ai/... 로 생성되도록).
  • (선택) gend-api self-service JDBC 가이드용 GEND_TRINO_EXTERNAL_HOST=trino.gend.genon.ai 설정 (#1157).
보안 · 인가

외부에 열리므로 인증/인가가 유일한 방어선입니다. TLS 로 토큰은 암호화되지만, required-audience 가 아직 미설정(#530)이라 gend realm 의 임의 토큰이 통과합니다. BI/ETL 은 gend-api 의 trino service-account(client_secret)를 재사용하지 말고 전용 Keycloak client 로 발급하고, rules.json 으로 catalog/table 권한을 최소화하세요.

  • 세션 user = 토큰 principal: user(DB-API/JDBC)는 토큰 preferred_username(client_credentials 면 service-account-<client_id>)과 반드시 일치해야 합니다. 다른 user 로 붙으면 impersonation 규칙이 없어 Access Denied: User ... cannot impersonate user ... 로 거부됩니다 (dev 실증 #2376). 임의 user 실행이 필요하면 rules.json 에 impersonation 규칙을 추가하세요.
  • catalog 권한: 연결 principal 이 rules.json 의 group(admin/analyst/…)에 매핑돼야 실제 데이터(iceberg/tpch 등) 쿼리가 됩니다. 매핑이 없으면 SELECT 1 같은 catalog 없는 쿼리만 통과합니다.

파이썬 (trino DB-API)

import os, requests
from trino.auth import JWTAuthentication
from trino.dbapi import connect

# 1) Keycloak 에서 JWT 발급 (프로그램적 접속 — client_credentials)
token = requests.post(
"https://gend.genon.ai/auth/realms/gend/protocol/openid-connect/token",
data={"grant_type": "client_credentials",
"client_id": os.environ["TRINO_CLIENT_ID"], # 전용 client 권장
"client_secret": os.environ["TRINO_CLIENT_SECRET"]},
timeout=15,
).json()["access_token"]

# 2) 외부 엔드포인트로 HTTPS 접속 (port-forward 불필요)
conn = connect(
host="trino.gend.genon.ai", port=443, http_scheme="https",
auth=JWTAuthentication(token),
# user 는 토큰 principal(preferred_username)과 반드시 동일해야 함.
# client_credentials 면 "service-account-<client_id>". 다르면 "cannot impersonate" 거부.
user="service-account-trino", catalog="iceberg", schema="default",
)
cur = conn.cursor()
cur.execute("SELECT nationkey, name FROM tpch.sf1.nation LIMIT 5")
print(cur.fetchall())

JDBC (DBeaver / Trino JDBC 드라이버)

jdbc:trino://trino.gend.genon.ai:443?SSL=true&accessToken=<JWT>&user=service-account-trino

SSL=true 는 필수(443 TLS), accessToken 은 위 1)에서 발급한 JWT, user 는 토큰 principal 과 동일해야 합니다(아래 주의 박스 참고). DBeaver 는 Driver properties 에 accessToken 을 넣습니다.

변경 이력

  • Phase 5 활성화: #533 (2026-04-24)
  • 운영 가이드 문서 추가: #540
  • upstream 문서 개선: trinodb/charts#421
  • 외부 JDBC TLS Ingress (trino.gend.genon.ai): #2376