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

Tenants

원문 보기 위키 갱신

Tenants (테넌트)

lakeFS Enterprise에서 사용할 수 있어요. 문의하세요.

Private preview

Tenants는 자체 관리 lakeFS Enterprise에서 private preview 상태이며, 일반 공급 전에 인터페이스와 동작이 바뀔 수 있어요. 여러 서피스가 아직 테넌트를 인식하지 못하니, 이 기능에 의존하기 전에 Known limitations을 참고하세요.

여러 팀이 하나의 lakeFS 설치 환경을 공유할 때, 로그인한 모든 사용자는 원칙적으로 모든 저장소를 볼 수 있고, 한 팀의 데이터와 다른 팀의 데이터 사이에는 신중하게 작성된 RBAC 정책 집합만이 존재해요. 이 방식도 동작하지만, 플랫폼 팀이 격리 규칙을 손으로 쓰는 일을 감당해야 하고, 잘못 작성된 단 하나의 정책만으로도 팀 경계를 넘어 데이터가 노출될 수 있어요.

Tenants는 그런 손으로 쓴 규칙을 서버가 강제하는 경계로 대체해요. 테넌트는 자기만의 저장소를 소유하는 설치 환경의 이름 붙은 파티션이고, 사용자는 그 테넌트의 멤버일 때만 그 데이터에 도달할 수 있어요. 멤버십은 사용자가 어떤 정책을 가지고 있든 모든 데이터 플레인 액션마다 검사되기 때문에, 정책만으로는 절대 경계를 넘을 수 없어요. 팀 온보딩은 테넌트를 만들고 그룹을 연결하는 일이 되죠. 그리고 기존 저장소는 root라는 예약된 테넌트에 살고 있어서, 이 기능을 켜도 이미 실행 중인 설치 환경은 아무 변화가 없어요.

출처: Tenants (테넌트)

본문

테넌트 범위와 공유 리소스

각 저장소는 정확히 하나의 테넌트에 속하고, 저장소 나열은 요청에 선택된 테넌트의 저장소만 보여줘요. 접근에는 테넌트 멤버십과 일치하는 RBAC 정책이 둘 다 필요해요. lakeFS 테넌트는 여러분의 애플리케이션 접근 제어 모델의 테넌트 식별자와 독립적이에요.

다음은 설치 환경 전체에서 공유돼요:

  • 사용자, 그룹, 정책. 이들은 설치 수준에서 관리되고, 테넌트 멤버십이 각 테넌트로의 접근을 제어해요.

  • 저장소 이름과 스토리지 네임스페이스. 둘 다 테넌트별이 아니라 설치 환경에서 고유해요. 그래서 한 테넌트에서 이미 사용 중인 이름이나 네임스페이스는 root를 포함한 다른 모든 테넌트에서 거부돼요.

  • 스토리지 배치와 성능. 다른 테넌트의 저장소들이 같은 기반 리소스를 공유하기 때문에, 한 테넌트는 전용 용량이나 다른 테넌트의 부하로부터의 보호를 얻지 못해요.

  • 정책 연결. 정책은 설치 수준에서 사용자와 그룹에 연결되기 때문에, 사용자를 테넌트에 추가하면 기존 정책 부여가 그곳에서 적용될 수 있어요.

알려진 제한 사항

Tenants는 자체 관리 lakeFS Enterprise에서 private preview 상태이며, 일반 공급 전에 인터페이스와 동작이 바뀔 수 있어요. 다음은 아직 지원되지 않아요:

  • lakeFS Cloud. Tenants는 호스티드 서비스에서 사용할 수 없어요.

  • lakeFS Mount. Mount는 스코프 없는 요청을 보내서, root 테넌트의 저장소에서만 동작하고, 다른 테넌트에 있는 저장소를 마운트하려는 시도는 그 저장소가 존재하지 않는 것처럼 실패해요.

  • Metadata search. 인덱서는 테넌트 스코프 없이 실행되고, 인덱싱하도록 설정된 저장소는 root 테넌트에서만 해석되며, 그것이 만드는 인덱스 저장소도 그곳에 살아요.

  • Datasets. 데이터셋 메타데이터 키와 데이터셋 풀 리퀘스트는 테넌트로 스코프되지 않고 설치 환경 전체예요.

  • Audit log. 모든 테넌트의 활동은 root 테넌트의 단일 로그에 기록되고, 각 엔트리는 자신이 스코프된 테넌트 이름을 담아요. reading audit records across tenants를 참고하세요.

  • 테넌트 수 제한. 라이선스는 현재 테넌트 수에 제한을 강제하지 않지만, 이후 릴리스에서 그럴 거예요.

  • High-Level Python 패키지 (PyPI의 lakefs). 테넌트 지원이 없어요. 생성된 SDK 클라이언트를 사용하세요. 생성 시 테넌트를 받아들여요.

테넌트 격리 동작 방식

사용자, 그룹, 정책은 설치 환경 전체에 걸쳐 유지돼서, 아이덴티티 관리와 싱글 사인온은 정확히 그대로예요. Tenants가 추가하는 것은 데이터 플레인 앞의 멤버십 게이트예요. 모든 저장소는 정확히 하나의 테넌트에 속하고, 요청이 테넌트로 스코프되면 lakeFS는 어떤 fs: 권한을 평가하기 전에 호출자가 그 테넌트의 멤버인지 확인해요. 모든 저장소에 FSFullAccess를 보유한 사용자도 자신이 속하지 않는 테넌트의 저장소를 읽을 수 없어요. 정책이 고려되기 전에 게이트가 요청을 거부하기 때문이에요.

테넌트 관리는 별도로, 멤버십이 아니라 정책만으로 거버넌스돼요. 따라서 테넌트를 관리하는 능력과 그 데이터를 읽을 수 있는 능력은 독립적이고, 데이터도 필요한 관리자는 멤버이기도 해야 해요. 이 분리가 위임을 안전하게 만드는 것이고, 그것을 표현하는 정책은 아래 delegating tenant administration에서 설명돼요.

아이덴티티가 전역이기 때문에, auth:*를 보유한 설치 관리자는 테넌트 전체에 대한 완전한 가시성을 유지하고 자신을 어느 테넌트에든 연결할 수 있어요. 이는 설계상 감독 누락이 아니라 수용된 속성이므로, 설치 관리자 자격 증명에 그에 상응하는 주의를 기울이세요.

아래 요청 경로는 게이트가 인증과 정책 평가에 상대적으로 어디에 위치하는지 보여줘요.

flowchart TD
    A["Request<br/>X-LakeFS-Tenant: team-a"] --> B[Authenticate the caller]
    B --> C["Resolve the tenant<br/>no header means root"]
    C --> D{Is the caller a member<br/>of the tenant?}
    D -- no --> E["404, indistinguishable from<br/>a tenant that does not exist"]
    D -- yes --> F[Evaluate RBAC policies]
    F -- deny --> G[Denied]
    F -- allow --> H["Repository inside<br/>the tenant"]

멤버십 게이트는 어떤 정책이 평가되기 전에 실행돼요. 그래서 어떤 정책도 호출자가 속하지 않은 테넌트의 저장소에는 도달할 수 없어요.

root 테넌트

모든 설치 환경에는 root라는 예약된 테넌트가 있어요. 이곳은 테넌트가 도입되기 전에 만들어진 모든 저장소와, 테넌트 스코프 없이 만들어진 저장소를 보관해요. root는 암시적이고, 생성·업데이트·삭제될 수 없으며, 테넌트 나열에도 절대 나타나지 않아요. 테넌트 스코프를 실지 않은 요청은 root에서 동작해요. 그래서 tenants를 활성화해도 기존 클라이언트는 그대로 동작해요.

테넌트 이름은 저장소 이름과 같은 문자 규칙을 따르고, S3 버킷 어드레싱에서 저장소 이름과 나란히 나타날 때 이름의 모호함을 막는 두 가지 추가 제한이 있어요: 테넌트 이름은 이중 하이픈(--)을 포함할 수 없고 하이픈으로 끝날 수 없어요. root라는 이름은 예약돼 있어요.

테넌트 생성과 멤버십 관리

테넌트는 REST API, lakectl, 관리자 UI를 통해 생성되고, 생성에는 auth:CreateTenant 권한이 필요해요. 테넌트를 만든 호출자는 자동으로 그곳에 연결되므로, 새 테넌트는 만든 사람이 바로 사용할 수 있어요.

lakectl auth tenants create --name team-a --description "Team A workspace"
lakectl auth tenants list
lakectl auth tenants show --name team-a

멤버십은 REST API, lakectl, 관리자 UI를 통해, 개별 사용자든 그룹 전체든 테넌트에 연결하는 방식으로 관리돼요. 그룹을 연결하는 것이 목표로 삼을 패턴이에요. 아이덴티티 프로바이더가 사용자별 호출이 아니라 기존 그룹 할당을 통해 테넌트 접근을 구동하게 할 수 있거든요:

lakectl auth tenants groups attach --name team-a --group data-engineering
lakectl auth tenants groups list --name team-a
lakectl auth tenants groups detach --name team-a --group data-engineering

lakectl auth tenants users attach --name team-a --user jane.doe
lakectl auth tenants users list --name team-a
lakectl auth tenants users detach --name team-a --user jane.doe

같은 연산들이 API에서 /auth/tenants/{tenant}/groups/{group}과 /auth/tenants/{tenant}/users/{user}에 대한 PUT과 DELETE로 제공돼요. 테넌트 이름으로 '*'를 넘기면 모든 테넌트에 한꺼번에 연결돼요. 이후에 만들어진 테넌트를 포함해서요. 가비지 컬렉션에서 사용하는 것 같은 설치 전체 아이덴티티가 부여되는 방식이고, 셸이 그것을 확장하지 않도록 이름은 따옴표로 감싸요. 이렇게 연결된 멤버는 어떤 단일 테넌트의 나열에도 나타나지 않아요; lakectl auth tenants users list --name '*'로 그들을 볼 수 있어요.

사용자는 GET /user/tenants로 자신이 속한 테넌트를 알아낼 수 있고, 웹 UI의 테넌트 전환기도 그 목록을 채우는 데 이것을 사용해요. 테넌트 삭제는 그것이 어떤 저장소도 보유하지 않아야 하고, 저장소가 남아 있으면 요청이 충돌로 실패해요. 멤버십은 테넌트와 함께 제거돼요.

요청을 테넌트로 스코핑하기

요청은 X-LakeFS-Tenant 헤더에 자신의 테넌트를 실어요. 그 헤더가 없는 요청은 root 테넌트에서 동작해요. REST API, S3 게이트웨이, Iceberg REST 카탈로그는 같은 헤더를 존중하기 때문에, 테넌트 선택은 자격 증명의 속성이 아니라 요청의 속성이고, 한 세트의 자격 증명이 소유자가 속한 모든 테넌트에서 동작할 수 있어요. 헤더는 테넌트를 선택하지만 접근을 부여하지는 않아요. SDK 사용자는 클라이언트의 기본 헤더로 X-LakeFS-Tenant를 설정해 테넌트를 선택해요.

테넌트 스코프 데이터 요청에서 lakeFS는 테넌트가 존재하지 않거나 호출자가 멤버가 아니면 404와 tenant not found 메시지를 반환해요. 같은 응답을 사용함으로써 호출자가 다른 테넌트를 발견하는 것을 막아요.

Forward the tenant header through proxies and gateways

프록시, 게이트웨이, 인터셉터는 전달되는 요청과 모든 후속 요청에서 X-LakeFS-Tenant를 보존해야 해요. 헤더가 없으면 lakeFS는 root로 기본값을 잡아서, 연속된 연산이 서로 다른 테넌트를 대상으로 하고 저장소 설정이 불완전하게 남을 수 있어요.

lakectl은 같은 스코핑을 --tenant 플래그, LAKEFS_DEFAULT_TENANT 환경 변수, 설정 파일의 tenant 키로 노출해요. 우선순위 순서대로예요. root로 돌아가야 할 때 --tenant root를 넘기면 설정된 기본값을 재정의해요:

lakectl --tenant team-a repo list
export LAKEFS_DEFAULT_TENANT=team-a
lakectl repo list          # scoped to team-a
lakectl --tenant root repo list

저장소는 요청이 스코프된 테넌트 안에서 만들어지고, 이후에는 테넌트 사이를 절대 이동하지 않아요. 저장소 나열은 활성 테넌트의 것만 반환하기 때문에, 같은 명령을 두 개의 다른 스코프 아래에서 실행하면 서로 소를 벗어난 두 집합이 보여요.

웹 UI에서는 내비게이션 바의 테넌트 전환기가 세션의 스코프를 설정하고, 인터페이스의 나머지는 선택된 테넌트가 설치 환경 전체인 것처럼 동작해요.

테넌트 관리 위임하기

테넌트의 요점은 그 테넌트만의 관리자가 설치 전체 권한 없이 테넌트를 운영할 수 있다는 거예요. 그 위임은 preconfigured policies에서 문서화된 두 정책으로 표현돼요. 둘 다 자동 프로비저닝되지 않아요. 테넌트별 부여는 적용될 테넌트를 지정하기 때문에 테넌트마다 한 번씩 작성되어야 하거든요.

테넌트별 관리자는 arn:lakefs:auth:::tenant/team-a에 auth:ReadTenant, auth:ListTenants, auth:UpdateTenant와 네 가지 attach/detach 액션을 부여하는 정책을 보유해요. 이를 통해 테넌트 생성과 삭제는 설치 관리자에게 남겨두면서 자기 테넌트의 멤버십과 설명을 관리할 수 있어요. 그 정책을 팀의 그룹에 연결하는 것이 스스로 관리하는 팀 온보딩의 전부예요.

테넌트 나열은 그들의 정책이 승인하는 테넌트만 반환하기 때문에, 자신의 것만 보고 다른 것은 아무 것도 보지 못해요. 웹 UI의 테넌트 관리 화면도 같은 단일 테넌트를 렌더링해요. 다른 어떤 테넌트든 접근이 거부된 것처럼이 아니라 존재하지 않는 것처럼 응답해요. 즉 위임된 관리자는 API로 어떤 다른 테넌트들이 존재하는지 발견할 수 없어요.

위임된 관리자는 그 테넌트에 대한 auth:ReadTenant로 자기 테넌트의 사용자와 그룹을 나열할 수 있어요. 설치 환경의 모든 사용자나 그룹을 나열하려면 각각 auth:ListUsers나 auth:ListGroups가 필요해요.

S3 게이트웨이를 통한 테넌트 어드레싱

S3 게이트웨이는 버킷 이름만 가지고 작업해요. 그래서 테넌트 스코프 저장소에 도달하려면 두 이름을 그 하나의 문자열 안에 인코딩해야 해요. gateways.s3.resolve_tenant를 true로 설정하면 복합 버킷 네임스페이스가 켜져요. team-a--my-repo 형태의 이름이 첫 번째 이중 하이픈에서 분리되어, 테넌트 team-a 안의 저장소 my-repo를 어드레싱해요. 이 플래그는 기본으로 비활성화되어 있고, 비활성화된 동안에는 모든 버킷 이름이 통째로 취급되고 모든 요청이 root에서 동작해요.

분리는 이름에서 첫 번째 --를 가져가기 때문에, 자체 이름에 이중 하이픈을 포함하는 root의 저장소는 resolution이 활성화되면 테넌트를 한 번은 명시적으로 말해줘야 해요. 저장소 이름은 이 기능의 제한을 받지 않고, 명시적인 root-- 프리픽스가 그런 저장소를 계속 도달 가능하게 유지해요. 그래서 root에 있는 team-a--data라는 이름의 저장소는 root--team-a--data로 어드레싱돼요:

aws s3 ls s3://team-a--my-repo/main/            # my-repo, in tenant team-a
aws s3 ls s3://my-repo/main/                    # my-repo, in root
aws s3 ls s3://root--team-a--data/main/         # a root repository named team-a--data

복합 버킷 이름은 virtual-host 어드레싱에서도 같은 방식으로 동작해요. 전체 이름이 게이트웨이의 와일드카드 레코드가 이미 커버하는 단일 DNS 라벨을 차지하거든요.

버킷 나열도 prefix 파라미터를 통해 같은 네임스페이스에 참여해요. 구분자를 실은 프리픽스는 나열을 그 앞에 이름 지어진 테넌트로 스코프하고, 나머지는 저장소 이름을 필터링하고, 반환되는 각 이름은 인쇄된 대로 정확히 어드레싱될 수 있도록 그 프리픽스를 실어요. 따라서 team-a-- 프리픽스는 그 테넌트 전체를 나열하고, root--는 구분자를 포함하는 root 저장소에 도달하고, 완전한 구분자가 없는 프리픽스는 그동안 그래왔듯 root를 필터링해요. prefix 파라미터는 S3 API에 최근 추가된 것이고, 그것을 보낼 수 없는 클라이언트는 계속 root 나열을 받아요. 어떤 테넌트들이 존재하는지 발견하는 일은 버킷 나열이 아니라 REST API의 auth:ListTenants의 몫이에요.

테넌트 어드레싱은 발견 채널이 되지 않도록 신경 쓰기도 해요. 존재하지 않는 테넌트와 호출자가 멤버가 아닌 테넌트 둘 다 NoSuchBucket으로 응답하고 둘 다 아무 것도 나열하지 않아요. 그래서 버킷 어드레싱이나 나열 프리픽스가 어떤 테넌트들이 존재하는지 드러내지 않아요.

게이트웨이를 다른 S3 엔드포인트와 나란히 운영한다면 한 가지 상호작용은 주목할 가치가 있어요. gateways.s3.fallback_url 설정은 lakeFS가 가지지 않은 저장소에 대한 요청을 다른 엔드포인트로 포워드해요. lakeFS가 S3와 나란히 실행되는 방식이죠. 테넌트 어드레싱 버킷 이름은 명백히 lakeFS 주소라서 절대 폴백하지 않아요: 테넌트 안의 미스는 NoSuchBucket으로 응답하고, root 스코프 미스만 폴백에 도달해요.

정책의 테넌트 스코핑

정책은 리소스 ARN의 account 세그먼트를 통해 테넌트에 고정될 수 있어요. 다섯 번째 콜론 구분 필드이고 대부분의 정책이 사용하는 ARN에서는 비어 있어요. ARN reference는 문법과 네 가지 케이스를 전부 다뤄요. 세그먼트를 생략하는 것이 계속 모든 테넌트를 의미해서 기존 정책이 동작을 유지하는 이유도 포함돼요.

더 알아보기 (Learn more)

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