문서 보안 요구사항을 만족하는 시스템 설계 — 접근통제·워터마크·감사로그

"내부 자료가 외부로 나가지 않게 해주세요."

보안 관련 요구사항은 대체로 이런 형태로 들어옵니다. 방향은 분명하지만, 이것만으로는 무엇을 만들어야 할지 정해지지 않습니다.

이 요구를 그대로 받아 "유출을 완벽히 차단하는 시스템"을 목표로 잡으면 프로젝트가 어려워집니다. 화면에 표시된 내용을 다른 기기로 촬영하는 것까지 막을 수는 없기 때문입니다. 이 사실을 인정하지 않고 시작하면, 검수 단계에서 "완벽히 막힌다고 하지 않았느냐"는 논쟁이 생깁니다.

현실적인 목표는 다릅니다. 유출 경로를 줄이고, 남은 경로의 비용을 높이고, 발생했을 때 추적할 수 있게 하는 것입니다. 이 글은 그 관점에서 문서 보안 요구사항을 시스템 설계로 옮기는 방법을 정리한 내용입니다.


1. 무엇을 막을 것인지 경로별로 나눕니다

"유출 방지"는 하나의 기능이 아니라 여러 통제 수단의 조합입니다. 경로를 나눠야 설계가 가능해집니다.

경로마다 다른 수단이 필요합니다

유출 경로 통제 수단 완전성
권한 없는 사용자의 조회 인증·인가, 접근통제 높음
파일 다운로드 후 반출 다운로드 제한, 열람 전용 뷰어 중간
인쇄물 반출 인쇄 통제, 워터마크 중간
화면 캡처 캡처 차단(제한적), 워터마크 낮음
다른 기기로 촬영 워터마크 차단 불가, 추적만 가능
내용 필사·암기 없음 통제 불가

표에서 아래로 내려갈수록 차단이 어려워집니다. 설계의 핵심은 위쪽 경로를 확실히 막고, 아래쪽은 추적 수단을 남기는 것입니다.

완전성을 요구사항에 명시합니다

이 표를 발주 측과 함께 검토하고, 각 경로에 대해 무엇을 어디까지 할 것인지 합의해두는 것이 좋습니다. 이 합의가 나중에 검수 기준이 됩니다.

특히 "화면 캡처 차단"은 주의가 필요합니다. 운영체제나 브라우저 환경에 따라 가능 여부가 다르고, 우회 방법도 존재합니다. 가능한 범위를 명확히 적어두지 않으면 기대와 결과가 어긋납니다.

합의한 내용은 시험할 수 있는 문장으로 옮겨 적습니다. 아래는 표현을 바꾸는 예시입니다.

받은 요구 검수할 수 있는 문장
권한 없는 사람은 못 보게 권한 없는 계정으로 목록·상세·파일·썸네일·검색 결과를 요청하면 모두 거부되고, 거부 기록이 남는다
다운로드를 막아달라 기밀 등급 문서는 원본 파일을 내려받는 응답이 어떤 경로에도 없다. 열람은 서버가 변환한 이미지로만 제공한다
캡처를 막아달라 전용 뷰어(지정 운영체제)에서는 운영체제가 제공하는 캡처 제외 기능을 적용한다. 웹 브라우저와 외부 기기 촬영은 차단 대상이 아니며 워터마크로 추적한다
유출되면 찾을 수 있게 화면·인쇄물의 식별 코드로 열람자, 일시, 문서를 조회할 수 있다. 코드가 일부만 보이는 경우의 처리 범위를 별도로 정한다

대상 문서의 등급을 나눕니다

모든 문서에 같은 통제를 적용하면 업무가 마비됩니다. 등급을 나누고 등급별로 적용 수준을 정하는 편이 현실적입니다.

  • 공개 — 통제 없음
  • 내부 — 로그인한 직원만 조회
  • 제한 — 지정된 부서·직위만 조회, 다운로드 제한
  • 기밀 — 개별 승인 필요, 열람 전용, 워터마크, 전체 이력 기록

등급 부여 주체와 절차도 정해야 합니다. 작성자가 지정하는 방식, 부서별 기본 등급이 적용되는 방식, 특정 위치에 저장하면 자동 적용되는 방식이 있습니다.

등급 변경 이력도 남겨야 합니다. 기밀 문서의 등급을 내려서 반출하는 경로가 생기지 않도록 승인 절차를 두는 경우가 많습니다.


2. 권한 모델을 설계합니다

접근통제는 가장 확실하게 막을 수 있는 영역입니다. 여기서 빈틈이 생기면 나머지 통제는 의미가 없습니다.

역할 기반과 문서 기반을 조합합니다

방식 동작 장단점
역할 기반 직위·부서에 따라 권한 부여 관리가 단순. 예외 처리가 어려움
문서 기반 문서마다 열람자를 지정 정밀함. 관리 부담이 큼
혼합 기본은 역할, 필요 시 개별 지정 대부분의 경우 현실적

혼합 방식에서 정해야 할 것은 개별 지정이 역할 권한을 넘어설 수 있는가입니다. 특정 문서에 한해 평소 권한이 없는 사람에게 열람을 허용할지, 반대로 권한이 있어도 특정 문서는 제외할지를 결정해야 합니다.

표의 구분은 접근통제 모델로 보면 역할 기반(RBAC)과 속성 기반(ABAC)의 차이입니다. NIST는 ABAC를 주체, 객체, 요청된 동작, 그리고 경우에 따라 환경 조건의 속성을 정책에 비추어 평가해 허용 여부를 정하는 방식으로 정의하고, RBAC는 그중 "역할"이라는 속성 하나를 쓰는 특수한 경우로 설명합니다.(NIST SP 800-162)

문서 보안 요구사항은 대부분 역할만으로 표현되지 않습니다. "기밀 등급은 사내망에서만", "프로젝트 기간에만", "최근에 재인증한 경우에만" 같은 조건은 문서 등급(객체 속성)과 접속 위치·시각(환경 조건)을 함께 봐야 판정할 수 있습니다. 그래서 역할로 큰 범위를 정하고, 등급과 환경 조건으로 좁히는 구조가 됩니다. 처음부터 범용 정책 엔진을 도입할 필요는 없지만, 판정에 쓰는 속성이 무엇인지는 목록으로 확정해 두어야 합니다. 속성이 어느 시스템에서 오는지(인사 정보, 문서 메타데이터, 접속 정보)와 그 값이 얼마나 최신인지가 판정의 정확도를 결정합니다.

판정 순서를 한 곳에 고정합니다

혼합 방식의 결함은 대개 규칙이 충돌할 때 드러납니다. 역할로는 허용인데 문서 단위로 제외된 경우, 개별 허용이 있는데 만료된 경우에 어느 쪽이 이기는지가 화면마다 다르게 구현되어 있으면 빈틈이 생깁니다. 순서를 정해 함수 하나에 모으고, 모든 접근 경로가 이 함수를 거치게 합니다.

아래는 판정 순서를 보여주는 예시입니다(TypeScript, 재인증 유효 시간 5분은 예시 값). 속성 조회는 호출하는 쪽에서 끝내고, 이 함수는 판정만 합니다.

type Grade = 'PUBLIC' | 'INTERNAL' | 'RESTRICTED' | 'CONFIDENTIAL';
type Action = 'VIEW' | 'DOWNLOAD' | 'PRINT';
type DenyReason =
  | 'ACCOUNT_INACTIVE' | 'EXPLICIT_DENY' | 'NO_MATCHING_RULE'
  | 'ACTION_NOT_ALLOWED_FOR_GRADE' | 'REAUTH_REQUIRED';
type Decision = { allowed: true } | { allowed: false; reason: DenyReason };

interface AccessRequest {
  isAccountActive: boolean;
  action: Action;
  grade: Grade;
  isExplicitlyDenied: boolean;             // 문서 단위 제외 지정
  individualGrantExpiresAt: number | null; // 개별 허용의 만료 시각(epoch 초)
  isAllowedByRole: boolean;                // 역할·부서 규칙 일치 여부
  lastAuthenticatedAt: number;             // 마지막 인증 시각(epoch 초)
  now: number;
}

const REAUTH_WINDOW_SECONDS = 300;

function deny(reason: DenyReason): Decision {
  return { allowed: false, reason };
}

export function decideAccess(request: AccessRequest): Decision {
  if (!request.isAccountActive) return deny('ACCOUNT_INACTIVE');
  if (request.isExplicitlyDenied) return deny('EXPLICIT_DENY');

  const hasValidGrant =
    request.individualGrantExpiresAt !== null &&
    request.individualGrantExpiresAt > request.now;
  const isPublic = request.grade === 'PUBLIC';
  if (!isPublic && !request.isAllowedByRole && !hasValidGrant) {
    return deny('NO_MATCHING_RULE');
  }

  if (request.grade !== 'CONFIDENTIAL') return { allowed: true };
  if (request.action !== 'VIEW') return deny('ACTION_NOT_ALLOWED_FOR_GRADE');
  const isRecentlyAuthenticated =
    request.now - request.lastAuthenticatedAt <= REAUTH_WINDOW_SECONDS;
  return isRecentlyAuthenticated ? { allowed: true } : deny('REAUTH_REQUIRED');
}

이 구조에서 지켜야 할 것은 네 가지입니다.

  • 일치하는 규칙이 없으면 거부합니다. 새 등급이나 새 동작이 추가됐을 때 규칙을 빠뜨려도 열리지 않습니다.
  • 제외 지정이 허용보다 먼저입니다. 순서를 반대로 두면 개별 허용 하나로 제외 지정이 무력화됩니다.
  • 만료는 판정 시점에 비교합니다. 만료된 권한을 배치 작업으로 지우는 방식에만 의존하면, 배치가 멈춘 동안 권한이 살아 있습니다.
  • 거부 사유를 값으로 돌려줍니다. 이 값이 감사로그의 실패 사유가 되고, 사용자에게 무엇 때문에 막혔는지 안내하는 근거가 됩니다. 다만 권한 없는 사용자에게 문서의 존재 여부까지 알려주지 않도록, 화면에 보여줄 문구는 사유 값과 분리해 정합니다.

권한의 생애주기를 관리합니다

권한은 부여하는 것보다 회수하는 것이 자주 빠집니다.

  • 부서 이동 시 이전 부서 문서 권한이 유지되는가
  • 퇴사 시 즉시 차단되는가
  • 프로젝트 종료 후 임시 권한이 만료되는가
  • 외부 협력사 계정의 유효 기간이 있는가

임시 권한에는 만료일을 필수로 두는 것이 안전합니다. 만료 없이 부여된 권한은 아무도 회수하지 않아 계속 쌓입니다.

정기적으로 권한 현황을 점검하는 절차도 필요합니다. 현재 누가 어떤 문서에 접근할 수 있는지를 목록으로 뽑아 관리자가 검토하는 방식입니다.

문서 시스템이 개인정보를 담고 있어 개인정보처리시스템에 해당한다면 이 부분은 권고가 아니라 의무입니다. 「개인정보의 안전성 확보조치 기준」 제5조는 접근 권한을 업무 수행에 필요한 최소한의 범위로 차등 부여하고, 담당자나 업무가 바뀌면 지체 없이 권한을 변경·말소하며, 권한의 부여·변경·말소 내역을 기록해 최소 3년간 보관하도록 정하고 있습니다.(개인정보의 안전성 확보조치 기준) 권한 변경 이력 테이블을 접속기록과 따로 두고 보관 기간도 따로 잡아야 하는 이유입니다.

회수가 늦어지는 기술적 원인도 하나 짚어둡니다. 권한 판정 결과나 사용자 속성을 세션·토큰·캐시에 담아두면, 인사 시스템에서 퇴사 처리가 끝난 뒤에도 캐시가 만료될 때까지 접근이 유지됩니다. 기밀 등급 판정은 캐시를 쓰지 않거나, 권한 변경 시 해당 사용자의 세션과 캐시를 즉시 무효화하는 경로를 만들어 둡니다.

인증 수단을 등급에 맞춥니다

기밀 등급 문서에는 추가 인증을 요구하는 구성이 일반적입니다.

  • 조회 시점에 다시 한 번 인증
  • 다단계 인증 적용
  • 접속 위치·기기 제한
  • 업무 시간 외 접근 제한

제한을 걸 때는 예외 처리 경로를 함께 만들어야 합니다. 출장 중이거나 긴급 상황에서 접근이 필요한 경우가 반드시 생깁니다. 예외 승인 절차와 그 기록이 있으면 통제를 유지하면서 업무도 가능합니다.

다운로드와 열람을 분리합니다

파일을 내려받게 하는 순간, 그 파일에 대한 통제권을 잃습니다. 사용자의 기기에 저장된 파일은 어디로든 옮길 수 있습니다.

통제를 유지하려면 다운로드 없이 볼 수 있는 경로가 필요합니다.

방식 동작 유의점
서버 렌더링 뷰어 서버에서 이미지로 변환해 표시 원본이 내려가지 않음. 변환 부하
웹 뷰어 브라우저에서 표시하되 원본 접근 차단 구현에 따라 원본이 노출될 수 있음
전용 애플리케이션 지정 프로그램으로만 열람 설치 부담. 통제 수단은 많음

웹 뷰어를 쓸 때 주의할 점이 있습니다. 브라우저에서 문서를 표시하려면 어떤 형태로든 데이터가 클라이언트로 전달됩니다. 개발자 도구나 네트워크 기록으로 원본을 추출할 수 있는 구조라면 통제가 되지 않습니다.

서버에서 이미지로 변환해 내려주는 방식이 이 문제에서 자유롭습니다. 텍스트 선택이나 검색이 불가능해지는 단점이 있지만, 기밀 등급 문서에는 적절한 절충입니다.

다운로드를 허용해야 하는 경우에는 횟수 제한, 유효 기간, 사유 입력 같은 조건을 붙이는 방식이 쓰입니다. 완전한 통제는 아니지만 무분별한 반출은 줄어듭니다.

서버에서 검증합니다

당연한 이야기지만 실제로 자주 어긋나는 부분입니다.

  • 화면에서 버튼을 숨기는 것은 통제가 아닙니다
  • 목록 조회에서는 걸러지는데 상세 조회는 식별자만 있으면 되는 구조가 흔합니다
  • 파일 다운로드 경로가 별도로 열려 있는 경우가 있습니다
  • 미리보기 이미지나 썸네일이 권한 검사를 거치지 않는 경우가 있습니다

모든 접근 경로에서 동일한 권한 검사가 이루어져야 합니다. 특히 파일 자체의 URL이 노출되면 권한 검사를 우회할 수 있으므로, 직접 접근 가능한 경로를 두지 않는 설계가 필요합니다.

파일 URL은 서명하고 만료시킵니다

파일을 애플리케이션 서버가 직접 흘려보내면 권한 검사는 단순하지만, 용량이 큰 파일에서는 서버 부담이 큽니다. 그래서 저장소나 파일 전용 경로가 직접 응답하게 하고, 애플리케이션은 권한을 확인한 뒤 짧은 시간만 유효한 서명 URL을 발급하는 구성을 많이 씁니다.

원리는 문서, 사용자, 만료 시각을 묶어 서버만 아는 키로 서명하고, 요청이 들어오면 서명과 만료를 다시 확인하는 것입니다. 아래는 직접 구현하는 경우의 예시입니다(Node.js 16 이상, node:crypto).

import { createHmac, timingSafeEqual } from 'node:crypto';

interface DownloadClaim {
  documentId: string;
  userId: string;
  expiresAt: number; // epoch 초
}

function sign(claim: DownloadClaim, secret: string): string {
  const payload = JSON.stringify([claim.documentId, claim.userId, claim.expiresAt]);
  return createHmac('sha256', secret).update(payload).digest('base64url');
}

export function createDownloadPath(claim: DownloadClaim, secret: string): string {
  const query = new URLSearchParams({
    u: claim.userId,
    e: String(claim.expiresAt),
    s: sign(claim, secret),
  });
  return `/api/documents/${encodeURIComponent(claim.documentId)}/file?${query}`;
}

interface VerifyInput {
  claim: DownloadClaim; // 요청 경로와 쿼리에서 다시 읽은 값
  signature: string;
  secret: string;
  now: number;
}

export function isValidDownloadRequest(input: VerifyInput): boolean {
  const { claim, signature, secret, now } = input;
  if (!Number.isInteger(claim.expiresAt) || claim.expiresAt <= now) return false;
  const expected = Buffer.from(sign(claim, secret));
  const given = Buffer.from(signature);
  return expected.length === given.length && timingSafeEqual(expected, given);
}

클라우드 저장소의 서명 URL 기능을 쓰는 경우에도 성격은 같습니다. AWS는 S3의 미리 서명된 URL을 "소지한 사람에게 접근을 허용하는 bearer token"이라고 설명하며, 만료 전까지 여러 번 사용할 수 있다고 안내합니다.(Amazon S3 사용 설명서) 서명 URL은 권한 검사를 대신하는 것이 아니라 권한 검사 결과를 짧은 시간 동안 들고 다니는 증표입니다. 그래서 다음을 함께 설계합니다.

  • 만료는 분 단위로 짧게 잡습니다. URL은 메신저에 붙여넣는 순간 다른 사람에게 넘어갑니다. 만료가 길수록 권한 회수가 늦게 반영됩니다. S3의 경우 서명 후 일정 시간이 지난 요청을 버킷 정책(s3:signatureAge 조건)으로 거부하거나 접근 가능한 네트워크를 제한하는 방법이 같은 문서에 안내되어 있습니다.
  • 만료는 요청 시작 시점에만 검사됩니다. 같은 문서에 따르면 S3는 HTTP 요청 시점에 만료를 확인하므로, 만료 직전에 시작된 다운로드는 만료 후에도 이어집니다. 만료 시각을 "그 이후에는 파일이 전송되지 않는 시각"으로 요구사항에 적으면 안 됩니다.
  • 서명에 사용자를 포함하고, 세션이 있는 경로라면 세션 사용자와 대조합니다. 저장소가 직접 응답하는 구성에서는 대조가 불가능하므로 만료를 더 짧게 잡습니다.
  • 발급 시점에 감사로그를 남깁니다. 저장소가 직접 응답하면 애플리케이션은 실제 다운로드를 알 수 없습니다. 발급 기록과 저장소 접근 로그를 문서 식별자로 연결할 수 있게 해둡니다.
  • 기밀 등급에는 쓰지 않습니다. 원본이 내려가는 경로 자체를 두지 않는 등급에는 서명 URL도 발급하지 않습니다.
  • 서명 키를 교체할 수 있게 만듭니다. 키 식별자를 URL에 포함해 두면 키를 바꾸는 동안 이전 키로 발급된 URL을 만료 시각까지 검증할 수 있습니다.

권한 설계에서 자주 생기는 오류

증상 원인 해결
목록에는 없는 문서가 주소를 직접 입력하면 열림 목록 쿼리에만 권한 조건이 있고 상세·파일 API는 식별자만 확인 판정 함수를 공통 계층에 두고 문서를 반환하는 모든 API가 거치게 함. 권한 없는 계정으로 각 API를 호출하는 자동 시험을 배포 조건에 포함
본문은 막혔는데 검색 결과에 제목과 본문 일부가 보임 검색 색인이 권한 정보 없이 만들어짐 색인에 등급·권한 속성을 함께 넣어 검색 단계에서 걸러내고, 결과를 반환하기 전에 다시 판정
썸네일·미리보기 이미지가 로그인 없이 열림 변환 결과물을 공개 경로나 CDN에 저장 변환 결과물도 원본과 같은 등급으로 취급하고 같은 판정을 거쳐 제공
퇴사자 계정으로 한동안 열람이 됨 권한·속성을 세션이나 캐시에 보관하고 무효화 경로가 없음 권한 변경 시 세션·캐시 즉시 무효화, 상위 등급은 매 요청 판정
뒤로 가기나 공용 PC에서 이전 사용자의 문서가 보임 문서 응답이 브라우저나 중간 서버에 캐시됨 문서·이미지 응답에 Cache-Control: no-store 적용
임시 권한이 만료일 이후에도 동작 만료 처리를 삭제 배치에만 의존 판정 시점에 만료 시각을 비교
관리자 화면에서는 모든 문서가 열림 관리 기능이 판정 함수를 거치지 않음 시스템 관리 권한과 문서 열람 권한을 분리하고, 관리자 열람도 같은 판정과 기록을 거치게 함

3. 워터마크는 차단이 아니라 추적 수단입니다

워터마크에 대한 기대가 실제 효과와 어긋나는 경우가 많습니다.

무엇을 기대할 수 있는가

워터마크는 화면이나 인쇄물에 열람자 정보를 겹쳐 표시하는 방식입니다. 기대할 수 있는 효과는 두 가지입니다.

  1. 억제 효과 — 자기 이름이 찍힌 문서를 유출하기는 부담스럽습니다
  2. 추적 가능성 — 유출된 자료가 발견되면 누가 열람한 것인지 확인할 수 있습니다

차단 효과는 없습니다. 워터마크가 있어도 촬영이나 캡처는 가능합니다. 이 점을 요구사항 단계에서 분명히 해두어야 합니다.

표시 내용을 정합니다

워터마크에 무엇을 넣을지에 따라 추적 정확도가 달라집니다.

  • 열람자 이름과 소속
  • 사번 또는 계정 식별자
  • 열람 일시
  • 접속 IP 또는 기기 정보
  • 문서 식별자

정보를 많이 넣을수록 추적은 쉬워지지만 가독성이 떨어집니다. 실무에서는 화면에는 최소한으로 표시하고, 상세 정보는 서버 기록으로 남기는 방식을 씁니다. 화면에 짧은 식별 코드만 표시하고, 그 코드로 열람 기록을 조회하는 구성입니다.

식별 코드를 쓸 때 정할 것은 세 가지입니다.

  • 코드의 단위 — 열람 한 건마다 새 코드를 발급하면 "누가, 언제, 어떤 문서를"까지 한 번에 좁혀집니다. 사용자별 고정 코드는 구현이 쉽지만 일시와 문서는 다른 단서로 찾아야 합니다.
  • 코드의 생성 방식 — 사번이나 계정명을 그대로 변형한 값보다 서버가 발급한 임의 값을 쓰는 편이 낫습니다. 유출된 자료에 개인정보가 직접 실리지 않고, 코드만 보고 다른 사람의 코드를 추정할 수도 없습니다.
  • 코드와 기록의 보관 기간 — 코드로 조회할 열람 기록이 먼저 삭제되면 워터마크는 읽을 수 없는 표시가 됩니다. 워터마크 추적이 가능해야 하는 기간과 감사로그 보관 기간을 맞춥니다.

표시 방식을 정합니다

  • 위치 — 고정인지, 일정 시간마다 이동하는지. 이동하면 특정 부분을 가리는 캡처를 어렵게 만듭니다
  • 투명도 — 문서 내용을 읽을 수 있으면서 촬영 시 식별 가능한 수준
  • 밀도 — 화면 전체에 반복 배치할지, 일부에만 표시할지
  • 인쇄 시 적용 — 화면과 인쇄물에 각각 적용할지

인쇄물 워터마크는 별도로 설계해야 합니다. 화면 워터마크는 브라우저에서 처리하지만, 인쇄는 출력 단계에서 처리되므로 다른 방식이 필요합니다. 인쇄 시 서버에서 워터마크를 넣은 파일을 생성해 내려주는 방식이 확실합니다. 농도도 화면과 따로 잡아야 합니다. 저희가 구축한 워터마크 DRM 솔루션에서도 진하면 문서를 읽기 어렵고 옅으면 복사 과정에서 사라지는 문제가 있어, 농도와 배치를 조정해 균형을 맞추는 작업이 필요했습니다.

화면 워터마크를 어디에서 합성하는지도 정해야 합니다. 브라우저가 문서 위에 별도 요소로 겹쳐 그리는 방식은 구현이 간단하지만, 워터마크와 문서가 사용자 기기 안에서 분리된 채로 존재하므로 표시를 끄는 것이 어렵지 않습니다. 상위 등급 문서는 서버가 페이지를 이미지로 변환할 때 워터마크를 함께 그려 넣어, 워터마크 없는 화면 데이터가 클라이언트에 전달되지 않게 합니다. 이 경우 열람자마다 이미지가 달라지므로 변환 결과를 사용자 간에 공유 캐시할 수 없고, 그만큼 변환 부하를 용량 산정에 반영해야 합니다.

워터마크가 버티지 못하는 경우

워터마크는 유출된 자료에 남아 있고 읽을 수 있을 때만 추적 수단이 됩니다. 설계 단계에서 다음 상황을 미리 정리해 둡니다.

상황 결과 설계에서 할 수 있는 것
화면 일부만 잘라낸 캡처·촬영 워터마크가 없는 영역만 유출될 수 있음 화면 전체에 반복 배치하고, 한 화면 어디를 잘라도 코드 전체가 한 번 이상 들어가는 간격으로 설계
저해상도 촬영, 축소, 재압축 옅은 워터마크가 뭉개져 읽을 수 없음 실제 촬영·축소 조건으로 판독 시험을 하고 그 결과로 농도와 글자 크기를 정함
내용을 다시 타이핑하거나 요약해 전달 워터마크가 따라가지 않음 기술적 수단 없음. 열람 기록으로 범위를 좁히는 것까지가 한계
같은 문서를 여러 사람이 열람 워터마크가 없는 유출물은 열람자 전원이 후보 열람 대상을 좁히는 것이 추적 범위를 좁히는 방법

두 번째 항목은 검수 항목으로 넣어둘 만합니다. "워터마크가 표시된다"는 확인만 하고 넘어가면, 실제로 촬영된 사진에서 코드를 읽을 수 있는지는 사고가 난 뒤에야 알게 됩니다. 대상 모니터와 일반적인 스마트폰으로 촬영한 사진에서 코드를 판독하는 시험을 한 번 해보는 것으로 충분합니다.

문서 자체에 표시를 넣는 방법도 있습니다

시각적 워터마크와 별개로, 문서 파일 자체에 식별 정보를 삽입하는 방식이 있습니다. 육안으로는 보이지 않지만 분석하면 확인할 수 있는 형태입니다.

이 방식을 적용할 때 검토할 사항입니다.

  • 파일 형식별로 적용 가능한가
  • 편집·재저장 시 유지되는가
  • 어떤 도구로 확인할 수 있는가
  • 개인정보가 파일에 남는 것에 대한 검토가 되었는가

마지막 항목이 중요합니다. 식별 정보가 파일에 남는다는 것은 그 파일이 외부로 나갔을 때 개인정보도 함께 나간다는 뜻입니다. 어떤 정보를 넣을지 신중히 결정해야 합니다. 여기서도 이름이나 사번 대신 서버가 발급한 식별 코드를 넣는 방식이 안전합니다.

보이지 않는 표시는 보이는 워터마크를 대체하지 못합니다. 역할이 다르기 때문입니다.

구분 보이는 워터마크 보이지 않는 표시
억제 효과 있음. 열람자가 표시를 인지함 없음. 존재를 알리지 않으면 억제되지 않음
확인 방법 육안 전용 검출 도구와 원본 또는 키
약한 지점 잘라내기, 가리기 형식 변환, 재저장, 촬영을 거친 뒤의 잔존 여부가 방식마다 다름
적합한 용도 화면 열람, 인쇄물 파일로 반출되는 문서의 출처 확인

보이지 않는 표시가 변환·인쇄·촬영을 거친 뒤에도 검출되는지는 방식과 제품에 따라 차이가 크고, 공급사가 제시하는 조건과 실제 사용 조건이 다를 수 있습니다. 도입을 검토한다면 대상 파일 형식과 예상 유출 경로를 정해 직접 검출 시험을 하고, 검출되지 않는 경로를 요구사항 문서의 한계 항목에 적어두어야 합니다. 검출 결과를 징계나 법적 절차의 근거로 쓸 계획이라면, 오검출 가능성과 검출 절차의 기록 방법도 함께 검토 대상입니다.


4. 감사로그를 설계합니다

유출이 발생했을 때 확인할 수 있는 기록이 없으면, 앞서의 통제 수단들이 의미를 잃습니다.

법령이 요구하는 수준을 먼저 확인합니다

개인정보를 다루는 시스템이라면 접속기록 관련 요건이 적용됩니다. 현행 「개인정보의 안전성 확보조치 기준」(개인정보보호위원회고시 제2025-9호, 2025. 10. 31. 일부개정) 제8조의 내용은 다음과 같습니다.(개인정보의 안전성 확보조치 기준)

조항 내용
제8조제1항 개인정보처리시스템에 접속한 자(정보주체 제외)의 접속기록을 1년 이상 보관·관리
제8조제1항 단서 다음 중 하나에 해당하면 2년 이상: 5만명 이상의 정보주체에 관한 개인정보를 처리하는 시스템, 고유식별정보 또는 민감정보를 처리하는 시스템, 기간통신사업자
제8조제2항 접속기록과 개인정보 다운로드 상황을 확인·점검하는 주기, 방법, 사후조치 절차를 내부 관리계획으로 정하고 이행
제8조제3항 접속기록이 위·변조되거나 도난, 분실되지 않도록 안전하게 보관하기 위한 조치

같은 고시 제2조는 접속기록을 식별자, 접속일시, 접속지 정보, 처리한 정보주체 정보, 수행업무 등을 전자적으로 기록한 것으로 정의합니다. 문서 시스템에 맞추면 누가, 언제, 어디서, 누구의 정보가 담긴 문서를, 조회했는지 내려받았는지까지가 기록 대상입니다.

시행 시점에 유의해야 합니다. 제2025-9호 부칙은 제8조제1항·제2항과 제5조제1항·제6항 등의 개정규정을 발령일의 1년 후, 즉 2026년 10월 31일부터 시행하도록 정하고 있습니다. 그 전까지 적용되는 종전 규정(제2023-6호)과 달라지는 점은 두 가지입니다.

  • 보관 대상 — 종전에는 "개인정보취급자"의 접속기록이었으나, 개정규정은 "개인정보처리시스템에 접속한 자(정보주체 제외)"로 넓혔습니다. 협력사 계정처럼 내부 직원이 아닌 접속자의 기록이 빠져 있지 않은지 확인이 필요합니다.
  • 점검 주기 — 종전에는 접속기록을 월 1회 이상 점검하도록 했으나, 개정규정은 주기와 방법을 내부 관리계획으로 정하도록 바꿨습니다. 시스템에는 "월 1회 점검 보고서" 같은 고정 기능보다, 정해진 주기에 맞춰 점검 대상을 뽑고 점검 결과와 사후조치를 남길 수 있는 기능이 필요합니다.

보관 기간은 하한입니다. 한편 열람 기록 자체도 임직원의 개인정보이므로 근거 없이 무기한 쌓아두는 것도 맞지 않습니다. 접속기록(1년 또는 2년 이상)과 권한 변경 내역(제5조제3항, 최소 3년)의 보관 기간을 각각 정하고, 기간이 지난 기록을 삭제하는 절차까지 함께 설계합니다.

조문 해석과 적용 사례는 개인정보보호위원회가 발간한 안내서를 참고할 수 있습니다. 다만 아래 링크의 안내서는 2024년 10월판으로 제2025-9호 개정 이전 내용을 기준으로 하므로, 개정된 조항은 고시 원문을 우선해 확인해야 합니다.(개인정보의 안전성 확보조치 기준 안내서)

적용 대상과 구체적 요건은 처리하는 개인정보의 유형과 규모에 따라 달라집니다. 문서 시스템이 개인정보처리시스템에 해당하는지부터 판단이 필요하며, 실제 시스템에 적용할 때는 법령 원문과 소관 기관의 최신 안내를 확인하시기 바랍니다.

기록할 항목을 정합니다

문서 보안 관점에서 남겨야 할 항목들입니다.

구분 항목
주체 계정 식별자, 소속, 접속 IP, 기기 정보
대상 문서 식별자, 문서 등급, 문서명
행위 조회, 다운로드, 인쇄, 공유, 권한 변경, 등급 변경
시각 요청 시각, 처리 완료 시각
결과 성공·실패, 실패 사유

실패 기록이 특히 중요합니다. 권한 없는 접근 시도가 반복되는 것은 탐지해야 할 신호입니다. 성공한 것만 기록하면 이 신호를 놓칩니다.

항목을 정할 때 함께 결정할 것들입니다.

  • 기록 시점의 값을 그대로 남깁니다. 문서 등급, 소속처럼 나중에 바뀌는 값은 참조가 아니라 당시 값을 복사해 둡니다. 등급이 내려간 뒤에 조회하면 "기밀 문서를 열람했다"는 사실이 기록에서 사라지기 때문입니다.
  • 로그에 문서 내용을 넣지 않습니다. 검색어, 문서 본문 일부, 요청 본문 전체를 그대로 남기면 감사로그가 또 하나의 유출 경로가 됩니다. 식별자와 행위만 남기고 내용은 원본 시스템에서 확인합니다.
  • 기록 실패 시의 동작을 등급별로 정합니다. 로그 저장소에 장애가 나면 열람을 막을지 허용할지 정해야 합니다. 기밀 등급은 기록할 수 없으면 열람도 허용하지 않고, 그 아래 등급은 임시 저장 후 재전송하는 식으로 나눌 수 있습니다. 정하지 않으면 구현자가 임의로 고른 동작이 운영에 들어갑니다.
  • 시각은 서버 한 곳에서 찍습니다. 클라이언트가 보낸 시각이나 서버마다 다른 시계를 쓰면 사고 조사에서 순서를 재구성할 수 없습니다. 서버 시간 동기화를 운영 항목에 포함합니다.

로그를 보호합니다

감사로그 자체가 변조되면 의미가 없습니다.

  • 기록 주체와 관리 주체를 분리합니다
  • 일반 운영자가 로그를 수정·삭제할 수 없도록 합니다
  • 별도 저장소에 보관합니다
  • 무결성 검증 수단을 둡니다

관리자 계정의 행위도 기록 대상입니다. 오히려 권한이 큰 계정일수록 기록이 중요합니다.

해시 체인으로 변조를 탐지합니다

"무결성 검증 수단"을 구체화하는 방법 중 하나가 해시 체인입니다. 각 기록의 해시를 계산할 때 바로 앞 기록의 해시를 입력에 포함시키는 방식입니다. 중간의 한 행을 고치거나 지우면 그 뒤의 연결이 맞지 않게 되므로, 전체를 다시 계산해 보는 것만으로 변조 위치를 찾을 수 있습니다.

아래는 PostgreSQL 11 이상(내장 sha256 함수와 EXECUTE FUNCTION 구문 사용)을 가정한 예시입니다. 순번, 시각, 해시는 애플리케이션이 아니라 트리거가 채웁니다.

CREATE TABLE audit_log (
  seq            bigint PRIMARY KEY,
  occurred_at    timestamptz NOT NULL,
  actor_id       text NOT NULL,
  client_ip      inet,
  action         text NOT NULL CHECK (action IN
                   ('VIEW','DOWNLOAD','PRINT','SHARE','GRANT_CHANGE','GRADE_CHANGE')),
  document_id    text NOT NULL,
  document_grade text NOT NULL,
  result         text NOT NULL CHECK (result IN ('ALLOWED','DENIED')),
  deny_reason    text,
  prev_hash      bytea NOT NULL,
  row_hash       bytea NOT NULL
);

-- 해시 입력은 세션 설정(시간대 등)에 좌우되지 않는 고정 형식으로 만든다
CREATE FUNCTION audit_payload(r audit_log) RETURNS text
LANGUAGE sql STABLE AS $$
  SELECT jsonb_build_array(
    r.seq,
    to_char(r.occurred_at AT TIME ZONE 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS.US'),
    r.actor_id, host(r.client_ip), r.action, r.document_id,
    r.document_grade, r.result, r.deny_reason
  )::text;
$$;

CREATE FUNCTION audit_log_chain() RETURNS trigger
LANGUAGE plpgsql AS $$
DECLARE
  last_row audit_log;
BEGIN
  -- 동시에 들어온 기록이 같은 이전 행을 가리키지 않도록 직렬화한다
  PERFORM pg_advisory_xact_lock(hashtext('audit_log_chain'));
  SELECT * INTO last_row FROM audit_log ORDER BY seq DESC LIMIT 1;

  NEW.seq         := COALESCE(last_row.seq, 0) + 1;
  NEW.occurred_at := clock_timestamp();
  NEW.prev_hash   := COALESCE(last_row.row_hash, sha256(''::bytea));
  NEW.row_hash    := sha256(NEW.prev_hash || convert_to(audit_payload(NEW), 'UTF8'));
  RETURN NEW;
END;
$$;

CREATE TRIGGER audit_log_chain_before_insert
BEFORE INSERT ON audit_log
FOR EACH ROW EXECUTE FUNCTION audit_log_chain();

-- 애플리케이션 계정(app_user는 예시 이름)에는 추가와 조회만 허용한다
REVOKE ALL ON audit_log FROM PUBLIC;
GRANT INSERT, SELECT ON audit_log TO app_user;

검증은 모든 행의 해시를 다시 계산하고, 순번과 앞뒤 연결이 맞는지 확인하는 쿼리 하나로 합니다. 결과가 0건이면 정상이고, 행이 나오면 그 순번부터 확인합니다.

SELECT seq
FROM (
  SELECT a.seq, a.prev_hash, a.row_hash,
         lag(a.seq)      OVER (ORDER BY a.seq) AS previous_seq,
         lag(a.row_hash) OVER (ORDER BY a.seq) AS previous_hash,
         sha256(a.prev_hash || convert_to(audit_payload(a), 'UTF8')) AS recomputed
  FROM audit_log a
) checked
WHERE row_hash <> recomputed
   OR (previous_seq IS NOT NULL
       AND (seq <> previous_seq + 1 OR prev_hash <> previous_hash))
ORDER BY seq;

이 방식이 보장하지 않는 것을 알고 써야 합니다.

  • 같은 DB 안에서는 탐지만 됩니다. 테이블 소유자나 슈퍼유저는 REVOKE의 영향을 받지 않고, 행을 고친 뒤 그 이후의 해시를 전부 다시 계산해 넣으면 체인은 다시 맞아떨어집니다. 가장 최근 행들을 통째로 지우는 것도 체인만으로는 드러나지 않습니다.
  • 그래서 체인의 끝 값을 DB 밖에 고정합니다. 일정 주기로 마지막 순번과 해시를 DB 관리자가 쓸 수 없는 저장소에 기록해 두고, 검증할 때 그 값과 대조합니다. 한 번 쓰면 보존 기간 동안 덮어쓰거나 지울 수 없는 저장소가 이 용도에 맞습니다. 예를 들어 Amazon S3의 Object Lock은 WORM 방식으로 객체를 보관하며, 컴플라이언스 모드에서는 계정의 루트 사용자도 보존 기간 중에 객체 버전을 덮어쓰거나 삭제할 수 없습니다.(Amazon S3 사용 설명서) 앞에서 말한 "기록 주체와 관리 주체의 분리"가 기술적으로는 이 지점에서 이루어집니다.
  • 기록 순서가 직렬화됩니다. 예시의 잠금은 한 번에 한 건씩만 기록하게 만듭니다. 열람 빈도가 높은 시스템에서는 이 구간이 병목이 될 수 있으므로, 기록을 큐에 넣고 단일 기록기가 순서대로 체인에 붙이거나 체인을 여러 갈래로 나누는 구성을 검토합니다. 처리량은 대상 환경에서 직접 측정해 판단해야 합니다.
  • 해시 입력 형식을 바꾸면 과거 기록 검증이 깨집니다. 컬럼을 추가할 때는 형식에 버전을 두어 이전 행은 이전 형식으로 검증할 수 있게 합니다. 예시에서 시각을 UTC 고정 형식으로 바꿔 넣은 것도 같은 이유입니다. 세션 시간대에 따라 문자열이 달라지면 변조가 없어도 검증이 실패합니다.
  • 보관 기간이 지난 행을 지우면 체인의 앞부분이 끊깁니다. 삭제 범위의 마지막 해시를 외부 저장소에 남겨 두고, 그 값을 남은 체인의 시작점으로 삼아 검증합니다. 검증 쿼리가 첫 행의 prev_hash를 따로 대조하지 않는 것도 이 때문입니다.

감사로그에서 자주 생기는 오류

증상 원인 해결
사고 조사 때 "누가 봤는지"는 나오는데 "누가 시도했는지"가 없음 권한 검사에서 거부된 요청은 로그 코드에 도달하지 않음 판정 함수의 결과(허용·거부, 사유)를 받는 한 지점에서 기록
파일 다운로드 기록이 실제보다 적음 화면 API에만 로그가 있고 파일 경로·서명 URL 발급에는 없음 문서를 내보내는 모든 경로를 나열해 경로별 기록 여부를 점검
로그의 IP가 전부 같은 값 프록시나 로드밸런서의 주소가 기록됨 신뢰하는 프록시가 넘겨준 클라이언트 주소만 사용. 임의의 요청 헤더 값을 그대로 믿지 않음
몇 달 지나자 이력 조회가 느려짐 단일 테이블에 계속 쌓이고 조회 조건에 맞는 인덱스가 없음 문서별·사용자별·기간별 조회에 맞춘 인덱스, 기간 단위 분할과 보관 기간 경과분 삭제 절차
운영자가 장애 대응 중 로그 테이블을 정리함 운영 계정에 삭제 권한이 있고 보관 기간이 문서로만 존재 운영 계정에서 수정·삭제 권한 제거, 삭제는 보관 기간을 확인하는 별도 절차로만 수행
점검은 하는데 근거가 남지 않음 조회 화면만 있고 점검 결과를 기록하는 기능이 없음 점검 일시, 점검자, 확인한 범위, 조치 내용을 남기는 기능을 함께 구현

볼 수 있어야 기록입니다

쌓아두기만 하고 조회할 수단이 없으면 사고 발생 시 활용할 수 없습니다.

  • 문서별 열람 이력 조회
  • 사용자별 접근 이력 조회
  • 기간·행위 유형별 검색
  • 이상 패턴 조회 — 평소보다 많은 문서 조회, 대량 다운로드, 업무 시간 외 접근

이상 패턴에 대해서는 알림을 설정해두는 편이 좋습니다. 사후에 확인하는 것보다 발생 시점에 인지하는 것이 대응 여지가 큽니다. 다만 기준을 느슨하게 잡아 알림이 매일 쏟아지면 담당자는 곧 확인을 멈춥니다. 기준은 사용자별 평소 사용량 대비로 잡고, 운영하면서 조정할 수 있게 설정값으로 둡니다. 알림이 무시되는 구조에 대해서는 알림이 무시되기 시작하는 지점 — 발송 시스템 설계에서 따로 다뤘습니다.


5. 반출이 필요한 경우의 경로를 만듭니다

통제만 강화하면 정당한 업무까지 막힙니다. 자료를 외부에 제공해야 하는 상황은 실제로 존재합니다.

  • 감독기관 제출
  • 계약 상대방에게 산출물 인도
  • 법률 자문을 위한 자료 제공
  • 감사 대응

이런 경우를 위한 공식 반출 절차가 필요합니다.

항목 정해야 할 내용
신청 누가 어떤 양식으로 요청하는가
승인 등급별 승인 권한자
처리 워터마크 적용, 유효 기간, 비밀번호 설정
기록 반출 사유, 대상자, 문서 목록
사후 회수 또는 파기 확인

절차가 지나치게 무거우면 사람들은 개인 메일이나 메신저로 보냅니다. 승인 단계를 최소화하고 처리를 빠르게 하는 것이 오히려 통제 유지에 도움이 됩니다.

외부로 나가는 파일에는 유효 기간을 설정하는 방식도 쓰입니다. 일정 기간이 지나면 열리지 않도록 하는 구성입니다. 완전하지는 않지만, 오래된 자료가 계속 떠도는 상황은 줄일 수 있습니다.


6. 업무와 충돌하는 지점을 조정합니다

보안 통제는 반드시 업무 편의와 충돌합니다. 이 조정을 설계 단계에서 하지 않으면 운영 중에 통제가 풀립니다.

과도한 통제는 우회를 만듭니다

자주 관찰되는 현상입니다.

  • 다운로드를 막으니 화면을 캡처해서 공유
  • 외부 메일 발송을 막으니 개인 메신저로 전송
  • 매번 승인이 필요하니 미리 대량으로 받아두고 사용
  • 접근이 불편하니 개인 저장소에 복사본을 만들어 둠

통제가 업무를 지나치게 방해하면 사람들은 우회로를 찾습니다. 그리고 우회로는 통제도 기록도 없는 경로입니다. 결과적으로 보안 수준이 더 떨어집니다.

협력사와 외부 사용자를 고려합니다

내부 직원만 쓰는 시스템은 오히려 단순합니다. 외부 사용자가 있으면 설계가 달라집니다.

  • 계정 발급과 회수 절차
  • 소속 조직 단위의 접근 범위 제한
  • 계약 종료 시 일괄 차단
  • 외부 사용자에게 적용할 통제 수준

외부 사용자에게는 더 강한 통제를 적용하되 범위를 좁게 가져가는 것이 일반적입니다. 필요한 문서만 지정해서 열람하게 하고, 다운로드는 원칙적으로 제한하는 방식입니다.

예외 절차를 만들어둡니다

통제를 풀어야 하는 상황은 반드시 생깁니다.

  • 누가 승인할 수 있는가
  • 얼마 동안 유효한가
  • 어떤 기록을 남기는가
  • 사후에 검토하는가

예외를 인정하지 않는 설계가 오히려 위험합니다. 공식 예외 경로가 없으면 비공식 경로가 생기고, 그것은 기록되지 않습니다.


7. 자주 나오는 질문

화면 캡처를 완전히 막을 수 있습니까

없습니다. 환경에 따라 부분적으로 어렵게 만들 수는 있습니다.

  • 전용 애플리케이션이라면 운영체제 수준의 캡처 제한 기능을 활용할 수 있는 경우가 있습니다
  • 웹 브라우저 환경에서는 제한이 어렵습니다
  • 가상 데스크톱 환경을 쓰면 통제 지점을 늘릴 수 있습니다

운영체제가 제공하는 기능의 범위는 공식 문서에 적혀 있고, 그 한계도 함께 적혀 있습니다.

환경 기능 공식 문서가 밝히는 범위와 한계
Android 앱 창에 FLAG_SECURE 지정 스크린샷이 빈 화면으로 찍히고, 보안되지 않은 디스플레이(화면 전송 등)에 창이 표시되지 않음. Android 11(API 30) 이하에서는 기기에 따라 안정적으로 동작하지 않는 경우가 있다고 안내됨
Windows 데스크톱 앱 SetWindowDisplayAffinity WDA_MONITOR는 모니터 외의 경로에서 창 내용을 비우고, WDA_EXCLUDEFROMCAPTURE(Windows 10 버전 2004 이상)는 창 자체를 캡처에서 제외. 보안 기능이나 DRM이 아니며, 화면을 사진으로 찍는 경우 등에는 보호를 보장하지 않는다고 명시됨

출처는 각각 Android 개발자 문서와 Microsoft Learn입니다. 두 기능 모두 앱이 자기 창에 대해 운영체제에 요청하는 것이므로, 전용 애플리케이션을 만들 때만 쓸 수 있고 브라우저로 여는 웹 페이지에는 적용할 수 없습니다. 요구사항에 캡처 차단을 넣는다면 "지원 운영체제와 버전, 적용되는 캡처 경로"를 표로 적고, 그 밖의 환경은 열람 자체를 허용할지 여부를 정해야 합니다.

어떤 방식이든 다른 기기로 화면을 촬영하는 것은 막을 수 없습니다. 따라서 캡처 차단을 요구사항으로 받을 때는 적용 환경과 한계를 문서에 명시해야 합니다.

기존 문서 시스템에 보안 기능만 추가할 수 있습니까

기존 시스템의 구조에 따라 다릅니다. 확인할 점들입니다.

  • 문서 접근이 단일 경로로 이루어지는가, 아니면 여러 경로가 있는가
  • 파일이 직접 접근 가능한 위치에 저장되어 있는가
  • 기존 권한 체계를 확장할 수 있는가
  • 로그를 남길 지점이 확보되는가

파일이 직접 접근 가능한 위치에 있다면 그것부터 정리해야 합니다. 아무리 화면에서 통제해도 파일 경로를 아는 사람은 우회할 수 있습니다.

암호화를 적용하면 해결됩니까

암호화는 필요하지만 충분하지 않습니다. 해결하는 문제와 해결하지 못하는 문제가 다릅니다.

  • 해결 — 저장 매체 탈취, 전송 구간 도청, 백업 파일 유출
  • 미해결 — 정당한 권한을 가진 사용자가 복호화된 내용을 반출하는 경우

문서 유출의 상당수는 두 번째 경우입니다. 암호화는 기본으로 적용하되, 권한 관리와 추적 수단이 함께 있어야 합니다.

어디까지 구현하는 것이 적정합니까

문서의 민감도와 유출 시 피해 규모에 따라 정합니다. 판단 기준입니다.

  1. 유출되면 어떤 피해가 발생하는가
  2. 현재 어떤 경로가 열려 있는가
  3. 각 통제 수단의 도입 비용과 업무 영향은 어느 정도인가

모든 문서에 최고 수준을 적용하는 것은 비효율적입니다. 등급을 나누고 상위 등급에 집중하는 편이 비용 대비 효과가 큽니다. 실제로 기밀에 해당하는 문서는 전체의 일부인 경우가 대부분입니다.


8. 착수 전 점검 목록

요구사항 정의

  • 유출 경로별로 통제 수단과 완전성을 합의했는가
  • 차단 불가능한 영역을 문서에 명시했는가
  • 문서 등급 체계와 등급별 적용 수준이 정의되어 있는가
  • 등급 부여 주체와 변경 절차가 있는가

권한 설계

  • 역할 기반과 개별 지정의 관계가 정의되어 있는가
  • 임시 권한에 만료일이 있는가
  • 부서 이동·퇴사 시 권한 회수 절차가 있는가
  • 정기 권한 점검 절차가 있는가
  • 모든 접근 경로에서 서버 측 권한 검증이 이루어지는가
  • 파일에 직접 접근 가능한 경로가 없는가
  • 규칙이 충돌할 때의 판정 순서와 "일치하는 규칙이 없으면 거부" 원칙이 한 곳에 구현되어 있는가
  • 검색 결과, 썸네일, 변환 결과물도 같은 판정을 거치는가
  • 권한 변경이 세션·캐시에 즉시 반영되는가
  • 서명 URL의 만료 시간과 발급 기록 방식이 정해져 있는가
  • 권한 부여·변경·말소 내역의 보관 기간이 정해져 있는가

워터마크

  • 표시 내용과 방식이 정의되어 있는가
  • 인쇄물에 대한 처리가 별도로 설계되어 있는가
  • 파일 삽입 방식을 쓸 경우 개인정보 관점 검토가 되었는가
  • 상위 등급 문서의 워터마크가 서버에서 합성되는가
  • 촬영·축소된 이미지에서 식별 코드를 판독하는 시험을 했는가
  • 식별 코드로 조회할 열람 기록의 보관 기간이 추적 필요 기간과 맞는가

감사로그

  • 적용되는 법령상 보관 기간을 확인했는가
  • 개정 고시의 시행 시점과 접속기록 보관 대상(내부 직원 외 접속자 포함 여부)을 확인했는가
  • 실패한 접근 시도를 기록하는가
  • 관리자 행위도 기록 대상인가
  • 로그 변조를 방지하는 조치가 있는가
  • 무결성 검증에 쓰는 기준 값이 DB 관리자가 수정할 수 없는 곳에 보관되는가
  • 로그 저장 실패 시의 동작이 등급별로 정해져 있는가
  • 보관 기간이 지난 기록의 삭제 절차가 있는가
  • 문서별·사용자별 이력 조회 수단이 있는가
  • 이상 패턴에 대한 알림 기준이 있는가

운영

  • 예외 승인 절차와 기록 방식이 정의되어 있는가
  • 외부 사용자에 대한 통제 수준이 별도로 정의되어 있는가
  • 통제로 인한 업무 영향을 검토했는가

마치며

문서 보안 설계에서 가장 중요한 것은 무엇을 할 수 있고 무엇을 할 수 없는지를 처음에 분명히 하는 것입니다.

권한 통제는 확실하게 막을 수 있습니다. 다운로드와 인쇄는 상당 부분 통제할 수 있습니다. 화면 캡처는 환경에 따라 제한적입니다. 다른 기기로 촬영하는 것은 막을 수 없고, 추적만 가능합니다.

이 구분을 요구사항 단계에서 합의하면, 이후의 설계는 각 경로에 어떤 수단을 얼마나 적용할지를 정하는 작업이 됩니다. 반대로 "완벽한 차단"을 목표로 두면 검수 단계에서 합의할 수 없는 논쟁이 남습니다.

저희는 문서 권한 관리와 출력물 워터마크를 결합한 워터마크 DRM 솔루션을 구축해 누적 3,000명 규모의 환경에 적용한 경험이 있습니다. 문서 보안 요구사항을 시스템으로 옮기는 단계에서 검토가 필요하시면 문의해 주세요.


함께 보면 좋은 글

참고 자료