> ## Documentation Index
> Fetch the complete documentation index at: https://docs.iru.com/llms.txt
> Use this file to discover all available pages before exploring further.

# 멀티 테넌트 격리

> Iru가 행 수준 보안을 통해 데이터베이스 엔진에서 적용되는 방식으로 모든 조직의 데이터를 분리하고 모든 변경 사항을 변조 감지 가능하게 유지하는 방법입니다.

Iru Identity는 멀티 테넌트 방식으로, 여러 조직이 동일한 서비스를 공유합니다. 이 설계의 가장
중요한 속성은 **한 테넌트가 다른 테넌트의 데이터를 절대 보거나 건드릴 수 없다는 것**입니다.
Iru는 애플리케이션 코드뿐 아니라 데이터베이스 엔진 자체에서 이를 적용하므로, 쿼리에 버그가
있더라도 경계를 넘을 수 없습니다.

## 데이터베이스에서 적용되는 격리

테넌트 데이터를 보관하는 모든 테이블에는 `tenant_id`가 있으며
\*\*PostgreSQL 행 수준 보안(RLS)\*\*으로 보호됩니다.

* 테이블은 RLS가 **활성화되고 강제**(`FORCE ROW LEVEL SECURITY`)되므로 정책이 모든 역할에
  적용됩니다. 이를 우회하는 권한 있는 경로는 없습니다.
* 단일 정책이 읽기와 쓰기를 모두 관리합니다. 행은 `tenant_id`가 현재 요청의 테넌트와 일치할
  때만 표시(`USING`)되고 쓰기 가능(`WITH CHECK`)합니다.

```sql theme={null}
-- The shape every tenant table shares
ALTER TABLE app.<table> ENABLE ROW LEVEL SECURITY;
ALTER TABLE app.<table> FORCE  ROW LEVEL SECURITY;

CREATE POLICY tenant_access_policy ON app.<table> FOR ALL
  USING      (tenant_id = current_setting('iru.tenant_id'))
  WITH CHECK (tenant_id = current_setting('iru.tenant_id'));
```

이는 권한 있는 일부 테이블에만 적용되는 것이 아니라 대규모로 균일하게 적용됩니다(**수백 개의
테이블**, 각각 강제 RLS와 동일한 읽기 및 쓰기 정책 적용).

<Note>
  경계가 데이터베이스에 있으므로 테넌트로 필터링하는 것을 잊은 쿼리는 다른 테넌트의 데이터를
  반환하지 않습니다. 엔진은 활성 테넌트 외부의 행을 표시하거나 변경하는 것을 거부합니다.
</Note>

## 테넌트 컨텍스트는 모든 요청과 함께 전달됩니다

```mermaid theme={null}
flowchart LR
  req["tenant.subdomain에<br/>대한 요청"] --> mw["테넌트 확인<br/>(알 수 없으면 거부)"]
  mw --> tx["DB 트랜잭션 열기"]
  tx --> setl["SET LOCAL iru.tenant_id = …<br/>+ 실행자 + 상관 관계 ID"]
  setl --> rls["RLS 정책이<br/>동일한 설정을 읽음"]
  rls --> data[("이 테넌트의 행만")]
```

1. 테넌트는 미들웨어에서 요청의 하위 도메인으로 확인됩니다. 확인할 수 없으면 요청이 **핸들러에
   도달하기 전에 거부됩니다**.
2. 각 데이터베이스 트랜잭션에 대해 Iru는 테넌트(작업 사용자 및 상관 관계/추적 ID와 함께)를
   **트랜잭션 범위** 설정(`SET LOCAL`)으로 설정합니다. 트랜잭션으로 범위가 지정되므로 풀링된
   연결을 재사용하는 다음 요청으로 누출될 수 없습니다.
3. RLS 정책은 바로 그 설정을 읽으므로, 보안 경계는 애플리케이션이 설정한 것과 동일한 값이며
   모든 연결에서 트랜잭션 방식으로 설정됩니다.

## 감사 기록을 다시 쓸 수 없도록 최소 권한 적용

애플리케이션이 실행되는 데이터베이스 역할은 **라이브** 데이터를 읽고 쓸 수 있지만, **감사
스키마**에 대해서는 **읽기** 액세스 권한만 보유합니다. 애플리케이션 SQL이 어떤 식으로든
악용되더라도 감사 기록을 수정하거나 삭제할 권한이 없습니다. 감사 추적은 애플리케이션 로직과
무관하게 권한 계층에서 보호됩니다.

참조 무결성도 테넌트 범위입니다. 외래 키는 복합 키(`tenant_id` + id)이므로 레코드는 **동일한
테넌트에 있는** 다른 레코드만 참조할 수 있습니다.

## 모든 변경 사항은 기록됩니다(추가 전용)

Iru는 테넌트 데이터 변경 사항에 대한 완전하고 변조 감지 가능한 기록을 보관합니다.

<AccordionGroup>
  <Accordion title="캡처되는 항목" icon="clipboard-list">
    변경 가능한 모든 테이블에는 삽입, 업데이트 및 삭제 시 데이터베이스 트리거가 작성하는 미러
    **감사(저널) 테이블**이 있습니다. 각 항목에는 **무엇이** 일어났는지(작업), **누가**(작업
    데이터베이스 주체와 인증된 사용자 모두), **언제**, 원래 요청과 연결하는 **상관 관계/추적
    ID**, 그리고 행의 **전체 변경 전/후 상태**가 기록됩니다.
  </Accordion>

  <Accordion title="추가 전용인 이유" icon="lock">
    감사 추적은 세 가지 독립적인 방식으로 보호됩니다. 트리거는 저널 행을 *삽입*만 하고,
    애플리케이션의 데이터베이스 역할은 감사 스키마에 대해 **읽기 전용** 액세스 권한을 가지며,
    트리거는 정의자 권한으로 실행됩니다. 애플리케이션이 감사 레코드를 변경하거나 지울 수 있는
    경로는 없습니다.
  </Accordion>

  <Accordion title="조용히 어긋날 수 없습니다" icon="circle-check">
    상시 일관성 검사가 모든 라이브 테이블의 열을 해당 감사 테이블과 비교하고 누락된 열이 있으면
    실패하므로, 새 필드가 감사 추적에서 조용히 빠질 수 없습니다. 스키마 마이그레이션 자체도
    버전이 관리되고, 체크섬으로 검증되며, 기록됩니다.
  </Accordion>
</AccordionGroup>

## 민감한 필드에 대한 추가 보호 계층

격리 외에도 특히 민감한 값은 저장되기 전에 **AES-256-GCM으로 필드 수준에서 암호화**(인증된
암호화)되며 애플리케이션이 읽을 때만 복호화됩니다. 암호화 키는 코드나 데이터베이스가 아닌
관리형 비밀 저장소에서 런타임에 제공됩니다.

## 전체 구조에서의 위치

격리는 **리전별 데이터 상주**와 함께 작동합니다. 각 리전에는 자체 데이터베이스가 있으며,
테넌트의 쿼리는 해당 리전의 데이터베이스에 대해서만 실행됩니다
([플랫폼 아키텍처](/ko/identity/security/platform-architecture) 참조). 해당 데이터베이스
내에서 RLS가 각 테넌트를 분리합니다.

<CardGroup cols={2}>
  <Card title="플랫폼 아키텍처" icon="server" href="/ko/identity/security/platform-architecture">
    이 격리가 실행되는 리전, 플레인 및 인프라입니다.
  </Card>

  <Card title="인증 아키텍처" icon="signature" href="/ko/identity/security/authentication-architecture">
    로그인 및 API 요청이 피싱 방지 및 변조 방지되는 방식입니다.
  </Card>

  <Card title="활동 로그" icon="list-timeline" href="/ko/identity/administration/activity-log">
    관리자가 검토할 수 있도록 제공되는 감사 추적입니다.
  </Card>

  <Card title="보안 및 개인정보 보호" icon="lock" href="/ko/identity/security/security-and-privacy">
    이러한 메커니즘이 제공하는 보장입니다.
  </Card>
</CardGroup>
