본문 바로가기
WIKI 기술 지식 베이스

JWT 로그인

원문 보기 위키 갱신

JWT 로그인 (머신 투 머신)

lakeFS Team과 lakeFS Enterprise에서 사용할 수 있어요. 무료 체험을 시작하거나 문의하세요.

대화형(브라우저) 로그인에는 Single Sign On (SSO)을 사용하세요. AWS에 상주하는 워크로드에는 AWS IAM Roles를 사용하세요.

JWT 로그인은 외부 아이덴티티 프로바이더에서 이미 JWT를 보유하고 있는 워크로드 - 서비스 계정, CI 러너, 애플리케이션 백엔드 - 가 그 JWT를 lakeFS 세션 bearer 토큰으로 교환할 수 있게 해줘요. 브라우저 플로우도 없고, 워크로드마다 lakeFS access key를 프로비저닝할 필요도 없어요.

출처: JWT 로그인 (머신 투 머신)

본문

동작 방식

   ┌──────────┐  client_credentials   ┌──────────┐
   │  Caller  │ ────────────────────► │   IdP    │
   └──────────┘                       │  (Entra/ │
        │   external JWT              │   Auth0) │
        │ ◄────────────────────────── └──────────┘
        │
        │   POST /api/v1/auth/jwt/login
        │   { "token": "<external JWT>" }
        ▼
   ┌──────────┐
   │  lakeFS  │
   └──────────┘
        │   { "token": "<lakeFS bearer>", "token_expiration": ... }
        │
        ▼
   Authorization: Bearer *** bearer>
   → any subsequent lakeFS API call
  • 호출자는 자신의 IdP에서 JWT를 얻어요. 서비스 계정 워크로드의 경우 일반적으로 OAuth 2.0 client_credentials 그랜트예요.

  • 호출자는 그 JWT를 POST /api/v1/auth/jwt/login으로 보내요. lakeFS는 IdP의 JWKS로 서명을 검증하고, 표준 iss / aud / exp / iat / nbf 클레임을 확인하고, JWT의 그룹/역할 클레임을 lakeFS 그룹으로 해석해요.

  • lakeFS는 해석된 정책 ID를 담은 단기 세션(Session) 을 저장하고, subject가 세션 id인 bearer 토큰을 반환해요.

  • 호출자는 이후 lakeFS API 호출에서 `Authorization: Bearer *** 헤더로 bearer를 사용해요.

S3 클라이언트는 외부 JWT나 lakeFS bearer 어느 쪽이든 같은 권한을 지닌 임시 S3 게이트웨이 자격 증명으로 교환할 수 있어요.

JWT 로그인에서 lakeFS 사용자 row는 만들어지지 않아요: 세션이 곧 인가 기록이에요. 서버에서 그 세션을 삭제하면 그 세션에서 발급된 모든 bearer가 즉시 무효화돼요.

언제 사용하나요?

시나리오 사용할 방식
대화형(사람) 로그인 SSO
IAM 역할이 있는 AWS 상주 워크로드 AWS IAM Roles
IdP가 발행한 JWT를 이미 보유한 워크로드 JWT login
IdP가 없는 워크로드 — 정적 자격 증명이 필요한 경우 lakeFS access key

설정

JWT 로그인은 옵트인(opt-in)이에요. auth.providers.jwt.jwks_url이 설정되지 않으면 엔드포인트는 501 Not Implemented를 반환해요.

auth:
  providers:
    jwt:
      # Required.
      jwks_url: https://<idp>/.well-known/jwks.json
      issuer:   https://<idp>/

      # At least one configured audience must match the token's `aud`
      # claim. Empty list skips the audience check.
      audiences:
        - https://lakefs/api

      # RFC 6901 JSON Pointer to the principal identity claim.
      # Defaults to "/oid" (Entra). For Auth0 use "/sub".
      identity_claim_ref: /oid

      # JSON Pointer to the group / role claim. Each value must match a
      # lakeFS group identifier (group name on Enterprise; generated
      # group ID on Cloud — see "Mapping IdP groups to lakeFS
      # permissions"). Default "/roles".
      groups_claim_ref: /roles

      # Caps the lakeFS session's lifetime. Effective expiry is
      # min(now + session_max_ttl, jwt.exp). Default 1h.
      session_max_ttl: 1h

      # Clock-skew tolerance for exp / iat / nbf. Default 60s.
      leeway: 60s

      # How often the background sweep deletes expired sessions.
      # Default 5m.
      cleanup_interval: 5m

      # Pin additional claims to exact string values. See
      # "Per-tenant isolation" below.
      required_claims:
        # https://lakefscloud.io/org_id: acme

필드 레퍼런스

Key Required Default Description
jwks_url yes — IdP의 JWKS 문서 URL.
issuer yes — 토큰의 iss 클레임과 정확히 같아야 하는 값.
audiences no [] 받아들이는 aud 값 목록. 비워 두면 이 검사를 건너뛰어요.
identity_claim_ref no /oid principal 식별자를 가리키는 JSON Pointer.
groups_claim_ref no /roles 그룹/역할 목록을 가리키는 JSON Pointer.
session_max_ttl no 1h 만들어지는 세션 수명의 상한.
leeway no 60s exp / iat / nbf에 허용되는 시계 오차.
cleanup_interval no 5m 만료 세션 정리(sweep) 주기.
required_claims no none 클레임 이름 → 정확한 문자열 값 맵.

엔드포인트

POST /api/v1/auth/jwt/login
Content-Type: application/json

{ "token": "<external JWT>" }
Status Meaning
200 성공. 본문: { "token": "", "token_expiration": }.
401 검증 실패: 서명, 만료, audience, issuer, identity 클레임 누락 등.
501 JWT 로그인이 설정되지 않음(jwks_url 없음).

IdP 그룹을 lakeFS 권한으로 매핑하기

JWT의 groups 클레임에 있는 모든 값에 대해 lakeFS는 일치하는 그룹을 찾아요. 그 그룹들에 연결된 정책의 합집합이 세션에 기록되고, 모든 인가 검사에서 참조돼요.

클레임 값은 lakeFS 그룹 식별자와 일치해야 해요. 그 식별자가 무엇인지는 RBAC 백엔드에 따라 달라져요:

배포 형태 클레임이 담아야 하는 그룹 식별자
lakeFS Enterprise (built-in RBAC) 그룹 이름, 예: data-engineers
lakeFS Cloud (external RBAC) 그룹의 생성된 ID, 예: LGIDAfIbmpkx-711slxX-BGKt — display name이 아니에요

lakeFS Cloud에서 그룹은 생성된 ID(LGID…)를 가지고, 사람이 읽기 좋은 문자열은 display name일 뿐이에요. OIDC 설정이 그룹을 ID로 참조하는 이유(default_initial_groups: [LGID…])와 같아요. display name을 담은 클레임은 어떤 정책으로도 해석되지 않아요. 그룹의 ID는 lakectl auth groups list나 UI에서 찾을 수 있어요.

Warning

일치하지 않는 그룹 값은 조용히 건너뛰어져요. groups 클레임이 어떤 그룹 식별자와도 일치하지 않는 토큰은 권한 없는 세션을 만들고, 모든 authorized 호출이 401 insufficient permissions를 반환해요(토큰은 여전히 인증되고 - 세션은 만들어지고 - 다만 아무 것도 할 수 없어요). JWT 기반 호출을 내보내기 전에 그룹을 만들고, 정책을 연결하고, IdP가 올바른 식별자를 내보내는지 확인하세요.

Microsoft Entra ID

Entra 설정 (한 번만)

  • App Registration — "lakeFS" (리소스)

  • Expose an API → Application ID URI: 예. api://lakefs. 이 값이 audience예요.

  • App roles → Create app role:

  • 허용 멤버 유형: Applications

  • 값: lakefs-data-engineers (이 값이 roles 클레임으로 내보내지고 lakeFS 그룹 ID와 일치해야 해요).

  • App Registration — "lakeFS-client" (호출자)

  • Certificates & secrets → New client secret - 값을 기록해 두세요.

  • API permissions → Add a permission → My APIs → lakeFS → lakefs-data-engineers 애플리케이션 권한에 체크 → Grant admin consent.

  • (선택 사항, 멀티 테넌트 배포에서 권장) 서비스 principal의 앱 메타데이터에서 org_id(또는 유사한) 클레임을 주입하는 claims-mapping 정책을 추가하고, lakeFS 설정의 required_claims로 고정하세요. 아래 Per-tenant isolation을 참고하세요.

lakeFS 설정

auth:
  providers:
    jwt:
      jwks_url: https://login.microsoftonline.com/<tenant-id>/discovery/v2.0/keys
      issuer:   https://login.microsoftonline.com/<tenant-id>/v2.0
      audiences: ["api://lakefs"]
      identity_claim_ref: /oid
      groups_claim_ref:   /roles
      session_max_ttl:    1h

일치하는 lakeFS 그룹을 미리 만들고 정책을 연결해요:

lakectl auth groups create --id lakefs-data-engineers
lakectl auth policies create --id ReadAll --statement-document - <<'EOF'
{ "statement": [
    { "effect": "allow", "action": ["fs:*"], "resource": "*" }
]}
EOF
lakectl auth groups policies attach --id lakefs-data-engineers --policy ReadAll

교환 플로우

# 1. Acquire an Entra access token via client_credentials.
TOKEN=$(curl -s -X POST \
  "https://login.microsoftonline.com/$TENANT_ID/oauth2/v2.0/token" \
  -H 'content-type: application/x-www-form-urlencoded' \
  -d "client_id=$ENTRA_CLIENT_ID\
&client_secret=$ENTRA_CLIENT_SECRET\
&scope=api%3A%2F%2Flakefs%2F.default\
&grant_type=client_credentials" | jq -r .access_token)

# 2. Exchange for a lakeFS bearer.
BEARER=$(curl -s -X POST "https://lakefs.example.com/api/v1/auth/jwt/login" \
  -H 'content-type: application/json' \
  -d "{\"token\": \"$TOKEN\"}" | jq -r .token)

# 3. Drive authenticated API calls.
curl -H "Authorization: Bearer ***" \
  "https://lakefs.example.com/api/v1/repositories"

토큰 형태 검증

올바른 클레임이 있는지 페이로드를 디코딩해서 확인해요:

echo "$TOKEN" | cut -d. -f2 | base64 -d 2>/dev/null | jq .

기대되는 필드:

{
  "iss": "https://login.microsoftonline.com/<tenant-id>/v2.0",
  "oid": "<service-principal-object-id>",
  "aud": "api://lakefs",
  "roles": ["lakefs-data-engineers"],
  "iat": 1700000000,
  "exp": 1700003600
}

roles가 없다면 App Role이 Allowed member types = Applications로 만들어졌는지, 그리고 그 역할에 대해 클라이언트 애플리케이션에 admin consent가 부여됐는지 확인하세요.

Auth0

Auth0 설정 (한 번만)

  • APIs → Create API

  • Identifier (audience가 됨): 예. https://lakefs/api

  • Signing Algorithm: RS256 (기본)

  • Enable RBAC: 켬

  • Add Permissions in the Access Token: 켬

  • Permissions → Add a Permission: 사용하려는 lakeFS 그룹마다 하나씩 권한을 정의해요. 권한의 이름은 lakeFS 그룹 식별자와 같아야 해요 - lakeFS Cloud에서는 display name이 아니라 생성된 그룹 ID(예. LGIDAfIbmpkx-711slxX-BGKt)예요. ID를 붙여넣을 때 어중간한 마지막 문자가 따라붙지 않도록 주세요.

  • Applications → Create Application → Machine to Machine

  • 위 API에 대해 새 애플리케이션을 승인해요.

  • 애플리케이션의 행을 펼쳐 1단계에서 정의한 권한(들)에 체크해요. 그러면 M2M 클라이언트는 발급되는 모든 access token의 permissions 클레임으로 이것들을 받아요.

  • client_id와 client_secret을 기록해 두세요.

lakeFS 설정

auth:
  providers:
    jwt:
      jwks_url: https://YOUR_TENANT.us.auth0.com/.well-known/jwks.json
      issuer:   https://YOUR_TENANT.us.auth0.com/
      audiences: ["https://lakefs/api"]
      identity_claim_ref: /sub
      groups_claim_ref:   /permissions
      session_max_ttl:    1h

Auth0 권한 이름과 ID가 일치하는 lakeFS 그룹을 만들고 정책을 연결해요(Entra 예제와 동일한 lakectl auth … 명령).

교환 플로우

# 1. Acquire an Auth0 access token via client_credentials.
TOKEN=$(curl -s -X POST "https://YOUR_TENANT.us.auth0.com/oauth/token" \
  -H 'content-type: application/x-www-form-urlencoded' \
  -d "grant_type=client_credentials\
&client_id=$AUTH0_CLIENT_ID\
&client_secret=$AUTH0_CLIENT_SECRET\
&audience=https://lakefs/api" | jq -r .access_token)

# 2. Exchange for a lakeFS bearer.
BEARER=$(curl -s -X POST "https://lakefs.example.com/api/v1/auth/jwt/login" \
  -H 'content-type: application/json' \
  -d "{\"token\": \"$TOKEN\"}" | jq -r .token)

# 3. Drive authenticated API calls.
curl -H "Authorization: Bearer ***" \
  "https://lakefs.example.com/api/v1/repositories"

토큰 형태 검증

echo "$TOKEN" | cut -d. -f2 | base64 -d 2>/dev/null | jq .

기대되는 필드:

{
  "iss": "https://YOUR_TENANT.us.auth0.com/",
  "sub": "<m2m-client-id>@clients",
  "aud": "https://lakefs/api",
  "permissions": ["lakefs-data-engineers"],
  "iat": 1700000000,
  "exp": 1700003600
}

permissions가 없거나 비어 있다면, API에 Enable RBAC과 Add Permissions in the Access Token이 둘 다 켜져 있는지, 그리고 M2M 애플리케이션에 그 권한이 체크되어 있는지 다시 확인하세요.

보안

비대칭 서명만 지원

검증기는 RS256/384/512, ES256/384/512, PS256/384/512를 받아들여요. HMAC 변형(HS*)과 none은 시작 시 거부돼요 - 대칭 서명은 JWKS 신뢰 모델과 호환되지 않아요.

테넌트별 격리

하나의 IdP가 여러 lakeFS 인스턴스를 위한 토큰을 발급하는 멀티 테넌트 배포(예: 하나의 Entra 테넌트가 여러 lakeFS Cloud 조직을 제공)에서는, 테넌트 A를 위해 발급된 토큰이 테넌트 B의 lakeFS에서 깨끗하게 검증될 수 있어요. iss가 같고, 시그니처가 유효하고, 둘 다 같은 Application ID URI를 사용하면 audience도 일치하거든요.

required_claims로 테넌트별 클레임을 고정해 완화해요:

auth:
  providers:
    jwt:
      ...
      required_claims:
        https://lakefscloud.io/org_id: <this-tenant>

서비스 principal의 앱 메타데이터에서 같은 클레임을 주입하는 IdP 쪽 claims-mapping 정책과 결합하면, 한 테넌트를 위해 발급된 토큰이 다른 테넌트에 사용될 수 없음을 보장해요.

IdP가 M2M 토큰에 대해 테넌트별 클레임을 내보낼 수 없다면 - 예를 들어 Organizations for Client Credentials가 없는 Auth0 테넌트에서는 client_credentials 그랜트에 네이티브 org_id 클레임이 제공되지 않아요 - azp(authorized party = M2M client_id)를 매칭해 전용 클라이언트를 고정하세요:

auth:
  providers:
    jwt:
      ...
      required_claims:
        azp: <m2m-client-id>

그러면 각 테넌트는 자기만의 M2M 애플리케이션을 사용하고, 그 클라이언트의 토큰만 받아들여져요. IdP Organization 기능에 의존하지 않고도 실질적인 격리 보장을 유지할 수 있어요.

Warning

공유 IdP 배포에서 테넌트별 격리를 건너뛰는 것은 교차 테넌트 권한 상승 위험이에요. 둘 이상의 lakeFS 인스턴스가 같은 IdP를 신뢰할 때는 항상 required_claims(org/tenant 클레임, 최소한 azp)을 설정하세요.

로깅되는 데이터

요청별 로그와 감사 레코드는 세션 인증된 호출자를 principal_type=session, 세션의 subject(예. jwt:<iss>:<oid>), 고유한 session_id로 식별해요. 원본 외부 JWT는 절대 로그로 남지 않아요. 검증기 에러는 클레임 이름과 타임스탬프를 노출하지만, 원본 토큰 바이트는 절대 노출하지 않아요.

취소(Revocation)

세션은 서버 쪽에 저장되기 때문에 취소는 즉각적이에요: 세션을 삭제하면 - 명시적으로든 만료되게 하든 - 그 세션에서 발급된 모든 bearer가 다음 요청에서 401을 반환해요. bearer의 시그니처가 여전히 유효하더라도요. cleanup_interval 설정은 만료된 세션이 KV에서 얼마나 자주 제거되는지 제어하고, 만료된 세션은 읽기 시점에 느긋하게(lazily) 수집되기도 해요.

감사 로그

JWT 로그인 bearer가 구동하는 모든 액션은 감사 레코드에 세션 principal을 담아요:

컬럼 값
principal_type session
subject jwt:: (세션의 subject)
session_id 살아 있는 세션 엔티티의 id
user subject와 동일(하위 호환 컬럼)

운영자는 감사 행을 principal_type=session으로 필터링해 설치 환경 전반의 모든 M2M 호출을 볼 수 있고, session_id로 필터링해 단일 로그인이 구동하는 모든 액션을 상관(correlate)할 수 있어요.

더 알아보기 (Learn more)

공식 문서의 원문은 https://docs.lakefs.io/security/jwt-login/ 에서 확인할 수 있어요.