사전 시크릿 관리
개념

시크릿 관리

gabury1

시크릿 관리는 남이 알면 안 되는 값을 코드 밖에 두고 다루는 일입니다. 비밀번호나 키를 어딘가에 보관해 두었다가 필요한 프로그램에만 건네줍니다. 그 값을 언제 새것으로 바꾸고 언제 없앨지도 여기서 정합니다.

상세

정수기 필터는 물맛이 이상해진 뒤에 갈지 않습니다. 반년이 차면 멀쩡해 보여도 새것으로 바꿉니다. 언제 바꿀지를 사람의 감각이 아니라 달력이 정합니다.

시크릿은 알아낸 사람이 곧 권한을 얻는 값입니다. 값을 복사해 가도 원래 자리에는 그대로 남습니다. 비밀번호, API(Application Programming Interface, 응용 프로그램 인터페이스) 키, 데이터베이스 자격 증명, SSH(Secure Shell) 키, 인증서가 여기에 듭니다. OWASP(Open Worldwide Application Security Project) 시크릿 관리 치트시트는 이런 값을 소스 코드 안에 평문으로 박아 두거나 설정 파일과 구성 관리 도구 여기저기에 흩뿌려 둔 조직이 많다고 적습니다. 관리라는 말이 덮는 일은 넷입니다. 저장, 제공, 감사, 회전입니다. 치트시트는 이 넷을 한곳으로 모아 시크릿 접근을 통제하려는 요구가 조직들 사이에서 커졌다고 적습니다.

시크릿에는 수명이 있습니다. 치트시트는 그 단계를 생성, 회전, 폐기, 만료로 나눕니다.

flowchart TD
    subgraph L["수명 단계"]
        A["생성"]
        B["회전"]
        C["폐기"]
        D["만료"]
    end
    D -->|"만료일이 절차를 촉발"| B

생성 단계에서 새 시크릿은 쓰임새에 맞게 안전하게 만들어져야 합니다. 암호학적으로 충분히 튼튼해야 합니다. 그리고 필요한 역할을 해내는 데 필요한 최소한의 권한만 부여받아야 한다고 적습니다. 회전은 주기적으로 값을 바꾸는 일입니다. 훔친 자격 증명이 짧은 기간만 통하게 하려는 것입니다. 치트시트는 정기 회전이 자격 증명을 재사용하는 습관으로 되돌아가는 경향도 줄인다고 적습니다.

폐기는 더 필요 없어졌거나 유출됐을 가능성이 있는 시크릿의 접근을 막는 일입니다. TLS(Transport Layer Security) 인증서라면 인증서 폐기도 함께 따릅니다. 만료는 가능한 자리에서 시크릿에 유효 기간을 걸어 두는 일입니다. 시크릿을 쓰는 시스템이 능동적으로 만료시킬 수도 있습니다. 시크릿 관리 시스템에 만료일을 걸어 두어 관련 절차를 촉발시키고 결국 회전으로 이어지게 할 수도 있다고 적습니다.

이 네 단계를 사람의 기억이 아니라 정해진 절차로 돌리는 것이 시크릿 관리입니다.

배경

자격 증명을 프로그램 안에 그대로 적어 넣는 일은 오래된 습관입니다. CWE(Common Weakness Enumeration, 공통 약점 목록)는 이것을 798번 약점으로 등록해 두었습니다. 갈래는 둘이라고 적습니다. 들어오는 쪽은 프로그램이 입력받은 자격 증명을 안에 박아 둔 값과 맞춰 보는 경우입니다. 나가는 쪽은 프로그램이 다른 시스템에 접속하려고 그 접속용 자격 증명을 자기 안에 지니는 경우입니다. 박아 둔 비밀번호는 설치본마다 같습니다. 시스템 관리자가 프로그램을 직접 고치지 않고서는 대개 바꾸거나 끌 수 없다고 적습니다. CWE 는 하드코딩된 비밀번호가 쓰이면 악의적인 사용자가 그 계정에 접근하리라는 것이 거의 확실하다고 적습니다. 그리고 조직이 달라도 모든 설치본이 같은 비밀번호를 쓰기 때문에 웜 같은 대규모 공격이 가능해진다고 적습니다.

그러면 값을 코드에서 떼어내야 합니다. 12요소 앱(The Twelve-Factor App)은 설정을 코드에서 엄격히 분리하라고 적습니다. 설정은 배포마다 크게 달라지지만 코드는 그렇지 않다는 것이 이유입니다. 분리가 제대로 됐는지 가리는 시험도 하나 제시합니다. 코드베이스를 지금 당장 오픈소스로 공개해도 자격 증명이 새지 않는가입니다. 떼어낸 값을 어디에 둘 것인가는 그다음 물음입니다. 12요소 앱은 환경 변수를 답으로 내놓습니다. 환경 변수는 코드를 고치지 않고 배포마다 바꿀 수 있습니다. 설정 파일과 달리 코드 저장소에 실수로 커밋될 여지가 적다고 적습니다. 언어와 운영체제를 가리지 않는 표준이라는 점도 이유로 듭니다.

코드에서 떼어낸 값은 이제 어딘가에 보관되고 누군가에게 전달되어야 합니다. 서비스가 여럿이면 같은 시크릿을 여러 서비스가 나눠 쓰는 일도 생깁니다. OWASP 시크릿 관리 치트시트는 그런 경우 침해나 유출이 어디서 비롯됐는지 짚어내기가 까다로워진다고 적습니다. 그래서 시크릿의 저장과 제공, 감사, 회전을 한곳으로 모아 접근을 통제하려는 요구가 커졌다고 적습니다. 그 일에 붙은 이름이 시크릿 관리입니다.

갈래

갈리는 자리는 셋입니다. 값이 얼마나 오래 사나, 값을 어디에 두나, 값을 실행 중인 프로그램에 어떻게 넣나입니다.

동적 시크릿과 정적 시크릿

쓸 때마다 새로 만들어 쓰고 거두는 값이 동적 시크릿입니다. OWASP 시크릿 관리 치트시트는 그 쓰임새로 짧게 사는 자격 증명과 API 키를 듭니다. 주 서비스를 다른 서비스에 이어 붙이려는 의도를 그때그때 표현하는 값입니다. 메모리 안이나 실행 중 통신을 지키는 짧게 사는 무결성·암호화 통제도 같은 갈래로 듭니다. 다만 이런 값은 대개 접속할 상대 서비스와 함께 만들어야 한다고 적습니다. 그리고 동적 시크릿을 만들어 내려면 오래 사는 정적 시크릿이 대개 먼저 필요하다고 적습니다.

정적 시크릿이 남는 자리도 있습니다. 배포 한 번보다 오래 살아야 하는 키 재료가 그렇습니다. 치트시트는 저장소 암호화 키와 TLS 공개키 기반구조 키를 예로 듭니다. 한시적인 역할이나 자격 증명을 만들지 못하는 서비스에 붙는 자격 증명도 정적으로 남습니다. 동적 쪽의 실물은 HashiCorp Vault 의 데이터베이스 시크릿 엔진입니다. 설정된 역할에 따라 데이터베이스 자격 증명을 그때그때 만들어 줍니다. Vault 문서는 서비스마다 서로 다른 자격 증명으로 접속하게 되므로 수상한 데이터 접근을 발견했을 때 감사가 훨씬 수월해진다고 적습니다.

시크릿을 두는 자리

치트시트는 CI/CD(Continuous Integration and Continuous Delivery, 지속적 통합과 지속적 전달) 작업에서 시크릿을 둘 수 있는 자리를 셋으로 나눕니다. 첫째는 CI/CD 도구 자신입니다. GitLab, GitHub, Jenkins 안에 시크릿을 저장하는 방식입니다. 코드에 커밋하는 것과는 다르다고 못 박습니다. 둘째는 시크릿 관리 시스템입니다. 클라우드 제공자가 주는 시설이 그 자리를 맡습니다. AWS(Amazon Web Services) Secrets Manager, Azure Key Vault, Google Secret Manager 를 예로 듭니다. HashiCorp Vault, Conjur, Keeper 같은 제삼자 제품도 같은 자리입니다.

셋째는 CI/CD 가 시크릿을 아예 만지지 않는 방식입니다. 치트시트는 시크릿을 쓰는 쪽이 직접 가져가는 편이 더 낫다고 적습니다. 이때 CI/CD 파이프라인은 오케스트레이션 시스템에 지정된 서비스 계정으로 서비스를 스케줄링하라고 지시하는 데까지만 관여합니다. 시크릿은 그 서비스 계정을 받은 쪽이 스스로 꺼냅니다. CI/CD 도구는 오케스트레이션 플랫폼 자격 증명은 여전히 쥐지만 시크릿 자체에는 더 이상 접근하지 못하게 됩니다.

프로그램에 넣는 길

컨테이너 안에서 도는 앱에 시크릿을 넣는 길은 셋이라고 치트시트가 적습니다.

flowchart TD
    A["파일 마운트"] --> P["앱 프로세스"]
    B["사이드카가 받아 메모리로"] --> P
    C["환경 변수"] --> P

파일 마운트는 시크릿을 설정 파일에 담아 두고 그 파일을 볼륨으로 붙이는 방식입니다. 이 마운트는 오케스트레이터가 붙여야 하고 이미지에 내장해서는 안 된다고 적습니다. 내장하면 컨테이너 정의와 함께 시크릿이 샙니다. 사이드카 방식은 별도의 앱이나 컨테이너가 시크릿 매니저 서비스에서 값을 직접 받아 옵니다. 치트시트는 이 방식이면 파일 시스템이나 컨테이너 환경 변수를 들여다봐도 시크릿이 보이지 않는다는 점을 듭니다.

환경 변수는 컨테이너 설정의 일부로 값을 직접 주는 방식입니다. 다만 시크릿 자체를 컨테이너 빌드 명령에 하드코딩해서는 안 된다고 적습니다. 컨테이너 정의와 함께 새어 나가기 쉬운 자리입니다. 환경 변수는 대개 모든 프로세스가 읽을 수 있습니다. 로그나 시스템 덤프에 섞여 들어갈 수도 있다고 적습니다. 그래서 다른 방법이 불가능한 경우가 아니라면 환경 변수 사용은 권장하지 않는다고 적습니다.

예시

Kubernetes 의 Secret 오브젝트

Kubernetes 문서는 Secret 을 비밀번호나 토큰, 키 같은 소량의 민감한 데이터를 담는 오브젝트라고 적습니다. 이런 정보를 파드 명세나 컨테이너 이미지에 넣지 않아도 되는 것이 요점입니다.

YAML
apiVersion: v1
kind: Secret
metadata:
  name: secret-dockercfg
type: kubernetes.io/dockercfg
data:
  .dockercfg: |
    eyJhdXRocyI6...

data 아래 값은 base64 로 인코딩합니다. Kubernetes 문서는 이 인코딩이 값을 가려 줄 뿐 쓸 만한 수준의 기밀성을 주지는 않는다고 못 박습니다. 위의 인증 값도 base64 로 인코딩됐을 뿐입니다. 이 Secret 을 읽을 수 있는 사람은 레지스트리 접근 베어러 토큰을 알아낼 수 있다고 적습니다. base64 인코딩을 직접 하고 싶지 않으면 stringData 필드를 대신 쓸 수 있습니다.

파드에 넣을 때는 컨테이너마다 env[].valueFrom.secretKeyRef 필드로 쓰려는 시크릿 키를 환경 변수에 잇습니다. 그리고 프로그램이 그 환경 변수에서 값을 찾도록 이미지나 명령줄을 고칩니다.

저장 자체에도 조건이 붙습니다. Kubernetes 문서는 Secret 이 기본적으로 API 서버 뒤의 데이터 저장소인 etcd 에 암호화되지 않은 채 저장된다고 경고합니다. API 접근 권한이 있는 사람은 누구나 시크릿을 가져가거나 고칠 수 있습니다. 네임스페이스에 파드를 만들 권한이 있는 사람은 그 권한으로 그 네임스페이스의 어떤 시크릿이든 읽을 수 있다고 적습니다.

AWS Secrets Manager 의 호출 한 줄과 자동 회전

AWS 문서는 하드코딩된 자격 증명을 지우고 필요할 때 서비스를 호출해 동적으로 받아 오라고 적습니다. 그 호출이 이 한 줄입니다.

aws secretsmanager get-secret-value --secret-id MyTestSecret
JSON
{
    "ARN": "arn:aws:secretsmanager:us-west-2:123456789012:secret:MyTestSecret-a1b2c3",
    "Name": "MyTestSecret",
    "VersionId": "a1b2c3d4-5678-90ab-cdef-EXAMPLE11111",
    "SecretString": "{\"user\":\"diegor\",\"password\":\"EXAMPLE-PASSWORD\"}",
    "VersionStages": ["AWSCURRENT"],
    "CreatedDate": 1523477145.713
}

SecretString 이 실제 값이 담겨 나오는 자리입니다. 이 호출은 지정한 판에서 암호화된 필드인 SecretString 이나 SecretBinary 중 내용이 든 쪽을 꺼내 옵니다.

회전은 설정으로 겁니다. AWS 문서는 회전을 시크릿을 주기적으로 갱신하는 과정이라고 적습니다. 시크릿과 데이터베이스나 서비스 양쪽의 자격 증명을 함께 바꾼다고 적습니다. 형태는 둘입니다. 관리형 회전에서는 서비스가 회전을 직접 설정하고 관리합니다. Lambda 함수를 쓰지 않습니다. 나머지 유형에서는 Lambda 함수가 시크릿과 데이터베이스나 서비스를 갱신합니다.

GitHub Actions 의 시크릿 참조

YAML
steps:
  - name: Hello world action
    with:
      # Set the secret as an input
      super_secret: ${{ secrets.SuperSecret }}
    env:
      # Or as an environment variable
      super_secret: ${{ secrets.SuperSecret }}

secrets 컨텍스트로 저장소에 만들어 둔 시크릿을 참조합니다. with 아래에 두면 액션의 입력으로 들어갑니다. env 아래에 두면 환경 변수로 들어갑니다.

참조가 막히는 자리도 정해져 있습니다. GitHub 문서는 시크릿을 if: 조건절에서 직접 참조할 수 없다고 적습니다. 그리고 포크된 저장소에서 워크플로가 촉발되면 GITHUB_TOKEN 을 뺀 시크릿은 러너로 전달되지 않는다고 적습니다.

관련 항목

다루는 값

자격 증명 · API 키 · 데이터베이스 비밀번호 · SSH 키 · 인증서 · 토큰 · 암호화 키

이것이 놓이거나 저장되는 저장소

시크릿 저장소 · 시크릿 관리 시스템 · 키 관리 서비스 · 하드웨어 보안 모듈 · 봉투 암호화 · 환경 변수

이것을 실제로 구현·채택한 제품

AWS · Secrets Manager · Azure · Google Secret Manager · HashiCorp Vault · Conjur · Keeper · GitLab · GitHub · Jenkins · GitHub Actions · Kubernetes · Lambda

이것을 정의하는 표준·문서

OWASP · CWE · 12요소 앱 · TLS

시크릿이 오가는 CI/CD 경로

CI/CD · 파이프라인 · 오케스트레이션 · 스케줄링 · 워크플로

이것이 거치는 처리 단계

키 관리 · 회전 · 폐기 · 만료 · 동적 시크릿 · 정적 시크릿 · 단기 자격 증명 · 브레이크글래스

시크릿 접근을 통제하는 수단

최소 권한 · 접근 제어 · 서비스 계정 · 감사 로그 · 권한

터지는 것과 그 이름

하드코딩된 자격 증명 · 시크릿 유출 · 시크릿 스캐닝 · 사고 대응

다른 이름: Secrets Management · secret management · Managing Secrets · 시크릿 매니지먼트