
안녕하세요. 주형권입니다.
AI를 적극적으로 업무에 도입해서 하다 보니 생각보다(?) 빠르게 업무를 하는 거 같습니다. 그래서 결과물이 제가 생각한 것보다 빠르게 도출되면서 블로그 글을 쓸 거리도 많아지고 있는 거 같습니다. 이 블로그에서 나오는 내용은 2개의 AI의 도움을 받았습니다. 우선 첫 번째로 Databricks에서 제공되는 Genie Code입니다. Agent AI로 사용하고 있습니다. 두 번째로는 Gemini입니다. 챗봇 형태로 생성형 AI로 사용하고 있습니다.
두 개의 AI 모두 회사에서 사용하도록 허가된 제품이며, 공식적으로 현재 회사에서 제공하고 있습니다. 그래서 두 개의 AI만을 이용해서 현재 블로그에서 나오는 내용의 모든 작업을 진행하였습니다. 물론 기획 및 디테일한 부분을 제가 모두 작업하여 앞서 작성하였던 [DRE] Databricks 감사 로그 기반 데이터 거버넌스 자동화 시스템 구현 의 내용과 같이 혼자서 기획, 설계, 개발을 하였습니다.
[DRE] Databricks 감사 로그 기반 데이터 거버넌스 자동화 시스템 구현
안녕하세요, 주형권입니다. 정말 오랜만에 블로그로 인사 드립니다.그동안 밀려드는 현업과 하루가 다르게 쏟아지는 AI 기술들을 쫓아가느라 도무지 글을 쓸 여유가 없었습니다. 그리고 개인적
burning-dba.tistory.com
오늘 글 주제인 [DRE] 보안 점검 리포트 자동화 (With. Databricks & AWS)의 경우 기존에 수동으로 하던 업무를 자동화 하는 내용의 글입니다. 제가 보안 전문가나 거버넌스 전문가는 아니지만 현재 보안팀의 요청으로 수행하는 수동 업무를 자동화한 것이므로, 내용이 조금 이상해도 양해를 부탁드립니다.
1. 왜 이것을 만들었는가?
저희 팀에서는 매월 보안 점검 리포트를 작성합니다. 개인정보를 누가 조회/수정/삭제/다운로드했는지, 비인가 계정은 없는지, 퇴사자 권한은 제때 회수되었는지 등을 점검하는 것 입니다. 기존에는 담당자가 엑셀로 로그 직접 조회하여 수동으로 만들었습니다. 매번 수일이 걸렸고, 워크스페이스가 여러개라 대조해야 할 데이터가 많았습니다.

여기서 제가 느낀 문제는 크게 세 가지였습니다:
첫째, 느립니다. 월 1회 점검이니까 그 사이에 퇴사자 계정이 30일간 방치될 수도 있습니다. 비인가 접근이 몇 주간 계속될 수도 있습니다.
둘째, 사람이 하면 누락됩니다. 특히 "이 쿼리가 개인정보 테이블,컬럼에 접근했는가?"를 판단하려면 접근 로그 + 테이블 리니지 + 개인정보 태그를 3단 조인해야 하는데, 이건 수동으로 불가능에 가깝습니다.
셋째, 사람에 의존합니다. 담당자가 바뀌면 점검 기준도 달라집니다. A님은 30일 미접속을 장기 미접속으로 보는데, B님은 60일로 보는 식입니다.
저는 이 문제를 DRE 관점에서 바라보았습니다. 파이프라인 장애 감지를 자동화했듯이, 보안 점검도 코드로 정의하면 되지 않을까? 판단 기준을 SQL로 명시하면, 누가 하든 동일한 기준으로 점검이 되고, 리포트는 자동으로 나오지 않을까? 위에 링크가 걸려있는 "[DRE] Databricks 감사 로그 기반 데이터 거버넌스 자동화 시스템 구현" 에서 더욱 진화되어 리포트까지 만들어지는 형태로 보시면 좋을거 같습니다.
2. 설계와 구현
제일 먼저 고민한 것은 "어디에서 데이터를 가져와서, 어떻게 점검하고, 어디에 보여줄 것인가?"였습니다. 알림으로 받아야 하는 부분과 대시보드화하여 보여줘야 하는 것들을 나누고 정리하였습니다. (결론적으로 Databricks로 데이터를 모으고, Databricks의 Redash를 사용 하였습니다.)
우선 다음과 같이 3 계층으로 설계하였습니다.

Layer 1: Data Collection (수집 계층)
보안 점검의 출발점은 "누가, 언제, 무엇을 했는가"를 알 수 있는 원천 데이터입니다. 문제는 이 데이터가 한 곳에 있지 않다는 것이었습니다. Databricks 내부 로그, AWS 클라우드 로그, Monitoring 시스템(RDS) — 세 군데에 흩어져 있었고, 각각 수집 방법과 주기가 달라야 했습니다.
① Databricks Audit / System table
Databricks는 플랫폼 내의 모든 행위를 system.access.audit 시스템 테이블에 기록합니다. 로그인, 쿼리 실행, 테이블 접근, 권한 변경, 다운로드등 이 모든 이벤트가 여기 있습니다. 하지만 시스템 테이블을 직접 대시보드에서 조회하면 비용과 성능 문제가 있기 때문에, 매시 체크포인트 기반으로 증분 수집하여 monitoring.governance.audit_checkpoint(audit 통합 테이블)에 적재합니다.
(📌참고 : Databricks는 system 테이블이 Delta Shraing이 불가능 / system.query.history는 Notebook 행위가 기록되지 않음)
같은 Job(H_monitoring_governance_collector)에서 쿼리 이력, 테이블 리니지, 개인정 칼럼 태그도 함께 수집합니다. 이것들이 있어야 "이 쿼리가 개인정보 테이블에 접근했는가?"를 판단할 수 있습니다.
② AWS CloudTrail
Databricks 바깥의 AWS 행위도 점검 대상입니다. 특히 AWS 콘솔 로그인과 S3 개인정보 버킷 직접 접근은 Databricks 로그에 남지 않습니다. 이를 위해 CloudTrail 로그를 S3에서 직접 읽어옵니다.
여기서 복잡했던 것은 리전 분산이었습니다. IAM은 글로벌 서비스라 us-east-1에만 로그가 남지만, S3 접근 로그는 버킷이 있는 리전에 각각 기록됩니다. External Location을 리전별로 등록하고, 수집 Task를 분리해서 해결했습니다.
추가로 IAM 사용자 목록(누가 등록되어 있는가)은 Databricks 내부망에서 iam.amazonaws.com으로 egress가 안 되는 제약이 있어서, Airflow에서 boto3로 수집한 뒤 SQL Warehouse API를 경유해 적재하는 우회 경로를 만들었습니다. (주로 연결이 안 되는 부분에 대해서는 eks위의 Airflow로 수집해서 Databricks에 적재하는 방식으로 하였습니다. / 이 문제는 회사의 내부 Network 정책 문제이므로, 회사마다 다릅니다.)
③ Monitoring RDS Sync
이 사람이 정당한 접근 권한이 있는가"를 판단하려면 사내 인사 시스템 데이터가 필요합니다. 현재 시스템상 Monitoring RDS에 이 데이터를 수집 하고 있으므로, 사용자 마스터(재직/퇴사 상태), 접근 승인 이력 등을 동기화합니다. 특히 user_master_snapshot은 기준 스냅숏을 보존해서, "이 사람은 언제 퇴사했고, 권한은 제때 회수되었는가"를 시점별로 추적할 수 있게 했습니다.
또한 기존에 만들었던 개인정보 사용관련 내역 , 비인가 IP 접근 로그등도 Monitoring RDS에 있으므로, 주기적으로 Databricks 쪽으로 Unity Catalog화 시켰습니다. 이렇게 해야지 추후에 한곳에서 Redash를 통해서 대시보드화가 가능하고, 유기적으로 데이터를 파악 할 수 있기 때문입니다. (Monitoring RDS의 경우 추후에 완전 Databricks에 종송 시키려고 계획중입니다.)
Layer 2: Monitoring Tables (가공 계층)
Layer 1에서 수집한 원천 데이터는 그대로 쓰기엔 형태가 제각각입니다. 이 계층의 역할은 수집된 데이터를 점검 가능한 형태로 정규화하는 것입니다. monitoring.governance.* 스키마에는 Databricks 거버넌스 관련 테이블이, monitoring.aws.* 스키마에는 AWS 클라우드 관련 테이블이 들어갑니다.
이 계층이 존재하는 이유는 관심사 분리 때문입니다. 수집 Job이 실패해도 기존 모니터링 테이블은 유지되고, 대시보드는 마지막으로 성공한 데이터를 보여줍니다. 또한 수집 환경이 Databricks이든 Airflow이든 상관없이, 결과물은 모두 Unity Catalog의 같은 테이블에 적재됩니다.
핵심 테이블들의 역할을 정리하면 다음과 같습니다.
| 테이블명 | 의미와 역할 |
| audit_checkpoint | "누가 무엇을 했는가" (전체 행위 로그) |
| workspace_users | "이 사람이 누구인가" (계정 상태·그룹) |
| user_master_snapshot | "이 사람이 정당한가" (재직/퇴사 이력) |
| schema_change_log | "스키마가 어떻게 변했는가" (ALTER 추적) |
| iam_user_snapshot | "AWS에 누가 등록되어 있는가" (일일 스냅샷) |
| iam_cloudtrail_logs | "AWS 콘솔에 누가 접속했는가" (로그인 추적) |
| cloudtrail_logs | "개인정보 버킷을 누가 직접 접근했는가" (S3 GetObject) |
이 테이블들이 Layer 3의 대시보드 쿼리에서 조인되면서 "A라는 사람이 B라는 개인정보 테이블에 접근했고, 이 사람은 C팀 소속이며 정당한 승인을 받았다/받지 않았다"는 판단이 가능해집니다.
Layer 3: Dashboard & Alerting (표현 계층)
마지막 계층은 "판단한 결과를 사람에게 보여주는" 부분입니다. 크게 두 가지로 나뉩니다.
① Dashboard
Databricks Redash Dashboard로 만든 15페이지짜리 보안 점검 리포트입니다. 점걸 월 파라미터를 선택하면 해당 월의 전체 보안 현황이 즉시 반영됩니다.
구성은 "요약 → 상세"의 드릴다운 구조입니다.

▸ 요약 페이지: 9개 점검 항목의 건수와 특이사항을 한눈에 보기 가능
▸ 개인정보 접속기록: 조회/수정/삭제/다운로드 각각의 상세하게 보기 가능
▸ 접근통제: 비인가 계정, 반복 로그인, 장기 미접속, 권한 회수 등 확인 가능
▸ 상세 페이지: 각 항목별 행 단위 drill-down (상세하게 추적하거나 새로운 담당자에게 탐지, 감사 기준 상세 표기)
실제로 요약 페이지를 월간 보안 점검 메일에 그대로 다운로드하여 첨부하면 끝나게 변경하였습니다. 기존에는 담당자가 각각의 SQL , Python코드등을 모두 실행하여 일일이 찾아서 숫자를 엑셀에 직접 기입하여 그것을 메일에 첨부하였다면, 이제는 대시보드 화면을 다운로드하여 첨부하면 끝납니다.
추후에 문제가 있거나 자세한 내역을 요구하면 각각의 상세 페이지에 들어가서 기준과 함께 상세 로그를 모두 조회도 가능합니다.


② Teams Alerts
월간 리포트만으로는 부족한 항목이 있습니다. 비인가 IP 접근은 30일 뒤에 발견하면 이미 늦습니다. 이런 긴급 항목은 Airflow에서 30분 간격으로 감지해서 Teams 채널로 즉시 알림을 보냅니다. 이 패턴은 이전 글에서 만들었던 파이프라인 장애 알림과 동일한 구조를 재사용한 것입니다. "감지 → 알림 → 조치" 흐름이 코드로 정의되어 있으니, 밤이든 주말이든 동작합니다.
이 3 계층 구조의 핵심은 각 층이 독립적으로 동작한다는 것입니다. 수집이 1시간 늦어져도 대시보드는 마지막 데이터로 정상 동작하고, 대시보드를 수정해도 수집 Job에 영향이 없습니다. 어디가 문제인지 빨리 찾을 수 있고, 한 곳을 고쳐도 다른 곳이 깨지지 않습니다.
설계 원칙
이 3계층 설계를 원칙은 네 가지입니다.
| 원칙 | 설명 | 적용 예시 |
| 멱등성(Idempotency) | 모든 수집 Job은 재실행해도 동일 결과 | replacewhere(event_date) MERGE INTO |
| 관심사 분리 | 수집/가공/시각화 3계층 독립 | 수집 Job 장애가 대시보드에 영향을 안줌 |
| 비용 최적화 | Serverless SQL Worehouse + 선별적 수집 | 필요한 이벤트만 필터링 수집 수집시 컴퓨팅 자원 재사용/최소화/최적화 |
| 소스 통일 | 수집 환경이 달라도 저장은 Unity Catalog(Databricks 테이블화) | Airflow/Databricks 모두 동일하게 적재 |

3. 데이터를 모으기
보안 점검을 하려면 일단 데이터가 있어야 합니다. "누가 무엇을 했는가"를 알려면 로그가 필요하고, "이 사람이 정당한가"를 판단하려면 사용자 마스터가 필요하고, "이 테이블이 개인정보인가"를 알려면 개인정보 태그가 필요합니다.
문제는 이 데이터들이 다 다른 곳에 있다는 것이었습니다. Databricks 시스템 테이블, AWS S3 CloudTrail, 사내 RDS. 이것들을 하나의 모니터링 레이어로 모아야 했습니다.
| 패턴 | 적재 방식 | 적용 대상 |
| System Table → MERGE | 체크포인트 증분 | audit_checkpoint, lineage, tags |
| Airflow → SCIM API → SQL Connector | DELETE + batch INSERT | workspace_users |
| JDBC → Overwrite | RDS에서 적재 | schema_change_log, user_master |
| S3 → replaceWhere | External Location 직접 읽기 | cloudtrail_logs, iam_cloudtrail_logs |
| Airflow → SQL API | 네트워크 제약 우회 | iam_user_snapshot, iam_user_changes |

패턴 1: System Table → MERGE
Databricks 시스템 테이블( system.access.audit , system.access.table_lineage, system.information_schema.column_tags)은 플랫폼이 자동으로 쌓아주는 데이터입니다. 문제는 이걸 대시보드에서 직접 조회하면 매번 전체 스캔이 발생해서 비용과 성능 문제가 생긴다는 것입니다. 또한 앞서 이야기 했듯이 어차피 여러개의 워크스페이스라서 조회도 안되며, 한번에 보여주려면 한곳에 적재해서 보여줘야함
그래서 H_monitoring_governance_collector Job이 매시 돌면서, 마지막 수집 시점(watermark) 이후의 데이터만 가져와서 MERGE INTO로 적재합니다. audit 로그는 workspace_name을 TEXT로 함께 적재해서 나중에 별도 조인이 필요 없게 했고, lineage와 tags는 개인정보 테이블만 필터링해서 저장합니다.
-- PII 테이블 식별 필터 (비운영/시스템 영역 제외)
WHERE tag_name = 'pii' AND tag_value = 'true' -- 개인정보
AND catalog_name NOT IN ('sandbox','monitoring','system','__databricks_internal',...) -- 제외 대상 카탈로그
AND catalog_name NOT LIKE '%sandbox%' -- sandbox는 무조건 제외
패턴 2: Airflow → SCIM API → SQL Connector
workspace_users는 Databricks SCIM API(/api/2.0/preview/scim/v2/Users)를 호출해서 수집합니다. 단, Databricks Job이 아니라 Airflow에서 실행됩니다. IAM 수집과 마찬가지로, 워크스페이스 토큰을 사용해 각 워크스페이스의 SCIM API를 직접 호출해야 하기 때문입니다.
수집 대상은 여러개의 워크스페이스입니다. 각 워크스페이스 별로 페이징 처리(100건씩)로 전체 사용자를 조회하고, databricks-sql-connector로 SQL Warehouse에 접속해 DELETE + batch INSERT(500건 단위)로 적재합니다. (페이징 처리 안하면 짤릴수 있습니다.)
[Airflow]
→ requests.get(f"https://{domain}/api/2.0/preview/scim/v2/Users")
→ 6개 워크스페이스 순회 (페이징 100건씩, user_id 중복 제거)
→ databricks-sql-connector → SQL Warehouse
→ DELETE FROM workspace_users WHERE workspace_id = :ws_id
→ INSERT INTO ... VALUES (...) (batch 500)
각 사용자의 groups와 roles를 JSON 배열로 저장하는데, 이 그룹 정보가 중요한 이유는 관리자 판별 기준이 groups LIKE '% Data Engineer%'이기 때문입니다. 대시보드에서 "이 다운로드가 관리자 행위인가, 분석가 행위인가"를 구분할 때 쓰입니다. 또한 display_name을 파싱 해서 이름/부서/계정구분을 분리 저장합니다. "홍길동 AI플랫폼팀" 같은 형식이면 이름="주형권", 부서="AI플랫폼팀"으로 나뉘고, 이름만 있으면 시스템계정으로 분류합니다. (그래야 나중에 쓸때 편합니다.)
패턴 3: JDBC → Overwrite
Monitoring RDS에 있는 사용자 마스터와 승인 이력은 2시간마다 JDBC로 읽어옵니다. 계속해서 데이터를 Overwrite 하는 방식인데, 사용자 데이터 자체가 많지 않고, RDS의 데이터를 MERGE로 계속 가져오기보다 수천 건 정도는 그냥 전체 FULL SCAN 하여 Overwrite 방식을 채택하였습니다.
또한 user_master_snapshot은 snapshot_date 칼럼을 추가해서 월말 기준 스냅숏을 보존합니다. "이 사람은 3월에 퇴사했는데, 4월 리포트에서 권한이 아직 살아있다"를 판단하려면 시점별 데이터가 있어야 합니다. Liquid Clustering으로 30일 치만 유지해서 비용을 제어합니다.
일단 user_master 테이블 자체가 기존에 시스템이므로, 최대한 건들지 말자는 의도 snapshot 형태로 만들었습니다. 앞서 말했듯이 건수 자체가 많지 않으므로 큰 부담은 아니라고 판단하였습니다.
패턴 4: S3 → replaceWhere
CloudTrail 로그는 S3에 JSON으로 쌓이는 구조입니다. External Location을 등록해서 Databricks에서 직접 읽고, replaceWhere(event_date)로 멹등하게 적재합니다. 같은 날짜를 두 번 수집해도 결과가 동일합니다.
여기서 구분해야 할 것이 두 가지입니다
- IAM/콘솔 로그(iam_cloudtrail_logs) : IAM은 글로벌 서비스라 us-east-1 한 곳에만 로그가 쌓입니다. eventSource IN ('iam.amazonaws.com', 'signin.amazonaws.com')만 필터 해서 가져옵니다. 처음에 전부 가져와서 일단 적재하고 필터해서 써보려고 하였는데, API를 통해서 가져오다 보니 너무 오래 걸려서 필터를 해서 가져오도록 변경하였습니다.
df = spark.read.json(f"s3://audit-logs/.../us-east-1/{Y}/{M}/{D}/")
.filter("eventSource IN ('iam.amazonaws.com', 'signin.amazonaws.com')")
df.write.format("delta") \
.option("replaceWhere", f"event_date = '{target_date}'") \
.mode("overwrite").saveAsTable("monitoring.aws.iam_cloudtrail_logs")
- S3 개인정보 버킷 접근(cloudtrail_logs) : 개인정보 버킷은 3개 리전에 흩어져 있어서, External Location 3개를 등록하고 source 칼럼으로 리전을 구분해서 통합 적재합니다. GetObject만 필터 해서 PII 다운로드만 추적합니다.

위와 같이 바로 등록해서 S3에 적재된 CloudTrail 로그 자체를 읽어서 처리하도록 만들었습니다.
패턴 5: Airflow → SQL API
Databricks 클러스터 내부망에서는 iam.amazonaws.com으로 egress가 차단되어 boto3.list_users() 호출이 ConnectTimeout으로 실패합니다. 사실 열면 열 수 있는데, 앞서 말했듯이 모니터링 자체가 기존의 시스템에 영향을 주거나 보안적 문제를 일으키지 않으면서 모든 것을 최소한의 영향력을 미치면서 만들기 위해서 이미 되어 있는 부분으로 우회하는 경향이 강했습니다.
결국 Airflow(egress 가능한 환경)에서 boto3로 IAM 사용자 목록을 수집하고, SQL Warehouse Statements API를 경유해서 Unity Catalog에 적재하는 우회 경로를 만들었습니다.
[Airflow EC2]
→ boto3.list_users() (IAM API)
→ SQL Warehouse Statements API (batch 500건)
→ DELETE FROM monitoring.aws.iam_user_snapshot WHERE snapshot_date = :today
→ INSERT INTO ... VALUES (...)
→ INSERT..SELECT diff → monitoring.aws.iam_user_changes (CREATED/DELETED)
iam_user_changes는 특이한데, 클라이언트에서 diff를 계산하지 않고 SQL Warehouse에서 전일 vs 당일 스냅숏을 LEFT ANTI JOIN 해서 생성/삭제를 판단합니다.
INSERT INTO monitoring.aws.iam_user_changes
SELECT :today, 'CREATED', t.user_id, t.user_name
FROM today_snapshot t LEFT ANTI JOIN yesterday_snapshot y ON t.user_id = y.user_id
UNION ALL
SELECT :today, 'DELETED', y.user_id, y.user_name
FROM yesterday_snapshot y LEFT ANTI JOIN today_snapshot t ON y.user_id = t.user_id
4. 점검 기준과 구현
데이터를 모았으면 이제 "무엇이 이상인가"를 판단해야 합니다. 여기서 제가 가장 중요하게 생각한 것은, 판단 기준을 사람의 머릿속이 아니라 명시하고 정의하고 통일 하는 것이었습니다.
예를 들어 "장기 미접속 계정"의 정의를 보겠습니다:
-- 장기 미접속 계정: 마지막 로그인 30일 초과 + 활성 상태
SELECT user_name, last_login, datediff(current_date(), last_login) AS days_inactive
FROM monitoring.governance.workspace_users
WHERE active = true
AND datediff(current_date(), last_login) > 30
이렇게 SQL로 정의해 두면, "30일"이라는 기준이 코드에 박혔니다. 담당자가 바뀌어도, 60일로 바꿀지 30일로 할지 고민할 필요가 없습니다. 이런 식으로 9개 항목 모두를 SQL로 정의했습니다. 또한 앞서 말했듯이 각각의 상세 페이지 아래 부분에 모두 기준과 코드를 명시하여, 담당자가 변경되어도 문제없이 볼 수 있도록 하였습니다.
개인정보 접속기록 점검
이 그룹의 핵심 질문은 "누가 개인정보 테이블에 무슨 짓을 했는가?"입니다. 가장 어려웠던 것은 "이 쿼리가 개인정보 테이블에 접근했는가?"를 판단하는 조인 패턴이었습니다. Databricks 시스템 테이블에는 "이 쿼리가 어떤 테이블을 읽었는가"를 직접 알려주는 칼럼이 없습니다. 그래서 3단 조인이 필요합니다:
-- 개인정보 조회 판단: audit → lineage(source 테이블) → tags(개인정보 여부)
audit_checkpoint -- "누가 언제 무슨 쿼리를 실행했는가"
JOIN table_lineage -- "그 쿼리가 어떤 테이블을 source로 읽었는가"
JOIN column_tags -- "그 테이블이 개인정보 태그가 붙어있는가"
이 조인이 성립하면 "개인정보 조회" 1건입니다. 여기서 중요한 것이 방향성입니다. lineage에는 source(읽은 테이블)와 target(쓴 테이블)이 있는데, 조회/수정/삭제에 따라 다르게 조인해야 합니다.
| 행위 | lineage 조인 기준 | 이유 |
| 조회 (SELECT) | source | 읽은 테이블이 개인정보가 포함인가? |
| 수정 (INSERT/UPDATE/MERGE) | target | 행위를 한 테이블이 개인정보가 포함인가? |
| 삭제 (DELETE/DROP) | target | 삭제,제거를 한 테이블이 개인정보가 포함인가? |
각 항목별로 좀 더 상세히 보면 다음과 같습니다.
① 개인정보 조회
- 3단 조인(audit → lineage source → 개인정보 tags) 가장 건수가 많은 항목이라, 대시보드 요약에서는 "상위 접근자 Top 3"을 특이사항으로 보여줍니다.
② 개인정보 수정
- DML(INSERT/UPDATE/MERGE/COPY): query_history + lineage target 기준 조인
- DDL(ALTER TYPE): schema_change_log에서 컬럼 타입 변경 추적
-- 수정 판단: DML을 lineage target 기준으로 개인정보 조인
SELECT qh.executed_by, qh.statement_id, tl.target_table_full_name
FROM query_history qh
JOIN table_lineage tl ON qh.statement_id = tl.source_statement_id
JOIN column_tags ct ON tl.target_table_full_name = ct.table_full_name
WHERE ct.tag_name = 'pii' AND ct.tag_value = 'true'
AND qh.statement_type IN ('INSERT', 'UPDATE', 'MERGE', 'COPY')
-- sandbox 오탐지 제외 sandbox는 복사해서 자유롭게 쓰기가 가능함
AND NOT REGEXP_LIKE(UPPER(qh.statement_text), '(INTO|OVERWRITE)(\\s+TABLE)?\\s+SANDBOX\\.')
-- MV 내부 테이블 제외 MV의 경우 내부 동작이므로, 제외
AND tl.target_table_name NOT LIKE '%_materialization_mat_%'
SANDBOX(개별 사용자가 자유롭게 쓰는 공간) 필터는 실제로 겪은 문제에서 나왔습니다. 샌드박스 카탈로그에 INSERT 하는 쿼리가 lineage에 있어서, 개인정보 테이블 수정(INSERT * SELECT 같은 행위)으로 잡히는 경우가 있었는데, statement_text를 정규식으로 검사해서 제외합니다. SANDBOX의 경우 사용자가 인가하에 자유롭게 복사하여 쓸 수 있으므로, 제외 대상이며 아마도 각각의 회사에도 이러한 자유로운 공간이 있을 것으로 판단되며, 이 부분은 자유롭게 고치시면 좋을거 같습니다.
또한 Materialized View 내부 테이블(%_materialization_mat_%) 역시 마찬가지로 오 탐지 원인이었습니다. (Databricks가 수시로 복사해서 만들고 있었음)
③ 개인정보 삭제
- "실제 삭제"만 잡습니다. lineage상의 데이터 이동은 삭제가 아닙니다.
-- 삭제 판단: 4가지 유형의 UNION ALL
1) DROP TABLE (audit: action_name = 'dropTable')
2) permanentDelete (audit: action_name = 'permanentDelete')
3) DELETE FROM (query_history: statement_type = 'DELETE')
4) 배치 삭제 Job 결과 (audit.management.delete_job_result)
현재 저희 팀에서는 개인정보를 주기적으로 삭제를 합니다. (고객의 요청 또는 법적 만료 시) 이로 인해서 배치 삭제가 자칫 수동 삭제나 DROP 같은 행위와 겹쳐서 과도하게 측정되는 경우가 있으므로, 이 부분에 대해서는 따로 제외하여 적재하고 집계하여 보이도록 하였습니다. (최대한 오탐을 방지하지 위해서)
④ 개인정보 다운로드
단순하지만 허수가 존재하였으며, 이를 필터 하여 보여주었습니다.
-- 다운로드 판단: UI 수동 다운로드만
WHERE action_name = 'downloadResult'
AND COALESCE(request_params.fileType, '') NOT IN ('json', 'arrows')
여기서 중요한 배경: Databricks는 쿼리 결과를 가져가는 모든 행위를 downloadResult로 기록합니다. 사람이 UI에서 CSV 버튼을 누른 것도, 프로그램이 API로 결과를 받아간 것도 동일한 이벤트명입니다. 구분은 fileType으로만 가능합니다:
- json : databricks-sql-connector나 SQL Statements API(/api/2.0/sql/statements)로 쿼리를 실행하면, 결과를 JSON으로 fetch 합니다. Airflow에서 SQL Warehouse로 데이터 적재할 때, 대시보드가 새로고침할 때, 모니터링 Job이 결과를 읽을 때 — 이 모든 게 downloadResult(json)으로 기록됩니다.
- arrows : Apache Arrow 프로토콜로 결과를 전송하는 경우입니다. Spark와 SQL Warehouse 간 데이터 전송, 또는 고성능 커넥터(PyArrow 기반)가 결과를 받아갈 때 사용됩니다. 역시 사람의 행위가 아닙니다.
즉, 이 필터 없이 돌리면 SQL Warehouse에 연결된 모든 프로그램적 접근이 "다운로드"로 잡힙니다. 실제로 처음 필터 없이 돌렸을 때 월 다운로드가 수백 건 이상으로 나왔는데, 실제 UI 수동 다운로드는 수십 건이었습니다. 나머지는 전부 대시보드 새로고침, Airflow Job, 모니터링 쿼리에서 발생한 것이었습니다.
또한 다운로드는 권한별로 구분합니다:
- 관리자(Data Engineer 그룹) : 정상 운영 행위로 간주
- 분석가(Data Analyst 그룹) : 정상 업무 행위로 간주
- 기타 : "확인 필요"로 표기 (사실상 잘못된 행위)
접근통제 점검
이 그룹의 핵심 질문은 "이 사람이 접근해도 되는 사람인가?"입니다.
⑤ 비인가 계정 탐지
Monitoring 시스템(RDS)의 사용자 마스터에 없는 계정이 Databricks에서 활동했다면 비인가입니다.
-- 비인가 계정: audit에 있지만 사용자 마스터에 없는 계정
SELECT a.user_identity_email, a.workspace_name, count(*) AS event_count
FROM monitoring.governance.audit_checkpoint a
LEFT JOIN monitoring.governance.user_master_snapshot u
ON a.user_identity_email = u.user_email
WHERE u.user_email IS NULL -- 마스터에 없음
AND a.user_identity_email IS NOT NULL
GROUP BY 1, 2
⑥ 반복 로그인 이상 탐지
동일 사용자가 같은 시간대에 연속으로 로그인한 경우. 비밀번호 무차별 대입 공격이나 세션 불안정으로 인한 비정상 로그인을 감지하는 목적입니다.
⑦ 장기 미접속 계정
위에서 본 SQL 그대로(30일 기준). 활성 상태인데 안 들어온다는 건, 권한이 방치된 것이므로 회수 대상입니다. 30일 기준의 이유는 현재 회사의 기준이 30일 이상이 장기 미접속으로 분류되기 때문입니다. 회사의 사정에 따라서 기준은 다릅니다.
⑧ 퇴사/조직변경 권한 회수
이게 제일 복잡했습니다. 단순히 "퇴사자가 있다"가 아니라, "퇴사했는데 아직 계정이 살아있다"를 찾아야 합니다. 퇴사를 할 경우 퇴사하였습니다~라고 친절하게 로그를 남기거나 하지 않기 때문에 이건 어쩔 수 없이 스냅숏을 비교하였습니다. (퇴사자가 관리자한테 저 퇴사합니다.라고 일일이 말하진 않으므로...)
-- 퇴사자 권한 미회수: 스냅샷에서 퇴사인데, WS에 아직 active
SELECT um.user_email, um.resign_date, wu.workspace_name, wu.active
FROM monitoring.governance.user_master_snapshot um
JOIN monitoring.governance.workspace_users wu
ON um.user_email = wu.user_name
WHERE um.employment_status = '퇴직'
AND wu.active = true
AND um.snapshot_date = (
SELECT MAX(snapshot_date) FROM user_master_snapshot
WHERE snapshot_date <= :report_month_end
)
⑨ 업무시간 외/휴일 처리
저 같은 경우 07:00 ~ 19:00 KST로 범위 밖 작업과 토/일 및 공휴일 달력 테이블을 만들어서 하였는데, 사실 업무 시간의 경우 최근에 유연근무제가 없는 회사가 없기도 하고 조금 애매하긴 했습니다. 딱히 기준을 보안점검을 담당하는 부서에서 정해준 것도 아니라서, 임의로 정한 것은 어쩔 수 없었습니다.
또한 기계적인 행위와 야간에 돌아가는 파이프라인과 주기적으로 업데이트되는 대시보드등을 제외하여 사람이 아닌 경우를 필터링하였습니다.
-- 업무시간외 판단: KST 변환 + 기계적 행위 제외
WHERE (
hour(from_utc_timestamp(event_time, 'Asia/Seoul')) * 60
+ minute(from_utc_timestamp(event_time, 'Asia/Seoul'))
) NOT BETWEEN 420 AND 1139 -- 07:00 ~ 18:59 KST
AND entity_type NOT IN ('DASHBOARD_V3', 'PIPELINE', 'JOB')
단, DBSQL_QUERY , NOTEBOOK은 제외하지 않았는데, 이 부분은 사람이 한 것인지 기계가 한것인지 판단이 어려워서 과도하게 탐지가 되어도 일단 잡는 게 맞다고 판단하여 제외하지 않았습니다.
5. 결과물
이렇게 만들어진 대시보드는 현재 15페이지로 구성되어 있으며, 월 파라미터를 통해서 월별로 해당 월의 점검 결과가 즉시 표현 됩니다. 실시간으로 데이터가 쌓이면 계속해서 업데이트되는 구조로 되어 있습니다.
기존에 작성에 많은 시간이 필요하고 사람 손을 많이 타야 했던 리포트가 클릭 한 번으로 나옵니다. 또한 상세 페이지 또한 모두 만들어져서 언제 누가 무엇을 물어보든 같은 기준으로 대답하고 같은 숫자를 제시할 수 있습니다.




그리고 IP 비인가 접근이나 개인정보 칼럼/테이블 사용에 관련한 알림을 실시간으로 Airflow가 감지해서 Teams로 알림을 보냅니다. 이 부분은 이전 글에서 만들었던 장애 알림 시스템과 동일한 패턴을 재사용했습니다.
[DRE] Databricks 감사 로그 기반 데이터 거버넌스 자동화 시스템 구현
[DRE] Databricks 감사 로그 기반 데이터 거버넌스 자동화 시스템 구현
📌 이 글의 핵심 요약무슨 문제인가: Databricks 글로벌/멀티 테넌트 환경을 운영하면서 비인가자의 접근, 불필요한 퍼스널 토큰 남용, 보안 취약점 노출을 수동으로 일일이 감시하기 어려웠음.어
burning-dba.tistory.com
[DRE] 데이터 파이프라인 장애 감지 자동화 — Airflow + Teams 연동
[DRE] 데이터 파이프라인 장애 감지 자동화 — Airflow + Teams 연동
📌 이 글의 핵심 요약무슨 문제인가: 기존 팀 단위의 단편적인 모니터링 방식으로는 본부 전체 사용자가 무작위로 생성하는 수많은 Databricks 워크플로우의 성공/실패/지연 상태를 체계적으로 관
burning-dba.tistory.com

정량적 성과
| 항목 | Before(수동) | After(자동) |
| 보안 점검 리포트 작성 | 3~7일 | 0분 |
| 비인가 탐지 | 점검 전까지 알 수 없음 | 실시간 |
| 점검 대상 | 일부 Workspace | 모든 Workspace + AWS |
| 점검 기준 | 담당자 의존 | SQL , Python 코드 자동화 |
마치며
2026년부터 DRE 업무를 하면서 가장 크게 느낀 것은, 기술만으로는 안 된다는 것입니다.
아무리 좋은 모니터링 시스템을 만들어도, 사람들이 이걸 신뢰하고 활용하지 않으면 의미가 없습니다. 이번 보안 점검 자동화도 마찬가지입니다. 대시보드를 만들었으니 끝이 아니라, 이걸 보는 사람들이 "아, 이게 미리 나오네"하고 신뢰해야 진짜 의미가 생깁니다. 이게 진짜 큰 의미인 게, 내가 알고 사용자에게 말하느냐 사용자가 이상하다고 해서 내가 보느냐는 신뢰에 엄청난 차이를 만듭니다.
이전 글에서도 말씀드렸지만, DRE에서 Monitoring과 Observability의 차이는 "알렸다"와 "알렸고, 무엇이 영향을 받는지까지 보여준다"의 차이라고 생각합니다. 보안 점검도 마찬가지로
- 단순히 "비인가 접근이 있었다" → Monitoring
- "비인가 접근이 있었고, 이 사람이 개인정보 테이블에 A, B 칼럼에 접근 후 해당 데이터를 다운로드하였다" → Observability
기존에 Monitoring에서 그쳤다면 이제는 Observability를 지향하고 있습니다. 기존에 저는 단순히 알림을 만드는 Monitoring 시스템을 만들었다면 이제는 관측을 하는 Observability 시스템을 만들고자 합니다.
하지만 어디까지나 아직은 사이드 업무이며, 비용과 자원의 한계는 여전히 존재합니다. 아직 DRE가 보편화되지도 않았으며, DRE의 업무를 위해서 너무 많은 자원을 쓴다면 배보다 배꼽이 크다고 봅니다. 더욱더 고민하고 최소의 자원으로 최대의 효율을 내는 Observability 시스템을 만들기 위해서 더욱 노력해 보겠습니다.
긴 글 읽어 주셔서 감사합니다.
'Data Reliability Engineering' 카테고리의 다른 글
| [DRE] 실패 알림을 넘어 영향 범위 예측으로 — 데이터 파이프라인 Observability 구현 (with. Databricks) (0) | 2026.07.30 |
|---|---|
| [DRE] 로그 정합성 모니터링 고도화 (0) | 2026.07.15 |
| [DRE] Databricks 감사 로그 기반 데이터 거버넌스 자동화 시스템 구현 (1) | 2026.05.28 |
| [DRE] 데이터 파이프라인 장애 감지 자동화 — Airflow + Teams 연동 (0) | 2026.04.12 |
| [DRE] 데이터 엔지니어 다음 커리어, Data Reliability Engineer(DRE) 로드맵 (1) | 2025.12.08 |
