SQL Academy · 강의

접근 감사

누가 무엇을 볼 수 있는지 추적합니다.

레슨 4/413개 단계

접근 감사은(는) CoddyKit의 무료 SQL Academy 강의입니다. 이것은 4개 중 4번째 강의입니다. 아래에서 전체 강의를 무료로 읽을 수 있으며, 내장 코드 에디터와 24/7 AI 튜터와 함께 브라우저에서 직접 실습할 수 있습니다. 이 강의는 SQL Academy 학습 경로의 일부이며, 진행 상황이 웹과 CoddyKit 앱에 동기화됩니다. SQL Academy 강의에는 총 4개의 강의가 포함되어 있습니다.

access 감사를 하는 이유

누가 어떤 데이터에 언제 access했는지 아는 것은 데이터베이스 보안의 핵심입니다. 감사는 신뢰할 수 있는 이벤트 기록을 만들어 무단 access를 탐지하고, 사고를 조사하며, GDPR, HIPAA 또는 SOC 2와 같은 규정 준수 요구 사항을 충족할 수 있게 합니다.

이번 레슨에서는 감사 TABLE을 설계하고, 트리거로 access 이벤트를 자동으로 수집하며, PostgreSQL의 기본 제공 로그 기록 기능을 사용하고, 감사 기록을 조회해 누가 무엇을 볼 수 있는가?라는 질문에 답하는 방법을 배웁니다.

감사 로그 TABLE 설계

첫 단계는 주목할 만한 모든 이벤트를 기록하는 전용 TABLE을 만드는 것입니다. 좋은 감사 로그에는 TABLE 이름, 작업 유형, 이전 값과 새 값, 작업을 수행한 사용자, 정확한 타임스탬프가 저장됩니다.

아래 예에서는 행의 스냅샷을 저장하기 위해 JSONB 열을 사용하는 범용 audit_log TABLE을 만듭니다. 스키마를 변경하지 않고도 어떤 TABLE이든 처리할 수 있을 만큼 유연합니다.

CREATE TABLE audit_log (
  id          BIGSERIAL PRIMARY KEY,
  event_time  TIMESTAMPTZ NOT NULL DEFAULT now(),
  db_user     TEXT NOT NULL DEFAULT current_user,
  app_user    TEXT,
  table_name  TEXT NOT NULL,
  operation   TEXT NOT NULL CHECK (operation IN ('INSERT','UPDATE','DELETE','SELECT')),
  row_id      BIGINT,
  old_data    JSONB,
  new_data    JSONB
);

현재 사용자 기록

PostgreSQL은 쿼리를 실행하는 사용자를 식별할 수 있는 여러 기본 제공 함수를 제공합니다. current_user는 SET ROLE 이후 적용되는 ROLE 이름을 반환합니다. session_user는 ROLE 전환 여부와 관계없이 항상 원래 로그인 ROLE을 반환합니다.

하나의 공유 DB ROLE을 사용하면서 SET LOCAL app.current_user를 통해 애플리케이션 수준의 사용자를 전달하는 애플리케이션에서는 current_setting()으로 해당 설정을 읽을 수 있습니다.

-- Who is the database user right now?
SELECT current_user,
       session_user;

-- Read an application-level user injected by the app layer
SELECT current_setting('app.current_user', true) AS app_user;

감사 트리거 함수 작성

트리거 함수는 데이터 변경 이벤트를 수집하는 가장 신뢰할 수 있는 방법입니다. 자동으로 실행되므로 애플리케이션 코드가 이를 우회할 수 없습니다. 아래 함수는 연결된 모든 TABLE에서 발생하는 INSERT, UPDATE, DELETE를 기록하고, 이전 행 값과 새 행 값을 JSONB로 저장합니다.

TG_TABLE_NAME(트리거를 실행한 TABLE)과 행 값을 저장 가능한 형식으로 변환하는 row_to_json()의 사용에 주목하세요.

CREATE OR REPLACE FUNCTION fn_audit_changes()
RETURNS TRIGGER
LANGUAGE plpgsql
SECURITY DEFINER
AS $$
BEGIN
  INSERT INTO audit_log (
    db_user,
    app_user,
    table_name,
    operation,
    row_id,
    old_data,
    new_data
  ) VALUES (
    current_user,
    current_setting('app.current_user', true),
    TG_TABLE_NAME,
    TG_OP,
    COALESCE(NEW.id, OLD.id),
    CASE WHEN TG_OP = 'INSERT' THEN NULL ELSE row_to_json(OLD)::JSONB END,
    CASE WHEN TG_OP = 'DELETE' THEN NULL ELSE row_to_json(NEW)::JSONB END
  );
  RETURN NULL;
END;
$$;

TABLE에 트리거 연결하기

트리거 함수가 있으면 CREATE TRIGGER 문으로 감사하려는 각 TABLE에 함수를 연결합니다. AFTER를 사용하면 로그 항목이 생성되기 전에 데이터가 실제로 기록되었는지 확인할 수 있습니다. FOR EACH ROW 절은 수정된 각 행마다 트리거를 한 번씩 실행합니다.

여기서는 가상의 patients TABLE에 트리거를 적용해 모든 INSERT, UPDATE, DELETE를 자동으로 기록합니다.

CREATE TRIGGER trg_audit_patients
AFTER INSERT OR UPDATE OR DELETE
ON patients
FOR EACH ROW
EXECUTE FUNCTION fn_audit_changes();

SELECT 쿼리 감사

데이터 변경 트리거는 쓰기 작업만 수집합니다. 읽기 access를 감사하려면 다른 방법이 필요합니다. 한 가지 방법은 문 수준의 AFTER SELECT 트리거를 사용하는 것입니다(PostgreSQL 14 이상에서 특정 상황에 지원됨). 더 일반적인 패턴은 민감한 TABLE을 감싸는 함수나 뷰 안에서 읽기 작업을 명시적으로 기록하는 것입니다.

아래 예에서는 민감한 TABLE을 함수로 감싸고 결과를 반환하기 전에 모든 읽기 작업을 기록합니다.

CREATE OR REPLACE FUNCTION get_patient_record(p_id INT)
RETURNS SETOF patients
LANGUAGE plpgsql
SECURITY DEFINER
AS $$
BEGIN
  -- Log the read access
  INSERT INTO audit_log (db_user, app_user, table_name, operation, row_id)
  VALUES (
    current_user,
    current_setting('app.current_user', true),
    'patients',
    'SELECT',
    p_id
  );

  RETURN QUERY
  SELECT * FROM patients WHERE id = p_id;
END;
$$;

PostgreSQL 기본 제공 로그 기록

PostgreSQL의 postgresql.conf는 애플리케이션 코드 없이도 사용할 수 있는 강력한 서버 측 로그 기록 기능을 제공합니다. log_min_duration_statement를 설정하면 임계값을 초과하는 모든 쿼리가 기록됩니다. log_connections와 log_disconnections를 설정하면 누가 로그인하고 로그아웃하는지 기록됩니다.

아래 쿼리는 pg_stat_activity 시스템 뷰를 사용해 현재 활성 상태인 세션을 확인합니다. 이는 가벼운 실시간 access 감시 방법입니다.

-- See who is currently connected and what they are running
SELECT pid,
       usename        AS db_user,
       application_name,
       client_addr,
       state,
       query_start,
       LEFT(query, 80) AS current_query
FROM   pg_stat_activity
WHERE  datname = current_database()
ORDER  BY query_start DESC;

감사 로그 질의

감사 로그는 효과적으로 질의할 수 있을 때에만 가치가 있습니다. 일반적인 질문에는 어떤 사용자가 가장 최근에 레코드에 접근했는지, 시간에 따라 행에서 무엇이 변경되었는지, 지난 24시간 동안 민감한 데이터 조회가 몇 번 발생했는지가 포함됩니다.

아래 질의는 특정 환자 기록에 접근한 모든 사용자를 찾아 가장 최근 접근이 먼저 오도록 정렬합니다.

SELECT event_time,
       db_user,
       app_user,
       operation,
       old_data,
       new_data
FROM   audit_log
WHERE  table_name = 'patients'
  AND  row_id = 42
ORDER  BY event_time DESC
LIMIT  20;

의심스러운 접근 패턴 탐지

감사 데이터가 수집되면 이상 징후를 표시하는 질의를 작성할 수 있습니다. 예를 들어 사용자가 평소보다 갑자기 훨씬 많은 행을 조회하거나, 짧은 시간 동안 동일한 민감한 기록에 여러 번 접근하는 경우 데이터 탈취 시도를 나타낼 수 있습니다.

아래 질의는 지난 한 시간 동안 애플리케이션 사용자별 SELECT 작업 수를 세고, 100개가 넘는 행을 조회한 사용자를 표시합니다.

SELECT app_user,
       COUNT(*) AS records_accessed
FROM   audit_log
WHERE  operation = 'SELECT'
  AND  event_time >= now() - INTERVAL '1 hour'
GROUP  BY app_user
HAVING COUNT(*) > 100
ORDER  BY records_accessed DESC;

감사 로그 자체 보호

수정할 수 있는 감사 로그는 신뢰할 수 없습니다. 일반 사용자와 애플리케이션 역할이 행을 DELETE하거나 UPDATE할 수 없도록 감사 로그를 잠가야 합니다. 가장 안전한 방법은 애플리케이션 역할에는 INSERT만 허용하고 SELECT 권한은 전용 감사자 역할에만 부여하는 것입니다.

감사 행을 UPDATE하거나 DELETE하려는 시도가 있으면 예외를 발생시키는 트리거를 사용하여 변경 불가성도 적용할 수 있습니다.

-- Only the app role may insert; nobody may update or delete
REVOKE ALL     ON audit_log FROM PUBLIC;
GRANT  INSERT  ON audit_log TO app_role;
GRANT  SELECT  ON audit_log TO auditor_role;

-- Trigger to block any tampering
CREATE OR REPLACE FUNCTION fn_protect_audit()
RETURNS TRIGGER LANGUAGE plpgsql AS $$
BEGIN
  RAISE EXCEPTION 'audit_log rows are immutable';
  RETURN NULL;
END;
$$;

CREATE TRIGGER trg_protect_audit
BEFORE UPDATE OR DELETE ON audit_log
FOR EACH ROW EXECUTE FUNCTION fn_protect_audit();

RLS 정책 결정 감사

행 수준 보안이 활성화되면 PostgreSQL은 오류를 발생시키는 대신 행을 조용히 숨깁니다. 따라서 사용자가 볼 권한이 없는 행을 읽으려고 했는지 파악하기 어렵습니다. 한 가지 방법은 제한적인 정책이 행을 필터링하기 전에 항상 감사 레코드를 INSERT하는 허용 정책을 추가하는 것입니다.

아래 질의는 테이블에 어떤 RLS 정책이 존재하며 해당 정책이 어떤 역할에 적용되는지 확인하는 방법을 보여 줍니다.

-- View all RLS policies on the patients table
SELECT polname       AS policy_name,
       polcmd        AS command,
       polroles::TEXT AS applies_to,
       polqual::TEXT  AS using_expression,
       polwithcheck::TEXT AS with_check_expression
FROM   pg_policy
WHERE  polrelid = 'patients'::REGCLASS
ORDER  BY polname;

지식 확인

SQL에서 접근 감사에 대한 이해도를 확인해 보세요.

수업 요약

이 수업에서는 PostgreSQL에서 완전한 접근 감사 시스템을 구축하는 방법을 살펴보았습니다.

  • 감사 로그 테이블 — 누가 무엇을 언제 했는지 기록하는 JSONB 기반 테이블입니다.
  • 트리거 함수 — TG_TABLE_NAME, row_to_json(), current_user를 사용하여 연결된 모든 테이블의 INSERT, UPDATE, DELETE 작업을 자동으로 기록합니다.
  • 조회 감사 — 민감한 테이블을 데이터를 반환하기 전에 SELECT 작업을 기록하는 함수로 감쌉니다.
  • 내장 모니터링 — pg_stat_activity는 현재 세션을 보여 주고, 서버 측 기록 기능은 코드 변경 없이 질의를 기록합니다.
  • 이상 징후 탐지 — 감사 로그에 대한 집계 질의로 비정상적인 접근량을 표시할 수 있습니다.
  • 변경 불가성 — 감사 테이블에서 UPDATE/DELETE를 취소하고 변경을 차단하는 트리거를 추가하여 변조를 방지합니다.

잘 설계된 감사 추적은 누가 무엇에 접근했는지를 확인하는 가장 신뢰할 수 있는 도구이며, 모든 규정 준수 또는 보안 조사의 기반이 됩니다.

무료로 시작

AI 튜터와 함께 SQL을(를) 배우세요 — 무료

브라우저에서 실제 코드를 작성하고 실행하며, 24/7 AI 튜터로부터 즉각적인 도움을 받고, 웹이나 앱에서 중단한 부분부터 계속 학습하세요.

코스
46
레슨
183

자주 묻는 질문

“접근 감사” 강의는 무료인가요?

네 — “접근 감사” 전체 내용을 이 웹사이트에서 무료로 읽을 수 있습니다. 인터랙티브하게 실습하려면(내장 코드 에디터와 24/7 AI 튜터), CoddyKit PRO로 업그레이드하면 SQL Academy 강의 전체를 잠금 해제할 수 있습니다. SQL Academy 강의에는 총 4개의 강의가 포함되어 있습니다.

“접근 감사”에서 뭘 배우나요?

누가 무엇을 볼 수 있는지 추적합니다. 브라우저에서 직접 실행하는 실습 코드로 SQL Academy을(를) 배우며, 24/7 AI 튜터가 강의를 진행하면서 질문에 답변해줍니다.

SQL Academy을(를) 시작하는 데 경험이 필요한가요?

사전 경험은 필요하지 않습니다. CoddyKit의 SQL Academy은(는) 초급자부터 고급 학습자까지를 위해 구성되어 있으므로, 여기서 시작하거나 처음부터 시작할 수 있으며 자신의 속도대로 진행할 수 있습니다. 이것은 4개 중 4번째 강의입니다.

“접근 감사” 강의는 얼마나 걸리나요?

대부분의 CoddyKit 강의는 약 5~10분이 소요됩니다. 각 강의는 간결하고 인터랙티브하여 꾸준한 진행이 가능하며, 웹과 앱에서 중단한 부분부터 바로 시작할 수 있습니다.

이 SQL Academy 강의에서 코드를 작성하고 실행할 수 있나요?

네. 모든 SQL Academy 강의에는 내장 코드 에디터가 포함되어 있으므로, 브라우저에서 바로 실제 코드를 작성하고 실행한 후 즉시 AI 피드백을 받을 수 있습니다 — 로컬 설정이 필요 없습니다.

이 강의의 모든 강의

  1. 역할과 권한
  2. 행 수준 보안 정책
  3. 열 수준 권한
  4. 접근 감사
← SQL Academy(으)로 돌아가기