CREATE MASKING POLICY
CREATE MASKING POLICY
현재/지정한 스키마에 새 마스킹 폴리시(masking policy)를 만들거나 기존 마스킹 폴리시를 교체하는 명령이에요. 마스킹 폴리시는 테이블/뷰의 특정 컬럼에 적용해서 데이터를 가리는 방식이에요.
출처: 문서
본문
마스킹 폴리시를 만든 뒤에는 ALTER TABLE … ALTER COLUMN 명령으로 테이블의 컬럼에, 또는 ALTER VIEW 명령으로 뷰에 이 폴리시를 적용해요.
이 명령은 다음 변형(variant)을 지원해요:
- CREATE MASKING POLICY: 새 마스킹 폴리시를 만듭니다.
- CREATE OR ALTER MASKING POLICY: 없으면 만들고, 있으면 기존 폴리시를 수정합니다.
구문 (Syntax)
CREATE [ OR REPLACE ] MASKING POLICY [ IF NOT EXISTS ] <name> AS
( <arg_name_to_mask> <arg_type_to_mask> [ , <arg_1> <arg_type_1> ... ] )
RETURNS <arg_type_to_mask> -> <body>
[ COMMENT = '<string_literal>' ]
[ EXEMPT_OTHER_POLICIES = { TRUE | FALSE } ]
CREATE OR ALTER MASKING POLICY 구문
CREATE OR ALTER MASKING POLICY <name> AS
( <arg_name_to_mask> <arg_type_to_mask> [ , <arg_1> <arg_type_1> ... ] )
RETURNS <arg_type_to_mask> -> <body>
[ COMMENT = '<string_literal>' ]
[ EXEMPT_OTHER_POLICIES = { TRUE | FALSE } ]
필수 매개변수
<name>: 마스킹 폴리시의 식별자예요. 스키마 내에서 고유해야 해요. 기본 문자로 시작해야 하며, 전체 식별자를 큰따옴표로 감싸지 않는 한 공백이나 특수 문자를 포함할 수 없어요 (예:"My object"). 큰따옴표로 감싼 식별자는 대/소문자를 구분해요.AS ( arg_name_to_mask arg_type_to_mask [ , arg_1 arg_type_1 ... ] ): 마스킹 폴리시의 시그니처(signature)로, 쿼리 실행 시 평가할 입력 컬럼과 데이터 타입을 지정해요.arg_name_to_mask arg_type_to_mask: 첫 번째 컬럼과 그 데이터 타입으로, 이후 정책 조건에서 마스킹/토큰화할 컬럼 데이터 타입 값을 나타내요. 조건부 마스킹 폴리시에서 가상 컬럼(virtual column)을 첫 번째 컬럼 인자로 지정할 수는 없어요.[ , arg_1 arg_type_1 ... ]: 쿼리 결과의 각 행에서 첫 번째 컬럼 데이터를 마스킹/토큰화할지 결정하는 조건부 컬럼과 그 데이터 타입이에요. 지정하지 않으면 Snowflake는 일반 마스킹 폴리시로 평가해요.
RETURNS arg_type_to_mask: 반환 데이터 타입은 입력 컬럼으로 지정된 첫 번째 컬럼의 입력 데이터 타입과 일치해야 해요.<body>:arg_name_to_mask로 지정된 컬럼의 데이터를 변환하는 SQL 표현식이에요. 조건부 표현식 함수(Conditional expression functions), 내장 함수, 또는 UDF를 포함할 수 있어요.
선택 매개변수
-
COMMENT = 'string_literal': 마스킹 폴리시에 주석을 추가하거나 기존 주석을 덮어써요. -
EXEMPT_OTHER_POLICIES = { TRUE | FALSE }: 사용 목적에 따라 다음 중 하나를 지정해요.- 행 접근 폴리시(row access policy)나 조건부 마스킹 폴리시가 이 마스킹 폴리시로 이미 보호된 컬럼을 참조할 수 있는지 여부.
- 외부 테이블에서 가상 컬럼에 할당된 마스킹 폴리시가 VALUE 컬럼에서 상속한 마스킹 폴리시를 덮어쓸 수 있는지 여부.
TRUE: 다른 폴리시가 마스킹된 컬럼을 참조하거나, 가상 컬럼에 설정된 마스킹 폴리시가 VALUE 컬럼에서 상속한 것을 덮어쓰도록 허용.FALSE: 허용하지 않음.
이 속성의 값은 폴리시를 테이블/뷰에 설정한 뒤에는 변경할 수 없어요. 값을 갱신하려면
CREATE OR REPLACE MASKING POLICY문을 실행해야 해요.
접근 제어 요구 사항
이 작업을 실행하려면 역할에 최소한 다음 권한이 필요해요:
| 권한 | 객체 | 참고 |
|---|---|---|
| CREATE MASKING POLICY | 스키마 | |
| OWNERSHIP | 마스킹 폴리시 | 기존 마스킹 폴리시에 대해 CREATE OR ALTER MASKING POLICY 문 실행에 필요 |
EXEMPT_OTHER_POLICIES 속성을 지정할 때, 마스킹 폴리시를 소유한 역할(OWNERSHIP 권한이 있는 역할)은 행 접근 폴리시 또는 조건부 마스킹 폴리시를 소유한 역할의 역할 계층(role hierarchy)에 있어야 해요. 예:
masking_admin » rap_admin » SYSADMIN
masking_admin » cond_masking_admin » SYSADMIN
사용법 참고 사항
- 기존 마스킹 폴리시를 교체하려면 현재 정의를 확인하기 위해
GET_DDL함수를 호출하거나DESCRIBE MASKING POLICY명령을 실행하세요. - 마스킹 폴리시 본문에 서브쿼리를 포함할 때는
CASE함수의WHEN분기에 EXISTS를 사용하세요. - 폴리시
body에 매핑 테이블 조회가 포함되면 보호된 테이블과 같은 데이터베이스에 중앙 집중식 매핑 테이블을 만드세요. 특히IS_DATABASE_ROLE_IN_SESSION함수를 호출할 때 중요해요. - 동일한 테이블/뷰 컬럼은 마스킹 폴리시 시그니처와 행 접근 폴리시 시그니처 중 하나에만 지정할 수 있어요.
- 데이터 공유 제공자(provider)는 리더 계정(reader account)에 마스킹 폴리시를 만들 수 없어요.
- 마스킹 폴리시 본문에
CURRENT_DATABASE또는CURRENT_SCHEMA함수를 지정하면, 그 함수는 세션에서 사용 중인 데이터베이스/스키마가 아니라 보호된 테이블을 포함하는 데이터베이스/스키마를 반환해요. OR REPLACE와IF NOT EXISTS절은 상호 배타적이에요. 같은 문에서 둘 다 사용할 수 없어요.CREATE OR REPLACE <object>문은 원자적이에요. 즉, 객체가 교체될 때 이전 객체가 삭제되고 새 객체가 단일 트랜잭션으로 생성돼요.
CREATE OR ALTER MASKING POLICY
ALTER MASKING POLICY명령의 모든 제한이 적용돼요.- 기존 마스킹 폴리시의 시그니처(인자 이름과 데이터 타입)는 변경할 수 없어요. 시그니처를 바꿔야 한다면 기존 폴리시를 드롭하고 새로 만드세요.
- 마스킹 폴리시 이름 변경은 지원되지 않아요.
- 태그 설정/해제는 지원되지 않아요.
예제: 일반 마스킹 폴리시 (Normal masking policy)
마스킹 폴리시 본문을 작성하는 데 조건부 표현식 함수, 컨텍스트 함수, UDF를 사용할 수 있어요.
전체 마스크 (Full mask): analyst 사용자 지정 역할은 평문 값을 볼 수 있고, 그 외 사용자는 전체 마스크를 봐요.
CREATE OR REPLACE MASKING POLICY email_mask AS (val string) returns string ->
CASE
WHEN current_role() IN ('ANALYST') THEN VAL
ELSE '*********'
END;
프로덕션 계정만 마스킹 해제: 프로덕션 계정은 마스킹되지 않은 값을, 그 외 계정은 마스킹된 값을 보게 해요.
case
when current_account() in ('<prod_account_identifier>') then val
else '*********'
end;
미인가 사용자에게 NULL 반환:
case
when current_role() IN ('ANALYST') then val
else NULL
end;
미인가 사용자에게 고정 마스크 값 반환:
CASE
WHEN current_role() IN ('ANALYST') THEN val
ELSE '********'
END;
SHA2 / SHA2_HEX 사용한 해시 값 반환: 마스킹 폴리시에 해시 함수를 사용하면 충돌이 발생할 수 있으므로 주의하세요.
CASE
WHEN current_role() IN ('ANALYST') THEN val
ELSE sha2(val) -- return hash of the column value
END;
부분 마스크/전체 마스크 적용:
CASE
WHEN current_role() IN ('ANALYST') THEN val
WHEN current_role() IN ('SUPPORT') THEN regexp_replace(val,'.+\@','*****@') -- leave email domain unmasked
ELSE '********'
END;
타임스탬프 사용:
case
WHEN current_role() in ('SUPPORT') THEN val
else date_from_parts(0001, 01, 01)::timestamp_ntz -- returns 0001-01-01 00:00:00.000
end;
중요: 현재 Snowflake는 마스킹 폴리시에서 서로 다른 입력/출력 데이터 타입을 지원하지 않아요 (예: 타임스탬프를 타게팅하고 문자열
***MASKED***반환). 입력과 출력 데이터 타입은 일치해야 해요. 우회 방법으로 실제 타임스탬프 값을 위조된 타임스탬프 값으로 캐스팅할 수 있어요.
UDF 사용:
CASE
WHEN current_role() IN ('ANALYST') THEN val
ELSE mask_udf(val) -- custom masking function
END;
Variant 데이터에:
CASE
WHEN current_role() IN ('ANALYST') THEN val
ELSE OBJECT_INSERT(val, 'USER_IPADDRESS', '****', true)
END;
사용자 지정 자격 테이블(custom entitlement table) 사용: WHEN 절에 EXISTS를 사용하는 것에 주의하세요. 마스킹 폴리시 본문에 서브쿼리를 포함할 때는 항상 EXISTS를 사용해요.
CASE
WHEN EXISTS
(SELECT role FROM <db>.<schema>.entitlement WHERE mask_method='unmask' AND role = current_role()) THEN val
ELSE '********'
END;
에이전트 활성 감지: IS_AGENT_ACTIVATED를 마스킹 폴리시에서 사용해 현재 실행 컨텍스트에 AI 에이전트가 활성화되어 있는지 식별하고 데이터 거버넌스 정책을 적용할 수 있어요.
CASE
WHEN SYS_CONTEXT('SNOWFLAKE$CURRENT', 'IS_AGENT_ACTIVATED')::BOOLEAN = TRUE THEN '********'
WHEN CURRENT_ROLE() IN ('ANALYST') THEN val
ELSE '********'
END;
이전에 암호화된 데이터에 DECRYPT 사용 (ENCRYPT 또는 ENCRYPT_RAW, passphrase 사용):
case
when current_role() in ('ANALYST') then DECRYPT(val, $passphrase)
else val -- shows encrypted value
end;
JSON (VARIANT)에 JavaScript UDF 사용: 이 예에서 JavaScript UDF는 JSON 문자열의 위치 데이터를 마스킹해요. UDF와 마스킹 폴리시에서 데이터 타입을 VARIANT로 설정하는 것이 중요해요. 테이블 컬럼, UDF, 마스킹 폴리시 시그니처의 데이터 타입이 일치하지 않으면 Snowflake가 SQL을 해석할 수 없어 오류를 반환해요.
-- Flatten the JSON data
create or replace table <table_name> (v variant) as
select value::variant
from @<table_name>,
table(flatten(input => parse_json($1):stationLocation));
-- JavaScript UDF to mask latitude, longitude, and location data
CREATE OR REPLACE FUNCTION full_location_masking(v variant)
RETURNS variant
LANGUAGE JAVASCRIPT
AS
$$
if ("latitude" in V) {
V["latitude"] = "**latitudeMask**";
}
if ("longitude" in V) {
V["longitude"] = "**longitudeMask**";
}
if ("location" in V) {
V["location"] = "**locationMask**";
}
return V;
$$;
-- Grant UDF usage to ACCOUNTADMIN
grant ownership on function FULL_LOCATION_MASKING(variant) to role accountadmin;
-- Create a masking policy using JavaScript UDF
create or replace masking policy json_location_mask as (val variant) returns variant ->
CASE
WHEN current_role() IN ('ANALYST') THEN val
else full_location_masking(val)
-- else object_insert(val, 'latitude', '**locationMask**', true) -- limited to one value at a time
END;
GEOGRAPHY 데이터 타입 사용: 이 예에서 마스킹 폴리시는 TO_GEOGRAPHY 함수를 사용해, CURRENT_ROLE이 ANALYST가 아닌 사용자에 대해 컬럼의 모든 GEOGRAPHY 데이터를 캘리포니아 산 마테오(San Mateo)에 있는 Snowflake의 경위도 고정 지점으로 변환해요.
create masking policy mask_geo_point as (val geography) returns geography ->
case
when current_role() IN ('ANALYST') then val
else to_geography('POINT(-122.35 37.55)')
end;
GEOGRAPHY 데이터 타입 컬럼에 마스킹 폴리시를 설정하고 세션의 GEOGRAPHY_OUTPUT_FORMAT 값을 GeoJSON으로 설정해요:
alter table mydb.myschema.geography modify column b set masking policy mask_geo_point;
alter session set geography_output_format = 'GeoJSON';
use role public;
select * from mydb.myschema.geography;
Snowflake는 다음을 반환해요:
---+--------------------+
A | B |
---+--------------------+
1 | { |
| "coordinates": [ |
| -122.35, |
| 37.55 |
| ], |
| "type": "Point" |
| } |
2 | { |
| "coordinates": [ |
| -122.35, |
| 37.55 |
| ], |
| "type": "Point" |
| } |
---+--------------------+
컬럼 B의 쿼리 결과 값은 세션의 GEOGRAPHY_OUTPUT_FORMAT 매개변수 값에 따라 달라져요. 예를 들어 값을 WKT로 설정하면:
alter session set geography_output_format = 'WKT';
select * from mydb.myschema.geography;
---+----------------------+
A | B |
---+----------------------+
1 | POINT(-122.35 37.55) |
2 | POINT(-122.35 37.55) |
---+----------------------+
예제: 조건부 마스킹 폴리시 (Conditional masking policy)
다음 예는 CURRENT_ROLE이 admin 사용자 지정 역할인 사용자, 또는 visibility 컬럼 값이 Public인 사용자에게 마스킹되지 않은 데이터를 반환해요. 그 외 조건은 모두 고정 마스크 값을 반환해요.
-- Conditional Masking
create masking policy email_visibility as
(email varchar, visibility string) returns varchar ->
case
when current_role() = 'ADMIN' then email
when visibility = 'Public' then email
else '***MASKED***'
end;
다음 예는 CURRENT_ROLE이 admin 사용자 지정 역할이고 다른 컬럼 값이 Public인 사용자에게 역토큰화(detokenized) 데이터를 반환해요. 그 외 조건은 토큰화된 값을 반환해요.
-- Conditional Tokenization
create masking policy de_email_visibility as
(email varchar, visibility string) returns varchar ->
case
when current_role() = 'ADMIN' and visibility = 'Public' then de_email(email)
else email -- sees tokenized data
end;
예제: 행 접근 폴리시나 조건부 마스킹 폴리시에서 마스킹된 컬럼 허용
이메일 주소 보기, 이메일 주소 도메인만 보기, 또는 고정 마스크 값 중 하나를 허용하는 마스킹 폴리시를 교체해요:
create or replace masking policy governance.policies.email_mask
as (val string) returns string ->
case
when current_role() in ('ANALYST') then val
when current_role() in ('SUPPORT') then regexp_replace(val,'.+\@','*****@')
else '********'
end
comment = 'specify in row access policy'
exempt_other_policies = true
;
이제 이 폴리시를 컬럼에 설정할 수 있고, 필요에 따라 행 접근 폴리시나 조건부 마스킹 폴리시가 이 마스킹 폴리시로 보호된 컬럼을 참조할 수 있어요.
CREATE OR ALTER MASKING POLICY
새 마스킹 폴리시를 만들거나 기존 폴리시의 본문을 교체해요:
CREATE OR ALTER MASKING POLICY email_mask AS (val string) RETURNS string ->
CASE
WHEN current_role() IN ('ANALYST') THEN val
ELSE '*********'
END
COMMENT = 'Mask email addresses from non-analyst roles';
더 알아보기 (Learn more)
- ALTER MASKING POLICY
- DROP MASKING POLICY
- SHOW MASKING POLICIES
- DESCRIBE MASKING POLICY
- CREATE ROW ACCESS POLICY
- 중앙/하이브리드/분산 접근 방식 선택, 고급 컬럼 수준 보안 주제