SQL/POSTGRESQL.

[PostgreSQL] Row Level Security

devz0 2023. 3. 30. 11:31
728x90

Row Level Security (행 수준 보안)

간단하게 PostgreSQL 데이터 베이스 테이블에 추가적으로 적용가능한 필터라고 볼 수 있다. 테이블 데이터에 접근하거나 수정,삭제와 같은 특정 작업들을 수행할 수 있도록 특정 그룹 사용자에게 권한을 부여하고 요청이 오면 쿼리 조건을 수행 하기 전 가장 먼저 적용되어 특정 정책에 따라 데이터가 축소되거나 엑세스 자체가 거부될 수 있다.

 

RLS 장점

1. RLS를 사용하면 개발자가 동일한 데이터베이스에 여러 테넌트에 대한 데이터를 안전하게 저장할 수 있다.

2. RLS 보안 정책은 쿼리를 실행하는 테넌트에 속하지 않는 행을 필터링해준다.

3. 데이터베이스 내부에 필터 논리를 중앙화하면 유지 관리가 단순해지고 보안 오류 위험이 줄어든다.

 

동작 예시

Django와 같은 대부분의 웹 응용프로그램은 단일 사용자로 데이터베이스에 연결하여 RLS를 활용하기 어렵기 때문에 각 애플리케이션 사용자에 대한 데이터베이스 사용자 (ROLE)를 생성해준다.

 

PostgreSQL는 세분화된 액세스 권한을 가진 사용자 및 역할을 생성할 수 있다. 새로운 사용자 또는 역할에는 각 데이터베이스에 필요한 권한이 선택적으로 부여되어야 하는데 이는 권한을 가진 사용자 및 역할을 생성하는 프로세스를 복잡하게 만든다.

→ 그렇게 때문에 애플리케이션 및 액세스 요구 사항에 따라 특정 권한 집합을 가진 역할을 생성해 각 사용자에게 적절한 역할을 할당해주는 것이 좋다. ( Postgresql에서 세분화된 액세스 제어를 설정하기 위해 권장되는 접근 방식 )

 

RLS 적용 방법

PostgreSQL RLS로 검색시 기본적인 예제들이 많이나온다. 따로 결과는 가져오지 않고 생성 SQL문에 대해서만 해석하려고 한다.

1. 데이터 베이스 계층의 RLS 적용

Django 사용 이전에 먼저 Postgresql에서 RLS를 적용시켜주어야 한다.

  • a. 유저 한명당 데이터 베이스 사용자(ROLE)를  생성 시킨다.
CREATE ROLE “1”;

    👉🏻 ROLE 생성시 고유한 값으로 중복이 되지 않도록 유저를 만들어야 하기 때문에 pk값으로 생성한다.

    👉🏻 int 타입으로 사용자를 만들 수 없기때문에 “” 추가해준다.

 

 

  • b. 권한을 부여할 데이터베이스 사용자도 함께 생성 시킨다.
CREATE ROLE rls_user;

    👉🏻 모든 사용자들이 상속 할 수 있는 권한 부여용 역할이다.

 

 

  • c. 권한 부여

GRANT의 기본 형식은 다음과 같다

GRANT || SELECT, INSERT,UPDATE,DELETE, TRUNCATE와 같은 권한 목록 지정 ALL옵션 가능
|| ON || 테이블 이름 지정 || TO 권한을 부여하려는 ROLE의 이름

   

GRANT USAGE ON SCHEMA PUBLIC TO rls_user;

    👉🏻 사용자가 테이블 등에 작업을 적용하기 위해 데이터베이스 개체와 상호작용 할 수 있도록 하는 권한을 부여해준다.

    👉🏻 ROLE 사용 권한을 스키마에 부여해준다.

 

GRANT ALL ON ALL TABLES IN SCHEMA PUBLIC TO rls_user;

    👉🏻 테이블 데이터를 읽고쓰고수정할 수 있는 모든 권한 부여해준다.

 

GRANT ALL ON ALL SEQUENCES IN SCHEMA PUBLIC TO rls_user;

    👉🏻 public 스키마의 모든 시퀀스에 대한 권한 부여해준다.

    👉🏻 SEQUENCES : 고유한 아이디값, 고유성을 보장하고 조건 적용 및 오버헤드 방지

 

 

  • d. RLS 설정
ALTER TABLE tbl_name ENABLE ROW LEVEL SECURITY;

    👉🏻 해당 테이블에 대한 RLS 정책 활성화시킨다.

    👉🏻  기본적으로 슈퍼유저, 테이블 소유자는 RLS에 영향을 받지 않는다. BYPASSRLS

 

CREATE POLICY policy_name ON tbl_name FOR ALL TO rls_user USING(tenant_id::text = current_user);

    👉🏻 각 사용자가 자신의 데이터를 볼 수 있도록 RLS 정책을 생성한다.

    👉🏻 TO rls_user : rls_user 역할의 구성원만 특정 테이블 행에 엑세스 가능

    👉🏻  데이터베이스 사용자를 "1"의 str 형태로 생성해주었기 때문에 tenant_id 형변환 연산자 :: 를 사용해 변환해주어야한다.

    👉🏻  USING 조건절: 같은 이름의 컬럼들 중 특정 컬럼에 대해서만 선택적으로 JOIN

 

 

 

2. 어플리케이션 계층의 RLS 적용

  • a. 미들웨어 단에서 세션변수 설정 (current_user 설정)

        세션 변수

               - 앱에서 특정 사용자의 상태를 저장하는 데 사용된다. 세션 변수를 사용하면 사용자의 브라우저가 서버에 요청을 보낼 때마다

                 새로운 정보를 입력하지 않고도 사용자 정보를 저장할 수 있다.

 

SET ROLE role_name;

    👉🏻 데이터베이스 사용자를 애플리케이션 사용자로 전환시킨다.

    👉🏻 미들웨어 함수 내 요청 객체에서 사용자의 id를 얻어 DB로 연결해 애플리케이션 사용자의 id와 일치하는 데이터베이스 사용자로 설정

 

 

 

미들웨어단에서 request.user의 company_id를 얻고 데이터베이스와 연결해 app유저의 ID와 일치하는 데이터베이스 사용자에게 역할을 성공적으로 설정해주면 RLS 정책을 유지하면서 Django의 모든 쿼리메서드를 기존처럼 사용할 수 있다. 예를들어 company id 1번의 사용자가 Customer 테이블 인스턴스를 모두 호출할 때 Customer.objects.all()을 실행시키면 django orm 쿼리 이전에 RLS 필터가 최우선으로 적용되어 company_id가 1번을 가진 모든 고객을 쿼리셋으로 반환해준다.

 

 

  • b. 기준이 되는 Tenant 모델 생성시 사용자 ROLE 함께 생성

 

CREATE ROLE “1”;
GRANT rls_user To “1”;

    👉🏻 tenant가 생성될 때 django post_save시그널을 사용해 DB에 연결해 사용자 역할을 생성해주고 권한 부여 데이터베이스

         사용자에 상속시켜준다.

 

 

 

예제들 같은 경우에 migrations 파일로 생성해 RunSQL을 실행해 진행하는 부분들이 있었는데 실제 프로젝트를 진행중이고 유지 보수를 위해 프로젝트단 urls.py에서장고 시그널을 사용해 RLS 정책 관리를 해주었다.

728x90
반응형