PII-Shield: ELK에 전달되기 전에 로그에서 PII 정리

발행: (2026년 6월 10일 PM 01:52 GMT+9)
11 분 소요
원문: Dev.to

Source: Dev.to

첫 번째 아이디어는 간단했습니다.

로그 한 줄을 가져와서 의심스러운 부분을 살펴보고 엔트로피를 계산합니다. 무작위 비밀처럼 보이는 모든 것을 가립니다.

PII는 개인 식별 정보를 의미합니다. 여기에는 이메일, 전화번호, 주소, 여권 번호, 카드 번호, 액세스 토큰 및 로그를 통해 자유롭게 이동해서는 안 되는 기타 값이 포함됩니다.

처음엔 엔트로피가 좋은 신호처럼 보였습니다. 많은 토큰, 키, 세션 값이 실제로 잡음처럼 보이기 때문이죠.

x9VdQp2Mz_La77kPq0
sk_live_51Nx...
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

하지만 엔트로피만으로는 충분하지 않았습니다.

엔트로피가 낮지만 여전히 가려야 하는 값도 있습니다. 예: password=123, token=dev, cvv=000.

반대로 무작위처럼 보여도 비밀이 아닌 값도 있습니다. Trace ID, UUID, 짧은 커밋 해시, 요청 ID, 경로 조각 등은 모두 의심스러워 보일 수 있습니다.

엔트로피 임계값이 너무 낮으면 필터가 유용한 로그까지 차단하고, 너무 높으면 약한 비밀을 놓칩니다.

그래서 PII‑Shield는 엔트로피를 넘어섰습니다. 정규식 규칙, 민감 키, 허용 목록, 그리고 결제 카드 번호를 위한 Luhn 알고리즘 같은 별도 검증자를 추가했습니다.

또한 오늘날 PII가 정리되는 방식이 마음에 들지 않았습니다.

많은 팀이 Fluentd, Logstash, SIEM 또는 로그 파이프라인 수준에서 로그를 정리합니다. 이는 도움이 되지만 너무 늦은 단계입니다. 원시 데이터는 이미 애플리케이션을 떠났고, 버퍼, 재시도, 임시 파일, 알림, 대시보드를 거쳤을 수 있습니다.

PII‑Shield는 데이터를 더 일찍 정리하려고 합니다. 이는 파드가 로그를 내보내기 전에 PII와 비밀을 제거하는 오픈소스 도구입니다.

레포지토리: https://github.com/pii-shield/pii-shield

기본 아이디어

요약하면 다음과 같습니다.

application writes a log
        |
        v
PII-Shield reads the raw log near the app
        |
        v
only the cleaned line goes out

목표는 “나중에 정리한다”가 아니라 “원시 값이 나가지 않게 한다”는 것입니다.

PII‑Shield는 여러 방식으로 사용할 수 있습니다.

  • 표준 입력·출력을 필터링하는 CLI 도구 또는 컨테이너
  • Kubernetes의 사이드카 컨테이너
  • 파드가 생성될 때 사이드카를 주입하는 Kubernetes Operator
  • 설치용 Helm 차트
  • 프로세스 내부에서 스캐너를 실행하고 싶다면 Node.js와 Python용 WASM SDK

주요 Kubernetes 흐름은 다음과 같습니다. 애플리케이션은 공유 볼륨에 파일로 로그를 씁니다. 사이드카는 그 파일을 읽고 각 라인을 스캔한 뒤, 정제된 스트림을 표준 출력으로 씁니다. 일반 로그 수집기가 그곳에서 읽을 수 있습니다.

┌──────────────────── pod ────────────────────┐
│                                             │
│  app container                              │
│      │                                      │
│      │ /var/log/app/output.log             │
│      v                                      │
│  shared emptyDir volume                     │
│      │                                      │
│      v                                      │
│  pii-shield sidecar -> sanitized stdout     │
│                                             │
└─────────────────────────────────────────────┘

                      v
              Loki / ELK / S3 / SIEM

이것은 모든 것을 보이지 않게 가로채는 것이 아니라, 애플리케이션이 알려진 파일에 로그를 써야 한다는 전제하에 작동합니다. 하지만 테스트가 쉽고, 사이드카 이미지 안에 쉘이 필요 없으며, 애플리케이션 런타임을 변경하지도 않습니다.

무엇을 민감하다고 판단하는가

스캐너는 “모든 개인 데이터를 찾아라”는 마법 버튼 하나만 있는 것이 아닙니다. 여러 층을 사용합니다.

1️⃣ 민감 키

라인에 password=..., token=..., secret=..., api_key=... 와 같은 키가 있으면, 그 키 옆의 값은 가려야 합니다.

input:  payment failed token=sk_live_51Nx...
output: payment failed token=[HIDDEN:9b22c1]

2️⃣ 사용자 정의 정규식 규칙

많은 기업이 자체 내부 ID를 가지고 있습니다. 티켓 번호, 정책 ID, 고객 ID, 의료 기록 번호, 법적 사건 번호, 계약 번호 등이 그 예입니다.

이 값들은 일반적인 신호만으로는 추측하기 어렵습니다. 따라서 직접 정의하는 것이 좋습니다.

export PII_CUSTOM_REGEX_LIST='[
  {"pattern": "^MRN-[0-9]{8}$", "name": "MedicalRecord"},
  {"pattern": "^CASE-[0-9]{4}-[0-9]{6}$", "name": "CaseNumber"}
]'

성능 팁

사용자가 10개의 규칙을 추가하면 스캐너가 각 토큰마다 10개의 정규식 검사를 수행하게 되어 처리량이 무거워질 수 있습니다. 그래서 설정 로드 시 규칙을 하나씩 검사한 뒤 | 로 연결해 하나의 큰 정규식으로 합칩니다. 각 규칙은 그룹으로 감싸져 있어, 매칭될 때 어떤 이름을 마스킹에 넣을지 알 수 있습니다.

예시:

(^MRN-[0-9]{8}$)|(^CASE-[0-9]{4}-[0-9]{6}$)

코드에서는 이를 CombinedCustomRegex 로 저장합니다. 개별 컴파일된 규칙은 여전히 설정에 남아 있지만, 메인 경로에서는 결합된 정규식을 사용합니다. 모든 규칙 집합에 대해 속도 향상이 보장되는 것은 아니지만, 매 토큰마다 개별 정규식을 순차적으로 적용할 필요를 없애줍니다.

3️⃣ 엔트로피

많은 비밀은 무작위 텍스트처럼 보입니다. API 키, 세션 토큰, 무작위 비밀번호 등이 대표적이죠. PII‑Shield는 Shannon 엔트로피를 사용합니다. 토큰이 충분히 길고 무작위라면 스캐너는 이를 의심스러운 것으로 간주합니다.

input:  Authorization failed: x9VdQp2Mz_La77kPq0
output: Authorization failed: [HIDDEN:3e12aa]

하지만 엔트로피만으로는 실제 로그와 충돌할 수 있습니다. 커밋 해시, UUID, 트레이스 ID, 요청 ID, 파일 경로 등도 의심스러워 보일 수 있기 때문입니다. 그래서 허용 목록을 둡니다.

export PII_SAFE_REGEX_LIST='[
  {"pattern": "^[a-f0-9]{7}$", "name": "GitShortSHA"}
]'

허용 목록은 다른 검사보다 먼저 실행됩니다. 규칙이 너무 넓으면 숨겨야 할 값을 그대로 통과시킬 수 있습니다. 이는 아이디어의 버그가 아니라 수동 튜닝의 비용입니다.

4️⃣ 특정 데이터 타입 검증

결제 카드 번호는 Luhn 알고리즘으로 검증합니다. 단순히 16자리 정규식이 아니라, 13~19자리 숫자 시퀀스를 찾아 경계를 확인하고, 약한 숫자 집합을 배제한 뒤 Luhn 체크섬을 적용합니다.

오탐을 줄이기 위한 컨텍스트 체크도 있습니다. Luhn 검증을 통과했지만 단순 트레이스 값일 수도 있습니다.

TraceId=4556737586899855

이는 카드로 간주해서는 안 됩니다.

하지만 다음과 같이 카드와 관련된 문맥이 있으면 숨겨야 합니다.

visa card 4556737586899855 provided

[REDACTED] 대신 해시를 쓰는가

모든 비밀을 [REDACTED] 로 바꾸면 디버깅이 어려워집니다.

때때로 열 개의 오류가 모두 같은 토큰이나 같은 사용자 ID를 가지고 있다는 사실을 알아야 할 때가 있습니다. 원시 값은 보여서는 안 되지만, 동일성을 확인할 필요는 있습니다.

그래서 PII‑Shield는 민감 값을 짧은 마커로 교체합니다.

[HIDDEN:a1b2c3]

마커는 소금(salt) 값을 기반으로 생성됩니다. PII_SALT 가 고정돼 있으면 같은 원시 값이 같은 마커를 얻습니다. QA와 SRE 팀은 로그 간에 이벤트를 비교할 수 있습니다.

시작할 때마다 소금이 무작위라면 재시작 후에는 연결 고리가 사라집니다.

프로덕션에서는 소금을 시크릿에서 가져와야 합니다.

env:
  - name: PII_SALT
    valueFrom:
      secretKeyRef:
        name: pii-shield-secrets
        key: salt
0 조회
Back to Blog

관련 글

더 보기 »

Eidentic 소개

Today we're releasing Eidentic, an open-source TypeScript SDK for building AI agents with self-improving memory and the production fundamentals built in — not b...

Typescript의 타입

Introdução Tipos são uma forma de definir a “forma” ou o contrato dos dados que estamos usando no código. Pensando em Javascript puro, ele é dinâmico: você pode...