마스킹된 포크
에이전트(또는 사람)에게 프로덕션의 형태만 주고 PII는 주지 마세요 — 마스킹 정책, 내장 함수 라이브러리, 그리고 마스크가 커밋될 때까지 마스킹된 브랜치와 샌드박스가 봉인된 채로 유지되는 방식.
MASK는 민감한 데이터를 위한 Agent-Safe Change Control 트랙입니다: 에이전트에게 프로덕션의 형태는 주고 PII는 전혀 주지 않는 포크를 건넵니다. 두 가지 방식으로 작동합니다 — 마스킹된 브랜치(사람용: 개발, CI, 계약자)와 마스킹된 샌드박스(에이전트용). 둘 다 아래에서 다룹니다.
데이터 마스킹은 브랜치가 생성되는 바로 그 순간에 민감한 컬럼을 익명화합니다. 브랜치 생성 호출에 마스킹 정책을 연결하면, 새 브랜치의 일치하는 컬럼이 브랜치에 접근할 수 있게 되기 전에 되돌릴 수 없게 재작성됩니다 — 해시되거나, 널 처리되거나, 잘리거나, 대체됩니다. 부모 브랜치는 결코 변경되지 않습니다.
프로덕션 형태의 데이터를 그 안의 PII는 넘기지 않으면서 개발, CI, 또는 계약자에게 건네고자 할 때 사용합니다: 실제 테이블 형태, 실제 행 수, 가짜 이메일.
작동 방식
마스킹 정책은 프로젝트 범위의, 이름이 붙은 규칙 집합입니다. 각 규칙은 패턴으로 컬럼을 매칭하고 내장 마스킹 함수를 지정합니다:
| 필드 | 의미 |
|---|---|
schema_pattern | 스키마 이름 패턴(% 또는 * = 임의, _ = 한 문자). 기본값은 *. |
table_pattern | 테이블 이름 패턴. |
column_pattern | 컬럼 이름 패턴. |
masking_fn | 아래 내장 함수 중 하나. |
fn_args | 함수 인자(mask_constant만 하나를 받습니다: value). |
브랜치 생성 시점에 masking_policy_id가 제공되면:
- 브랜치는 평소처럼 부모에서 포크됩니다(카피 온 라이트 — 즉시).
- 브랜치가
masking상태로 진입합니다. 엔드포인트가 프로비저닝되지 않고 프록시가 그에 대한 연결을 거부합니다: 마스킹되기 전 데이터를 읽을 수 있는 창은 없습니다. - 마스킹 워커가 최소 권한 롤로 브랜치의 컴퓨트에 연결하여 스키마를 탐색하고, 사용자의 규칙을 그에 매칭한 다음, 모든 재작성을 단일 트랜잭션으로 실행합니다.
- 브랜치가 **
ready**에 안착하고 엔드포인트가 올라옵니다 — 이제 마스킹된 데이터만 제공합니다. 실패가 발생하면 브랜치는 **failed**에 안착하고 봉인된 채로 남습니다. 삭제한 뒤 재시도하세요.
규칙 입력은 SQL이 아니라 데이터로 취급됩니다: 식별자는 따옴표로 묶이고 인자 값은 실행 시 파라미터로 바인딩되므로, 악의적인 컬럼 이름이나 상수가 재작성을 벗어날 수 없습니다.
내장 함수
| 함수 | 효과 |
|---|---|
mask_email | md5(value)@masked.invalid |
mask_name | Name_ + 8자 해시 접두사 |
mask_null | NULL(컬럼은 nullable이어야 함) |
mask_constant | 사용자가 제공하는 고정 값(fn_args.value) |
mask_ssn_partial | ***-**-1234 — 마지막 4자리 유지 |
mask_credit_card | ****-****-****-1234 — 마지막 4자리 유지 |
mask_ip | 0.0.0.0 |
mask_date_year | 연도는 유지하고 1월 1일로 잘라냄 |
mask_hash | md5(value) |
mask_shuffle | 해시 기반 스크램블(전체 문자 셔플은 예정됨) |
mask_phone | 합성된 +1-555-XXXX 번호 |
mask_uuid | 새로운 무작위 UUID |
카탈로그는 API로도 제공됩니다:
curl -s https://api.kisenon.com/v1/masking-functions \
-H "Authorization: Bearer $KISENON_API_KEY"정책 관리
콘솔에서 Project settings → Data masking을 열어 정책을 생성합니다: 이름을 지정하고, 규칙(패턴 컬럼 + 함수 드롭다운)을 추가하고, 저장합니다. 동일한 기능이 API에도 존재합니다:
curl -s -X POST \
https://api.kisenon.com/v1/projects/$PROJECT_ID/masking-policies \
-H "Authorization: Bearer $KISENON_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "dev-safe",
"rules": [
{"table_pattern": "users", "column_pattern": "email", "masking_fn": "mask_email"},
{"table_pattern": "users", "column_pattern": "phone", "masking_fn": "mask_null"},
{"table_pattern": "%", "column_pattern": "%ssn%", "masking_fn": "mask_ssn_partial"}
]
}'그런 다음 일반 브랜치 생성 호출에 정책을 추가하여 마스킹된 브랜치를 생성합니다:
curl -s -X POST \
https://api.kisenon.com/v1/projects/$PROJECT_ID/branches \
-H "Authorization: Bearer $KISENON_API_KEY" \
-H "Content-Type: application/json" \
-d '{"name": "masked-dev", "masking_policy_id": "'$POLICY_ID'"}'재작성이 실행되는 동안 브랜치는 state: "masking"으로 보고합니다.
콘솔에서(실시간, 프로젝트 이벤트 스트림을 통해) 또는 GET /v1/branches/{id}를 폴링하여 ready로 전환되는 것을 지켜보세요.
마스킹된 샌드박스(에이전트용)
동일한 정책이 에이전트의 샌드박스 포크를 마스킹하므로, 에이전트는 프로덕션의 형태에 대해 작업하되 그 PII는 결코 보지 않습니다. 생성 시 정책을 연결하세요:
keon sandbox create --project <id> --masking-policy <policy-id>마스크는 샌드박스가 활성화되기 전에 실행됩니다. 샌드박스는 masked: true와
그 masking_policy_id를 보고하며, 아직 creating 상태인 동안 masking 진행
객체(state, phase, rules_total, rules_completed,
rows_affected_so_far, error)가 재작성이 진행되는 것을 보여줍니다.
프로젝트별 하한선을 설정하여 모든 샌드박스가 기본적으로 마스킹되게 하세요:
keon projects update <id> --sandbox-masking-policy <policy-id> # or: none요청별 --masking-policy는 프로젝트 기본값을 재정의하지만, 의무(mandate)에서
결코 빠져나올 수 없습니다 — 에이전트가 마스킹에서 말로 빠져나가는 것에 대한
방어입니다. 의무 설정은 소유자/관리자만 가능하며, 에이전트 기능 키는 거부됩니다
(403).
구성상 페일클로즈드. 마스킹된 포크의 엔드포인트는 마스킹 상태로 태어나며
— 재작성이 커밋될 때까지 프록시가 연결을 거부합니다(SQLSTATE 57P05).
마스킹 단계가 실패하거나 연결되지 않으면, 생성은 닫힘으로 실패합니다: 마스킹되지
않은 포크에는 결코 URL이 발급되지 않습니다. 마스킹 워커는 최소 권한 롤
kisenon_masker(결코 슈퍼유저가 아님)로 연결하고, 클러스터 RBAC를 보유하지
않으며, 실행별 비밀번호는 결코 유지되거나 로깅되지 않습니다.
알아둘 만한 두 가지 주의사항:
- 마스킹된 값에 대한 DML은 재생 시 부모에서 매칭되지 않습니다.
UPDATE … WHERE email = 'a1b2@masked.invalid'같은 문장은main에 존재하지 않는 마스킹된 값을 대상으로 합니다. 스키마 레인 작업은 값에 독립적이며 깔끔하게 프로모트됩니다. 값에 의존적인 DML은 마스킹된 포크가 할 일이 아닙니다. - 외래 키는 계속 집행됩니다. 마스킹은 참조 무결성 트리거가 아니라 사용자
트리거(
ALTER TABLE … DISABLE TRIGGER USER)를 비활성화합니다. 따라서 외래 키 관계 안의 컬럼을 마스킹하면 RI 오류에 부딪혀 참조를 조용히 깨는 대신 포크를 닫힘으로 실패시킬 수 있습니다. - v1은
main데이터베이스만 마스킹합니다.
알아두면 좋은 것
- 마스킹은 생성 시점의 일회성입니다. 정책을 편집해도 이미 그것으로 생성된 브랜치는 결코 건드리지 않습니다 — 브랜치는 태어날 때의 데이터 형태를 유지합니다. 새 규칙을 적용하려면 브랜치를 다시 생성하세요.
- 사용 중인 정책은 삭제할 수 없습니다. 라이브 브랜치가 그것으로 생성된
정책을 삭제하면
409 policy_in_use를 반환합니다. 해당 브랜치를 먼저 삭제하세요. - 매칭되지 않은 규칙은 치명적이지 않고 건너뜁니다. 스키마가 변경되어 패턴이 아무것도 매칭하지 않더라도 브랜치는 여전히 완료됩니다 — 마스킹을 예상한 컬럼이 여전히 실제 데이터 형태를 갖고 있으면 규칙 패턴을 확인하세요.
- 타입 불일치는 브랜치를 실패시킵니다. 함수가 재작성할 수 없는 컬럼을
매칭하는 규칙(예:
integer에 대한mask_email)은 전체 마스크를 중단시킵니다 — 브랜치는failed에 안착하며 절반만 마스킹된 채로 노출되는 일은 결코 없습니다.