이 문서를 통해 아래 질문들에 답할 수 있게 됩니다.

  • Config와 Secret은 무엇이 다르고 왜 분리해야 하는가?
  • Secret을 코드, Docker image, CI log에 섞으면 어떤 운영 사고가 생기는가?
  • Secret 변경과 회전을 안전하게 하려면 어떤 절차가 필요한가?

개요

Config는 환경별로 달라지는 설정 값이다.

Secret은 노출되면 안 되는 민감 값이다.

DB URL, external API endpoint, timeout은 config에 가깝고, DB password, API token, private key는 Secret이다.

둘 다 코드와 분리해야 하지만 보호 수준은 다르다.

Secret은 접근 권한, 회전, 감사, 로그 masking까지 함께 관리해야 한다.

분리 원칙

좋은 Spring 설정은 기본 구조를 코드에 두고 값은 외부에서 받는다.

spring:
  datasource:
    url: ${DB_URL}
    username: ${DB_USERNAME}
    password: ${DB_PASSWORD}
payment:
  base-url: ${PAYMENT_BASE_URL}
  api-key: ${PAYMENT_API_KEY}

이 파일은 어떤 key가 필요한지 보여주지만 실제 Secret 값은 담지 않는다.

운영 값은 환경 변수, Secret manager, encrypted file, 배포 도구의 Secret 기능 등에서 주입한다.

Docker와 Secret

Docker image에 Secret을 넣으면 image가 유출될 때 Secret도 같이 유출된다.

특히 Dockerfile의 ARGENV에 민감 값을 직접 넣으면 image history에 남을 수 있다.

나쁜 예:

ENV DB_PASSWORD=prod-password

더 나은 방향은 실행 시점에 주입하는 것이다.

docker run \
  -e SPRING_PROFILES_ACTIVE=prod \
  -e DB_URL="$DB_URL" \
  -e DB_USERNAME="$DB_USERNAME" \
  -e DB_PASSWORD="$DB_PASSWORD" \
  registry.example.com/order-api:3f21a9c

환경 변수도 완전한 보안 저장소는 아니다.

운영에서는 접근 권한, process inspection 가능성, 로그 노출을 함께 본다.

Compose와 환경 파일

개인 프로젝트나 소규모 서버에서는 .env 또는 env_file을 쓸 수 있다.

services:
  app:
    image: order-api:3f21a9c
    env_file:
      - /etc/order-api/order-api.env

이때 /etc/order-api/order-api.env는 Git에 넣지 않고 서버에서 권한을 제한한다.

sudo chown root:app /etc/order-api/order-api.env
sudo chmod 640 /etc/order-api/order-api.env

.env를 repository에 올리거나 Slack에 붙여넣는 방식은 피해야 한다.

CI/CD와 Secret

CI/CD는 build와 deploy 권한을 가지므로 Secret 노출 위험이 크다.

빌드 단계에는 운영 Secret을 주입하지 않는 것이 원칙이다.

테스트에 필요한 값은 test 전용 Secret을 쓰고, prod Secret은 deploy 단계에서만 제한적으로 사용한다.

주의할 점은 다음이다.

  • shell set -x로 Secret이 로그에 찍히지 않는가?
  • 실패 로그가 환경 변수 값을 출력하지 않는가?
  • PR from fork에서 Secret이 노출되지 않는가?
  • 개인 access token으로 운영 배포를 하지 않는가?
  • Secret 회전 시 어떤 job과 서버가 영향을 받는가?

Secret 회전

Secret은 한 번 넣고 끝나는 값이 아니다.

유출, 퇴사, 권한 변경, 주기 정책에 따라 회전해야 한다.

회전 절차는 다음을 포함한다.

  1. 새 Secret 발급
  2. staging 적용과 smoke test
  3. prod에 새 Secret 주입
  4. 애플리케이션 재시작 또는 reload
  5. 새 Secret 사용 확인
  6. 이전 Secret 폐기
  7. 실패 시 되돌릴 방법 확인

DB password처럼 연결에 영향을 주는 Secret은 connection pool 재연결과 rolling restart까지 고려해야 한다.

장애 신호

  • 배포 직후 Access denied, Unauthorized, Invalid token이 증가한다.
  • 일부 인스턴스만 새 Secret을 읽고 일부는 예전 Secret을 쓴다.
  • CI log에 token 일부가 노출된다.
  • Docker image history나 Git history에 Secret이 남아 있다.
  • Secret 이름은 바뀌었는데 애플리케이션 환경 변수 key가 예전 이름이다.
  • Secret 회전 후 connection pool이 예전 credential로 계속 실패한다.

운영 체크리스트

  • Config와 Secret을 문서에서 구분해 적는가?
  • Secret 값이 application.yml, Dockerfile, image layer, Git history에 없는가?
  • Secret 접근 권한이 최소 권한인가?
  • CI build 단계에 prod Secret이 들어가지 않는가?
  • Secret 회전 절차와 실패 시 rollback이 있는가?
  • Secret 변경 후 어떤 서비스 재시작이 필요한지 아는가?
  • 로그 masking이 적용되어 있는가?

안전한 완화 조치

Secret 문제가 의심되면 먼저 신규 배포와 Secret 회전을 멈춘다.

인증 실패가 특정 인스턴스에만 있는지 전체 서비스에 있는지 나눈다.

이전 Secret으로 rollback할 수 있는지 확인하되, 유출이 의심되면 rollback이 안전하지 않을 수 있다.

Secret을 확인한다고 값을 채팅이나 ticket에 그대로 붙이지 않는다.

키 이름, version, 생성 시각, 적용 대상, error sample로 대화한다.

인프라 담당자와 공유할 자료

서비스: order-api prod
증상: 14:20부터 payment API 401 증가
변경: PAYMENT_API_KEY secret v7로 회전
영향: instance-1,2 실패 / instance-3 정상
애플리케이션 key: PAYMENT_API_KEY
요청: 인스턴스별 Secret version과 재시작 시각 확인
주의: Secret 값은 공유하지 않음

Secret 문제는 값 자체보다 version, 적용 대상, 권한, 회전 시각을 공유해야 한다.

실전 팁

  • config sample에는 key 이름과 설명만 둔다.
  • Secret 값은 로그, exception message, actuator env에 노출되지 않게 한다.
  • 운영 Secret은 build가 아니라 deploy/runtime 단계에서 주입한다.
  • Secret 회전은 staging에서 먼저 시험한다.
  • Secret 이름과 소유자, 회전 주기를 문서화한다.

위험 신호!

  • prod password가 application-prod.yml에 들어 있다.
  • Dockerfile에 token을 ENV로 넣는다.
  • CI 실패 로그에 환경 변수 전체를 출력한다.
  • 퇴사자 개인 token으로 배포가 돌아간다.
  • Secret 회전 후 검증과 폐기 절차가 없다.

확인 질문

확인 질문

  • Config와 Secret의 차이는 무엇인가?
    • Config는 환경별 동작을 바꾸는 값이고 Secret은 노출되면 안 되는 인증 정보나 민감 값이다.
  • Secret을 Docker image에 넣으면 위험한 이유는 무엇인가?
    • image registry, image layer, history, 서버 cache를 통해 Secret이 복제되고 회전 전까지 계속 노출될 수 있기 때문이다.
  • Secret 회전에서 가장 중요한 검증은 무엇인가?
    • 새 Secret이 모든 대상 인스턴스에 적용됐는지, 이전 Secret을 폐기해도 서비스가 정상인지, 실패 시 안전한 되돌림이 가능한지다.

참고 문서