왜 DML 로그가 필요한가요?
🔐 내부통제 및 감사 대응 목적
- DB 직접 접근을 통한 데이터 조작(DML: Insert, Update, Delete)에 대해 로그가 없으면 추적 불가능합니다.
- ISMS, KISA 가이드, 개인정보보호법 상 내부 시스템에서 중요 정보 변경 및 처리에 대한 이력관리는 필수입니다.
- 특히 내부자 위협 대응, 부정 행위 탐지, 사후 감사를 위해 DML 로그는 매우 중요합니다.
📜 ISMS-P 인증 기준 연관 항목
- 관리적 보호조치 > 2.5.5(접근 기록의 생성 및 보관)
→ "정보시스템 및 중요 정보에 대한 접근 및 처리 기록을 생성하여 보관하고, 위·변조 방지를 위한 보호조치를 적용하여야 한다." - 기술적 보호조치 > 3.3.2(중요 시스템 및 정보에 대한 접근 통제)
→ DML 작업은 반드시 기록되어야 하며, 관리자 작업의 이력 기록(logging) 은 보안관제에서도 핵심 포인트입니다.
❌ Connection log를 끄는 것이 위험한 이유
🚨 로그 미수집의 위험성
- Connection 로그를 끄면 누가, 언제, 어떤 IP에서 DB에 접속하여 무엇을 했는지 확인이 불가능합니다.
- 특히 DBA 또는 시스템 관리자에 의한 직접 접근은 고위험 작업으로 간주되며, ISMS 심사 시 중점 점검 항목입니다.
📉 보안/감사 실패 사례
- 실무에서 connection log 또는 audit log 비활성화로 인해 사고 추적 실패 사례 다수 발생.
- 일부 기업은 심사 중 DB 변경 작업이 누락되었거나, 로그 미보관 사유로 인증 실패 또는 시정조치 권고 받음.
DB Audit 기능 활성화
- Oracle, MySQL, MariaDB, MSSQL 모두
Audit plugin
또는Extended Events
,SQL Server Audit
등의 기능으로 DML 로그 설정 가능. - 단, 로그 용량 폭증 우려가 있다면 다음과 같은 조건을 설정
- 특정 계정(DBA, 어플리케이션 계정)만 감시
- 특정 테이블만 감시
- 시간대 조건 설정 등으로 필터링 적용
로그 저장 기간 조절
- 1년 저장이 부담된다면, 아래와 같이 운영
- 3개월은 원본 로그, 이후는 압축 또는 외부 저장소로 이동(예: S3, NAS)
- 통합 로그관리 시스템(SIEM)과 연계하여 분석/보관
변경 이력 테이블 운영
- Trigger 기반 이력 테이블 별도로 운영하여, DML 발생 시 변경 전/후 값 저장 가능
- 단, 이는 성능 저하 우려 있으므로 주요 테이블/컬럼 대상만 적용
nGrinder, SQL Proxy, Wazuh 등 연계
- DB 접근 이력을 애플리케이션 레벨 또는 보안 모니터링 도구로 통합하여 기록 강화
항목 | 권고 사항 |
---|---|
Connection Log | 절대 비활성화 금지 |
DML 로그 | DBA 계정 또는 주요 테이블 대상으로 선택적 수집 설정 |
보관 기간 | 1년 이상 또는 적어도 3~6개월 + 외부 보존 |
대체 방안 | Trigger 또는 보안 솔루션과 연계 가능 |
점검포인트
- DB 감사로그 설정 여부 확인
- DBA 또는 관리자 계정의 작업 이력 수집 여부
- DML 감시 대상 정의 여부
- 로그 보관 정책 수립 여부 (기간, 백업, 저장소)
- 로그 무결성 보호 적용 여부 (암호화, 접근권한 제한)
내부통제(Internal Control), ITGC(IT General Control), ISMS/ISMS-P 인증, 감사 추적성 확보 측면에서 중요합니다.
ITGC 및 감사 관점에서 DML 로그 분리 보관의 이슈
주요 감사 목적: "추적성", "무결성", "비가역성" 보장
- 추적성 (Traceability)
- "누가, 언제, 어떤 데이터에 대해 무엇을 변경했는가"를 시점 기준으로 명확하게 재현할 수 있어야 함
- 로그가 실시간 연계되지 않고, 외부 저장소에 분리되어 있을 경우 "시점 재구성"이 어렵다고 판단될 수 있음
- 무결성 (Integrity)
- 감사 로그는 조작 가능성으로부터 보호되어야 하며, 일반 파일 수준의 보관만으로는 변조 우려가 있음
- DB서버 외부에 보관할 경우 변조 가능성에 대한 대비책(디지털 서명, WORM 등)이 없으면 무결성 보장이 어렵다고 간주됨
- 비가역성 (Non-repudiation)
- 원본 로그는 삭제나 변경이 불가능하거나, 변경 시 반드시 흔적이 남는 형태여야 함
- 복사본만 보관하고 원본 삭제하거나 외부로 옮기면 비가역성을 확보하지 못한 것으로 평가됨
국내외 기준 및 인증 요구사항
🇰🇷 ISMS-P 인증 기준 (KISA, 개인정보보호위원회)
항목 | 세부 내용 |
---|---|
2.5.5. 접근 기록의 생성 및 보관 | 접근 및 주요 작업(예: DML)에 대한 기록을 생성 및 보관하고 무결성 보장 필요 |
3.1.3. 로그 보존 및 검토 | 감사 로그는 변조·삭제가 불가능한 형태로 보관해야 하며, 외부 백업 시에도 암호화, 서명 등으로 무결성 보장 필요 |
KISA 해설서 주석
"로그가 중앙서버 또는 외부 저장소에 분리 보관되더라도 DB 서버 내 원본을 일정 기간 보존하고, 별도의 위·변조 방지 수단을 적용해야 함"
🇺🇸 SOX (Sarbanes-Oxley Act) / ITGC 감사 원칙
- Change Management Control, Logical Access Control 영역에서 "Application and DB change logs must be immutable and tied to source systems."
- 로그는 원본 시스템(DB 서버) 상에서 생성되며, 이 기록이 감사 증적의 유일한 근거로 인정됨
- 외부 저장소로 로그가 이관될 경우, 그 이관 대상도 tamper-proof storage 또는 cryptographic assurance가 있어야 함
실무 점검 포인트 및 우회 가능한 대응방안
항목 | 보안 권고 사항 |
---|---|
DML 로그 위치 | DB 서버 내부 저장소에 일정 기간(예: 3~6개월) 보관 유지 필요 |
외부 이관 | 중앙 로그서버(SIEM 등)로 이관 가능하나, 서명/무결성 검증 기반 백업으로 인정 |
원본 로그 삭제 | 금지 (ISMS 및 감사 시 원본 부재로 판단되면 감점 또는 불인정) |
분리보관 | 가능하나, 반드시 DB서버 내 원본을 일정 기간 보유 + 외부 저장소는 무결성 보장 방식 적용 필요 |
DML 로그를 DB 서버 외부로만 보관하거나 원본 삭제 시 다음과 같은 문제가 발생합니다.
- 감사 대응 불가: 외부 보관 로그의 조작 가능성으로 인해 감사 증적 불인정
- ISMS 인증 감점 또는 미통과: 접근/처리 기록 항목에서 중대한 결함
- 내부통제 부적합: ITGC 관점에서 변조 가능성 있는 로그는 신뢰도 없음
따라서,
반드시 DB 서버 내에 일정 기간 이상 DML 로그를 원본 상태로 보관하고, 외부 이관 시에는 무결성 검증 방식(디지털 서명, WORM, 블록체인 기반 로그 등)을 병행해야 합니다.
참고 가이드
- ISMS-P 인증 기준 해설서 (KISA)
- SOX ITGC Control Guide - ISACA
- [국정원 로그관리지침 (보안관제 기준)]
→ 로그는 최초 수집 시스템 내 원본 보관 후 이관
MySQL에서 Connection Log와 DML Log(데이터 변경 쿼리 기록)를 서로 다른 로그 파일로 분리하여 기록하고자 한다면, 다음과 같은 방법을 사용할 수 있습니다.
로그 유형 | 목적 | 예시 쿼리 | 기록 위치 설정 |
---|---|---|---|
Connection Log | 누가 접속했는지, 언제 접속했는지 기록 | CONNECT , QUIT 등 |
general_log 또는 audit |
DML Log | 데이터를 변경한 쿼리 기록 | INSERT , UPDATE , DELETE |
audit log 또는 binlog (기록 목적에 따라) |
방법 1: MySQL Enterprise Audit Plugin 사용 (추천: 분리 가능)
MySQL Enterprise Edition에서는 audit_log
플러그인을 통해 Connection/DML 로그를 필터링 및 분리 저장할 수 있습니다.
🔧 설정 방법
- 플러그인 활성화
INSTALL PLUGIN audit_log SONAME 'audit_log.so';
- 로그 출력 형식 설정
# my.cnf 또는 my.ini audit_log_format = JSON audit_log_file = /var/log/mysql/audit.log audit_log_policy = LOGINS, QUERIES audit_log_include_users = youruser
- 로그 필터 정책 설정 (v5.7 이후)
CREATE AUDIT FILTER logins_filter SET FILTER 'event_class' = 'connection'; CREATE AUDIT FILTER dml_filter SET FILTER 'event_class' = 'general' WHERE command_text LIKE 'INSERT%' OR command_text LIKE 'UPDATE%' OR command_text LIKE 'DELETE%'; CREATE AUDIT USER 'user1' FILTER 'logins_filter'; CREATE AUDIT USER 'user1' FILTER 'dml_filter';
- 결과
audit.log
내에 JSON 포맷으로 분리된 로그를 수집 가능rsyslog
,fluentd
등과 함께 사용 시 파일 분할 가능 (ex.grep 'connection'
,grep 'query'
)
🔐 주의: MySQL Enterprise Edition 전용입니다. Community 버전은 사용 불가.
방법 2: MariaDB Audit Plugin (MySQL Community 호환) 사용
MySQL Community Edition에서는 Percona Audit Plugin 또는 MariaDB Audit Plugin을 사용하여 분리 로그 수집 가능.
📥 설치
# Percona audit plugin 예시
wget https://www.percona.com/downloads/percona-audit-plugin/
🔧 설정
# my.cnf
plugin-load=audit_log.so
audit_log_file = /var/log/mysql/audit.log
audit_log_policy = QUERIES, LOGINS
audit_log_exclude_users = 'replication'
LOGINS
: Connection 로그QUERIES
: DML 포함 모든 쿼리 로그READS
: SELECT 쿼리 포함
📁 로그 분리 저장 방법
Audit Log는 기본적으로 하나의 파일에 JSON 형식으로 기록되므로, 다음 방법으로 분리 가능:
logrotate
로 시간별 또는 크기별 분리grep
,jq
,fluent-bit
,logstash
등을 통해 Connection과 DML 이벤트를 분리 저장
jq 'select(.event_class=="connection")' audit.log > connection.log
jq 'select(.command=="insert" or .command=="update" or .command=="delete")' audit.log > dml.log
방법 3: General Log + Trigger 기반 분리
1️⃣ Connection Log: general_log 사용
[mysqld]
general_log = 1
general_log_file = /var/log/mysql/connection.log
log_output = FILE
→ 단점: 모든 쿼리를 기록하므로 용량 폭증 가능, DML만 분리 불가
2️⃣ DML 로그는 Trigger 기반 또는 binlog 활용
CREATE TRIGGER after_insert_log
AFTER INSERT ON your_table
FOR EACH ROW
INSERT INTO dml_audit_log(table_name, action, user, timestamp)
VALUES ('your_table', 'INSERT', CURRENT_USER(), NOW());
→ 단점: 테이블마다 트리거 만들어야 하며, 보안성은 audit plugin보다 낮음
🛡️ 보안 및 감사 대응 팁
- Connection 로그와 DML 로그는 파일 분리보다는 이벤트 필터링 방식으로 관리해야 감사 추적성 유지 가능
- rsyslog + jq + WORM 기반 스토리지 조합 추천
- 로그 무결성 검증을 위해 hash 체인 방식, 디지털 서명, 외부 저장 후 hash 보관 등의 보완 필요
분리 방법 | 가능 여부 | 추천 수준 | 조건 |
---|---|---|---|
MySQL Enterprise Audit Plugin | ✅ 완벽 분리 | ★★★★★ | 유료 |
MariaDB Audit Plugin (Community) | ✅ 분리 가능 | ★★★★☆ | 오픈소스 |
General Log + Trigger | ⚠️ 제한적 분리 | ★★☆☆☆ | 고급 설정 필요 |
binlog 기반 수집 (타 시스템에서) | ⚠️ 이벤트 수집 가능 | ★★★☆☆ | 실시간 연계 불가 |
ITGC(IT General Control) 감사 대상 테이블에 대해 Trigger 기반으로 DML 로그를 기록하는 방식은 MySQL 또는 기타 RDBMS 환경에서 주요 테이블에 대한 데이터 변경 내역을 감사 추적용 테이블에 별도로 기록하여 사고 추적성, 변경 책임성, 증적 확보를 가능하게 하는 대표적인 로깅 방식 중 하나입니다.
Trigger 기반 DML 로깅의 개요
- Trigger(트리거)는 특정 테이블에 대해
INSERT
,UPDATE
,DELETE
와 같은 DML 연산이 수행될 때 자동으로 실행되는 사용자 정의 로직입니다. - 이를 활용하여, 데이터 변경 전/후의 값과 사용자 정보 등을 로그 테이블에 자동 저장함으로써 내부 통제 및 ITGC 감사에 필요한 감사 증적을 확보할 수 있습니다.
Trigger를 활용한 DML 로그 기록 구조
📁 구성 요소
- 대상 테이블 (예:
customer
)- ITGC 감사 대상이 되는 핵심 비즈니스 테이블
- 감사 로그 테이블 (예:
audit_log_customer
)customer
테이블의 DML 변경 내역을 저장하는 전용 감사 로그 테이블
- 트리거 함수 (AFTER INSERT/UPDATE/DELETE)
- 대상 테이블에 변경이 발생했을 때 자동으로 로그 테이블에 기록
예제: customer 테이블에 대한 DML 감사 Trigger 구성
1️⃣ 감사 로그 테이블 생성
CREATE TABLE audit_log_customer (
log_id BIGINT AUTO_INCREMENT PRIMARY KEY,
action ENUM('INSERT', 'UPDATE', 'DELETE'),
customer_id INT,
old_name VARCHAR(100),
new_name VARCHAR(100),
changed_by VARCHAR(100),
changed_at DATETIME DEFAULT NOW(),
ip_address VARCHAR(45)
);
📝 변경 전/후 값, 수행자, 수행 시간, IP 등의 감사 정보 포함
2️⃣ AFTER INSERT 트리거
CREATE TRIGGER trg_customer_insert
AFTER INSERT ON customer
FOR EACH ROW
BEGIN
INSERT INTO audit_log_customer (action, customer_id, new_name, changed_by, ip_address)
VALUES ('INSERT', NEW.customer_id, NEW.name, CURRENT_USER(), SUBSTRING_INDEX(USER(),'@',-1));
END;
3️⃣ AFTER UPDATE 트리거
CREATE TRIGGER trg_customer_update
AFTER UPDATE ON customer
FOR EACH ROW
BEGIN
INSERT INTO audit_log_customer (action, customer_id, old_name, new_name, changed_by, ip_address)
VALUES ('UPDATE', OLD.customer_id, OLD.name, NEW.name, CURRENT_USER(), SUBSTRING_INDEX(USER(),'@',-1));
END;
4️⃣ AFTER DELETE 트리거
CREATE TRIGGER trg_customer_delete
AFTER DELETE ON customer
FOR EACH ROW
BEGIN
INSERT INTO audit_log_customer (action, customer_id, old_name, changed_by, ip_address)
VALUES ('DELETE', OLD.customer_id, OLD.name, CURRENT_USER(), SUBSTRING_INDEX(USER(),'@',-1));
END;
보안 및 감사 대응 고려사항
항목 | 설명 |
---|---|
🔒 무결성 보호 | 감사 로그 테이블은 UPDATE , DELETE 권한을 일반 사용자에게 부여하지 않도록 설정해야 함 (GRANT SELECT only ) |
🔍 정기 검토 | 정기적으로 감사 로그를 SIEM이나 로그관리 시스템과 연계하거나, 정기적 스냅샷을 외부 저장소에 백업 |
🧾 기록 항목 확장 | IP 주소, 세션 ID, 앱 계정 등 추가 메타데이터 확보 가능 (웹 또는 앱단에서 트리거 변수로 주입 필요) |
⚠️ 성능 부하 주의 | 대용량 변경 작업 발생 시 트리거로 인한 오버헤드 발생 가능 – 핵심 테이블만 선택 적용 |
📌 자동화 스크립트 구분 | 사람이 아닌 시스템 자동화 계정으로 인한 변경 시 구분 기록 필요 (user_agent 나 계정 명명규칙 활용) |
ITGC 감사 대응 측면에서의 유효성
요구사항 | 트리거 기반 감사 방식의 적합성 |
---|---|
변경기록 보관 | ✅ 변경 전/후 값 및 수행자 기록 가능 |
사후 추적성 | ✅ who/when/what 확보 |
변경 무결성 | ✅ DB 서버 내 기록 및 권한 제어로 변조 위험 감소 |
인증 근거 | ✅ 로그 테이블이 증적자료로 활용 가능 |
로그 삭제 방지 | ✅ DB 계정권한 제한 및 WORM 백업 병행 시 강화 가능 |
여러 테이블에 일괄 트리거 생성하는 SQL 생성기
- ITGC 감사 대상 테이블이 여러 개인 경우,
INSERT
,UPDATE
,DELETE
트리거를 자동으로 생성하기 위한 SQL 생성기
📋 입력 변수 (예시)
SET @table_list = 'customer,orders,products'; -- 대상 테이블 목록
🧪 SQL 생성기 스크립트 (MySQL 5.7 이상)
SET @tables = 'customer,orders,products';
SET @user = 'audit_user';
-- SPLIT TABLES
CREATE TEMPORARY TABLE table_names (tname VARCHAR(255));
INSERT INTO table_names
SELECT TRIM(SUBSTRING_INDEX(SUBSTRING_INDEX(@tables, ',', n), ',', -1))
FROM (
SELECT a.N + b.N * 10 + 1 AS n
FROM (SELECT 0 AS N UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3) a,
(SELECT 0 AS N UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3) b
) num
WHERE n <= LENGTH(@tables) - LENGTH(REPLACE(@tables, ',', '')) + 1;
-- GENERATE TRIGGERS
SELECT CONCAT(
'CREATE TRIGGER trg_', tname, '_insert AFTER INSERT ON ', tname, '
FOR EACH ROW
BEGIN
INSERT INTO audit_log_', tname, ' (action, changed_by, changed_at) VALUES (''INSERT'', CURRENT_USER(), NOW());
END;'
) AS insert_trigger,
CONCAT(
'CREATE TRIGGER trg_', tname, '_update AFTER UPDATE ON ', tname, '
FOR EACH ROW
BEGIN
INSERT INTO audit_log_', tname, ' (action, changed_by, changed_at) VALUES (''UPDATE'', CURRENT_USER(), NOW());
END;'
) AS update_trigger,
CONCAT(
'CREATE TRIGGER trg_', tname, '_delete AFTER DELETE ON ', tname, '
FOR EACH ROW
BEGIN
INSERT INTO audit_log_', tname, ' (action, changed_by, changed_at) VALUES (''DELETE'', CURRENT_USER(), NOW());
END;'
) AS delete_trigger
FROM table_names;
🎯 이 쿼리는 audit_log_[테이블명] 로그 테이블이 사전 생성되어 있어야 하며, 로그 컬럼은 공통 포맷으로 구성되어 있어야 합니다.
Wazuh 또는 SIEM 연동 예시
- MySQL 감사 로그 또는 트리거 로그 테이블 →
filebeat
또는osquery
로 수집 → Wazuh/ELK 또는 다른 SIEM으로 전달
1️⃣ Wazuh를 통한 로그 수집 구성 (osquery 방식)
osquery 테이블 연동 쿼리
-- Wazuh Agent에서 실행될 쿼리 예시 (osquery schedule에 등록)
SELECT
log_id, action, customer_id, changed_by, changed_at
FROM
audit_log_customer
WHERE
changed_at >= datetime('now', '-5 minutes');
Wazuh schedule 등록 예시 (/var/osquery/osquery.conf
)
"schedule": {
"audit_log_customer": {
"query": "SELECT * FROM audit_log_customer WHERE changed_at >= datetime('now', '-5 minutes');",
"interval": 300
}
}
Wazuh rules 예시 (audit_log_customer
)
<group name="mysql_audit">
<rule id="110010" level="7">
<decoded_as>json</decoded_as>
<field name="action">UPDATE</field>
<description>MySQL DML update detected on customer table</description>
</rule>
</group>
감사 로그 무결성 검증 (해시체인, pgcrypto 등)
- 감사 로그에 대한 사후 변조 방지, 정당한 변경 추적 보장
방식 1: 해시 체인(hash chain) 적용
audit_log_customer 테이블 구조 추가
ALTER TABLE audit_log_customer ADD COLUMN hash CHAR(64);
ALTER TABLE audit_log_customer ADD COLUMN prev_hash CHAR(64);
트리거 내 해시 생성 (SHA2)
DELIMITER $$
CREATE TRIGGER trg_customer_update
AFTER UPDATE ON customer
FOR EACH ROW
BEGIN
DECLARE last_hash CHAR(64);
SELECT hash INTO last_hash FROM audit_log_customer ORDER BY log_id DESC LIMIT 1;
INSERT INTO audit_log_customer (
action, customer_id, old_name, new_name, changed_by, changed_at, prev_hash, hash
)
VALUES (
'UPDATE', OLD.customer_id, OLD.name, NEW.name, CURRENT_USER(), NOW(),
last_hash,
SHA2(CONCAT(NEW.name, NOW(), last_hash), 256)
);
END$$
DELIMITER ;
🔍 이를 통해 로그 간 블록처럼 연결된 해시 체인 구조 생성 → 변조 시 체인 무결성 깨짐
방식 2: PostgreSQL + pgcrypto 예시 (암호 서명용)
pgcrypto 설치
sudo apt install postgresql-contrib
서명용 로그 테이블 컬럼 추가
ALTER TABLE audit_log_customer ADD COLUMN signature BYTEA;
서명 적용 트리거 함수
CREATE OR REPLACE FUNCTION sign_audit_log() RETURNS trigger AS $$
BEGIN
NEW.signature := digest(NEW.changed_by || NEW.changed_at || NEW.action, 'sha256');
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER trg_sign BEFORE INSERT ON audit_log_customer
FOR EACH ROW EXECUTE FUNCTION sign_audit_log();
항목 | 내용 |
---|---|
✔️ 자동 트리거 생성 | SQL 생성기로 다수 테이블에 감사 트리거 일괄 설정 가능 |
✔️ Wazuh 연동 | osquery 또는 filebeat 통해 Wazuh 수집 후 룰 기반 탐지 가능 |
✔️ 무결성 보장 | 해시체인 또는 pgcrypto 기반 서명 적용으로 감사 로그 위변조 방지 가능 |
댓글