DBMS 1

DA, SQL, DB보안 등 실무자를 위한 위한 DB기술 바이블!

Virtual Private Database

DBMS 1
Oracle 가이드
20가지 주요기능
Virtual Private Database
작성자
dataonair
작성일
2021-02-17 17:14
조회
1256

Virtual Private Database

열네번째. Virtual Private Database

VPD에 새롭게 추가된 4가지 policy type, 컬럼 단위 policy 적용, column masking 등의 신기능을 활용하여 한층 강력하고 유연한 보안 환경을 구현할 수 있습니다.

Fine Grained Access Control이라는 용어로 불리기도 하는 Virtual Private Database(VPD)는 로우(row) 레벨의 강력한 보안 기능을 제공합니다. VPD는 Oracle8i에서 처음 소개된 이후, 교육용 소프트웨어에서 금융 애플리케이션에 이르기까지 다양한 영역에서 활용되고 있습니다.

VPD는 접수된 데이타 쿼리를 사용자의 권한에 맞도록 테이블의 일부분만을 포함하는 부분적인 뷰에 대한 쿼리로 자동 변경합니다. VPD는 모든 쿼리에 대해 사용자에게 접근 허용된 로우(row)만을 필터링하도록 쿼리 조건을 추가합니다. 예를 들어 사용자가 SCOTT가 account manager로 할당된 account만을 보아야 하는 경우, VPD는 쿼리를 아래와 같이 재작성합니다:

select * from accounts;
to:select * from accounts
where am_name = 'SCOTT';

DBA가 ACCOUNTS 테이블에 보안 정책을 설정하면, 설정된 정책에는 policy function이라 불리는 함수가 적용됩니다. 이 함수는 “where am_name = 'SCOTT'“와 같은 문자열을 반환하고, 생성된 문자열은 쿼리 조건에 추가되는 predicate으로 활용됩니다. VPD 기능에 대해 친숙하지 않은 경우라면, 오라클 매거진의 기사 "Keeping Information Private with VPD를 참고하실 것을 권장 드립니다."

Policy Types

이처럼 반복적인 파싱을 통해predicate을 생성하는 것은 성능적으로 부담이 될 수 있습니다. 경우에 따라서는 성능 개선을 위한 대안을 고려할 수 있습니다. 대부분의 경우 predicate은 사용자가 누구이고, 그 사용자의 권한이 어디까지이고, 사용자의 상급 관리자가 누구인지 등의 여부에 따라 다이내믹하게 결정됩니다. Policy function에 의해 생성 및 반환되는 문자열은 매우 다이내믹한 성격을 가지며, 따라서 오라클은 정확성을 보장하기 위해서 매번 policy function을 반복적으로 수행합니다. 이는 성능 및 자원사용률 면에서 낭비를 초래할 수 있습니다. 이처럼 predicate이 실행할 때마다 달라지는 경우의 policy를 “dynamic” policy라 부릅니다. Dynamic policy는 Oracle9i 데이타베이스와 그 이전 릴리즈에서 이미 지원되어 왔습니다.

이와 별도로, Oracle Database 10g는 성능 향상을 목적으로 context_sensitive, shared_context_sensitive, shared_static, static 등의 다양한 policy 유형을 추가로 제공합니다: 이제 각 policy 유형의 의미와 활용 방안을 알아 보기로 합시다.

Dynamic Policy.하위 버전과의 호환성을 유지하기 위한 목적에서, 10g의 디폴트 policy 유형은 “dynamic”으로 설정되어 있습니다. Dynamic policy는 테이블이 액세스 될 때마다 각각의 로우(row) 및 모든 사용자에 대해서 policy function을 실행합니다. Dynamic policy가 predicate를 활용하는 방법에 대해 좀 더 자세히 알아보겠습니다:

where am_name = 'SCOTT'

where 키워드를 제외한다면, predicate은 크게 두 부분으로 나뉘어집니다. 등호 기호의 앞부분(am_name)과 뒷부분(‘SCOTT’)로 나뉘어집니다. 대부분의 경우, 뒷부분에는 사용자 데이타로부터 제공되는 변수와 같습니다. (예를 들어 사용자가 SCOTT라면 값은 ‘SCOTT’가 됩니다.) 등호 기호의 앞부분은 static한 문자열로 이루어지며, 따라서 각 로우(row) 별로 policy function을 반복적으로 수행할 필요가 없습니다. 이처럼 등호 기호의 앞부분과 뒷부분의 static / dynamic 여부를 미리 알고 있다면 부분적으로 성능을 개선하는 것이 가능해집니다. 10g에서는 이를 위해 “context_sensitive” 타입을 지원하며, dbms_rls.add_policy 호출 과정에서 이 타입을 매개변수로 사용할 수 있습니다:

policy_type => dbms_rls.context_sensitive

이번에는 여러 개의 컬럼을 갖는 ACCOUNTS 테이블에 대한 예를 설명하겠습니다. ACCOUNTS 테이블에 포함된 BALANCE 컬럼은 해당 계좌의 잔액을 표시합니다. 특정 사용자가 application context에 의해 정의된 액수 이하의 잔액을 갖는 계좌들을 조회할 수 있도록 허용되었다고 가정해 봅시다. 하드 코딩을 통해 계좌 잔액의 액수를 입력하는 대신 Policy function을 이용해 다음과 같이 application context를 활용할 수 있습니다:

create or replace vpd_pol_func
(
p_schema in varchar2,
p_table in varchar2
)
return varchar2
is
begin
return 'balance < sys_context(''vpdctx'', ''maxbal'')';
end;

Application context VPDCTX 의MAXBAL 속성을 설정해 두고 함수가 런타임에 값을 가져오도록 할 수 있습니다.

위의 코드 예제를 주의 깊게 살펴 보시기 바랍니다. Predicate은 크게 두 부분(‘<’기호 이전과 이후)으로 나뉘어집니다. 앞부분의 “balance”는 static한 문자열입니다. 뒷부분은 application context가 변경되기 전까지는 원래 값을 그대로 유지하므로 어느 정도 static하다고 볼 수 있습니다. 따라서 application context속성이 변경되지 않는 한, 전체 predicate이 static하다고 판단할 수 있으며, 따라서 함수를 재실행할 필요가 없습니다. Oracle Database 10g는 policy type이 context sensitive로 지정된 경우 이러한 최적화 알고리즘을 사용합니다. 세션에서 context 변경이 발생하지 않는 경우 함수는 재실행되지 않으며, 상당한 수준의 성능 향상 효과를 볼 수 있습니다.

Static Policy.경우에 따라서는 보다 static한 형태의 predicate이 사용될 수도 있습니다. 위의 예제에서는 maximum balance를 변수로서 정의했습니다. 이러한 접근방식은 Oracle userid가 많은 웹 사용자에 의해 공유되고 사용자의 권한에 따라 이 변수(application context)가 변경되어야 하는 웹 애플리케이션 환경에서 유용합니다. 예를 들어 TAO와 KARTHIK이라는 두 사용자가 APPUSER라는 동일한 데이타베이스 사용자로 접근하는 경우에도, 각 세션 별로 설정된 application context에 의해 서로 다른 권한을 할당 받게 됩니다. 다시 말해 MAXBAL의 값은 Oracle userid가 아닌 TAO와 KARTHIK의 개별 세션 별로 바인드 됩니다.

Static policy의 경우 predicate은 아래와 같이 보다 예측 가능한 형태로 제시됩니다.

LORA와 MICHELLE는 각각 Acme Bearings와 Goldtone Bearings의 어카운트 관리자입니다. 두 사람은 데이타베이스에 연결할 때 개인 ID를 사용하며 각자에게 허용된 로우(row)만을 조회할 수 있습니다. Lora의 경우 predicate은 “where CUST_NAME = 'ACME'”, Michelle의 경우 predicate은 “where CUST_NAME = 'GOLDTONE'”입니다. 이 경우 predicate은 사용자ID와 연결되며, 따라서 그들에 의해 생성된 어떤 세션이든 application context가 제공하는 같은 값을 predicate으로 사용하게 됩니다.

10g는 이러한 경우 SGA 캐시에 predicate을 저장하고 해당 세션에 대해서는 policy function을 재실행하지 않고 계속적으로 재활용합니다. Policy function은 아래와 같습니다:

create or replace vpd_pol_func
(
p_schema in varchar2,
p_table in varchar2
)
return varchar2
is
begin
return 'cust_name = sys_context(''vpdctx'', ''cust_name'')';
end;

Policy는 아래와 같이 정의됩니다:

policy_type => dbms_rls.static

이와 같은 접근법을 사용하면 policy function이 단 한 차례만 수행됨을 보장할 수 있습니다. 세션에서 application context가 변경되는 경우에도 함수는 재실행되지 않으므로 성능이 대폭적으로 향상됩니다.

Static policy는 여러 구독자(subscriber)가 사용하는 애플리케이션 환경에서 유용합니다. 이 경우 여러 사용자 또는 구독자가 단일 데이타베이스의 데이타를 공유하게 됩니다. 구독자가 로그인하면, 로그인 과정에서 “after-login trigger”가 동작하여 application context를 설정하고 policy function을 실행함으로써 predicate을 얻게 됩니다.

하지만 static policy는 양날의 칼과도 같습니다. 위의 예에서는, application context의 VPDCTX.CUST_NAME속성의 값이 세션 내에서 변경되지 않는다고 가정했습니다. 만일 그 가정이 잘못되었다면 어떻게 될까요 속성 값이 변해도 policy function이 재실행되지 않으므로 predicate에 새로운 값이 반영되지 않을 것이며, 따라서 완전히 잘못된 결과가 나올 것입니다. 그러므로 static policy를 사용할 때는 주의할 필요가 있으며, 속성 값이 변하지 않음을 분명히 확신할 수 있을 때에만 static policy를 사용해야 합니다. 이러한 가정이 불가능하다면, context sensitive policy를 사용하는 것이 무난합니다.

Shared Policy Types.코드를 재활용하고 파싱된 코드의 활용도를 최대로 높이려면, 여러 테이블에 공통적으로 적용되는 policy function을 구현할 필요가 있습니다. 예를 들어, 위의 예에서 계좌 별로 두 개의 테이블(SAVINGS와 CHECKING)이 존재하지만 규칙은 똑같이 적용되는 경우를 가정해 봅시다. 사용자는 자신에게 허용된 것 이외의 계좌 잔액은 조회할 수 없습니다. 이 경우 CHECKING 테이블과 SAVINGS 테이블에 사용되는 policy function은 동일합니다. 생성되는 policy의 유형은 context_sensitive로 가정합니다.

다음과 같은 이벤트가 순서대로 발생한다고 가정해 봅시다:

  1. 세션 연결
  2. application context 설정
  3. select * from savings;
  4. select * from checking;

Application context가 3번, 4번 과정에서 변경되지 않았음에도 조회되는 테이블이 다르기 때문에 policy function은 재실행될 것입니다. 하지만 policy function이 같기 때문에 재실행할 필요는 없었습니다. 10g는 동일한 policy를 여러 오브젝트가 공유하는 기능을 제공합니다. 위의 예의 경우 policy type은 다음과 같이 정의됩니다:

New in 10g is the ability to share a policy across objects. In the above example,
you would define the policy type of these policies as:
policy_type => dbms_rls.shared_context_sensitive

이처럼 policy를 “shared”로 지정함으로써 함수의 불필요한 실행을 방지하고 성능을 향상시킬 수 있습니다.

Selective Columns

이번에는 특정 컬럼이 select 된 경우에만 VPD policy가 적용되는 경우를 생각해 봅시다. ACCOUNTS 테이블이 아래와 같은 레코드를 갖는다고 가정합니다:

ACCTNO ACCT_NAME    BALANCE
------ ------------ -------
1 BILL CAMP 1000
2 TOM CONNOPHY 2000
3 ISRAEL D 1500

Michelle은 balance가 1,600이 넘는 account를 조회할 수 없습니다. Michelle이 다음과 같은 쿼리를 실행한 경우:

select * from accounts;아래와 같은 결과를 확인하게 될 것입니다:
ACCTNO ACCT_NAME BALANCE
------ ------------ -------
1 BILL CAMP 1000
3 ISRAEL D 1500

1,600이 넘는 balance를 갖는 acctno 2는 실행 결과에 포함되지 않았습니다. Michelle의 관점에서 볼 때 이 테이블은 3개가 아닌 2개의 로우를 갖습니다. 따라서 Michelle이 아래와 같은 쿼리를 실행하는 경우에도 그 결과는 3이 아닌 2가 반환됩니다:

select count(*) from accounts;

하지만 security policy에 예외 조건을 적용할 필요도 있습니다.
지금 Michelle은 모든 account balance를 조회인하려 하는 것입니다. Michelle이 조회할 수 없는 레코드의 count를 포함시키는 것을 허용하는 것을 고려할 수도 있을 것입니다. 10g는 이러한 경우를 위해 dbms_rls.add_policy 호출 과정에서 사용할 수 있는 새로운 매개변수를 추가하였습니다:

sec_relevant_cols => 'BALANCE'

이 매개변수가 사용된 경우, 사용자가 BALANCE 컬럼을 명시적으로 또는 암시적으로(예: select *) 조회하는 경우에 한해 VPD policy는 해당 로우를 조회 대상에서 제외시킵니다. 하지만 그 밖의 경우(예를 들어 사용자가 전체 로우의 count를 조회하는 경우)에는 테이블의 모든 로우에 대한 select가 허용됩니다. 이 경우 아래와 같이 쿼리를 수행하면 그 결과로 2가 아닌 3이 반환됩니다:

select count(*) from accounts;

하지만 아래의 쿼리는 두 개의 레코드만을 반환합니다.

select * from accounts;
Column Masking

이번에는 다른 조건이 붙는 경우를 생각해 봅시다. 일정한 임계값 이상의 balance를 갖는 레코드에 대한 조회를 완전히 제한하는 대신, 임계값 이상의 balance 컬럼에 대해 마스킹(masking)을 수행하는 조건으로 모든 레코드를 반환하는 방법을 선택할 수도 있습니다.

Michelle은 1,600 이상의 balance를 갖는 account를 조회할 수 없습니다. Michelle이 아래와 같은 쿼리를 실행하면:

select * from accounts;

acctno 1과 acctno3의 두 가지 레코드만을 확인하게 됩니다. 그 대신 아래와 같은 결과를 보기 원할 수 있습니다:

ACCTNO ACCT_NAME    BALANCE
------ ------------ -------
1 BILL CAMP 1000
2 TOM CONNOPHY
3 ISRAEL D 1500

위의 경우 모든 레코드가 표시되지만, 2000의 balance를 가진 acctno2 레코드의 BALANCE 컬럼은 null로 표시됩니다. 이러한 방법을 “column masking”이라 부르며, dbms_rls.add_policy 호출 과정에서 아래 매개변수를 사용하여 활성화할 수 있습니다:

sec_relevant_cols_opt => dbms_rls.all_rows

이 방법은 특정 컬럼에 대한 보안 유지가 필요한 경우 매우 유용하게 활용되며, 구현과정에서 별도의 코딩이 불필요합니다. 또 이 방법을 데이타 암호화의 대안으로 활용할 수/TD>

결론

Oracle Database 10g의 VPD 기능은 매우 강력한 형태로 발전되었으며, policy를 기준으로 선택적인 컬럼을 마스킹하거나, 특정 컬럼이 액세스되는 경우에만 policy를 적용하는 등의 다양한 요구사항을 지원합니다. 또 애플리케이션의 성격에 따라 다른 policy를 적용함으로써 성능을 향상시킬 수도 있습니다.

VPD와 dbms_rls 패키지에 대한 자세한 설명은 PL/SQL Packages and Types Reference의 Chapter 79, 또는 Oracle Database Security Guide를 참고하시기 바랍니다. 필자가 Don Burleson과 공동집필한 Oracle Privacy Security Auditing (Rampant TechPress)도 참고가 될 것입니다.