PKI 외부 CA 시크릿 엔진

PKI 외부 CA 시크릿 엔진

Enterprise 기능 — 적절한 Vault Enterprise 라이선스 또는 HCP Vault Dedicated 클러스터가 필요해요.

개요(Overview)

PKI External CA 시크릿 엔진은 HashiCorp Enterprise 플러그인으로, ACME(Automatic Certificate Management Environment) 프로토콜을 통해 외부 인증 기관(CA)에서 서명된 리프 인증서를 획득할 수 있게 해요. Vault 서버의 보안을 유지하면서 Let's Encrypt 같은 공개 CA나 다른 ACME 호환 CA를 활용할 수 있어요.

PKI 외부 CA 플러그인으로 다음을 할 수 있어요:

  • 외부 CA로 ACME 계정 관리
  • ACME 프로토콜을 통한 인증서 획득 자동화
  • 애플리케이션에 인증서 캐시 및 배포

PKI External CA 시크릿 엔진은 GlobalSign, Sectigo, DigiCert와 함께 작동하는 것이 검증됐어요.

출처: 문서

본문

설정(Setup)

플러그인을 활성화해요.

$ vault secrets enable pki-external-ca

ACME 계정을 구성해요.

Let's Encrypt(프로덕션):

$ vault write pki-external-ca/config/acme-account/letsencrypt-prod  \
    directory_url="https://acme-v02.api.letsencrypt.org/directory"  \
    email_contacts="[email protected],[email protected]"         \
    key_type="ec-256"

Let's Encrypt(스테이징):

$ vault write pki-external-ca/config/acme-account/letsencrypt-staging      \
    directory_url="https://acme-staging-v02.api.letsencrypt.org/directory" \
    email_contacts="[email protected]"

일부 ACME 서버는 계정 등록 시 External Account Binding(EAB)을 요구해요.

$ vault write pki-external-ca/config/acme-account/acme-eab      \
    directory_url="https://acme.example.com/v2/acme/directory"  \
    email_contacts="[email protected]"                          \
    eab_kid="your-eab-key-id"                                   \
    eab_key="urlbase64-encoded-eab-key"                         \
    key_type="rsa-2048"

요청에 사용할 수 있는 도메인과 Vault가 인증서를 발급하는 방법을 설정하는 역할 정의 파일(role.json)을 만들어요. 아래 예시는 일반적인 구성을 보여줘요.

명시적 도메인 일치(Explicit domain match):

{
  "acme_account_name": "letsencrypt-prod",
  "allowed_domains": "example.com,www.example.com,api.example.com",
  "allowed_domain_options": "bare_domains"
}

와일드카드 도메인 일치(Wildcard domain match):

{
  "allowed_domains": "*.example.com",
  "allowed_domain_options": "wildcards",
  "allowed_challenge_types": "dns-01"
}

하위 도메인 일치(Subdomain match):

{
  "allowed_domains": "example.com",
  "allowed_domain_options": "bare_domains,subdomains"
}

Identity 템플릿(Identity template):

{
  "allowed_domains": "{{identity.entity.name}}.users.example.com"
}

JSON 파일을 사용해 Vault에 역할을 만들어요.

$ vault write pki-external-ca/role/web-servers @role.json

자동 도전 과제 수행용 DNS 프로바이더(DNS providers for automatic challenge fulfillment)

PKI External CA 시크릿 엔진은 특정 DNS 프로바이더와 통합해 DNS-01 도전 과제 충족을 자동화해요. DNS 프로바이더를 전역적으로 구성해 모든 ACME 계정에 공유할 수 있어, 수동 개입이나 외부 시스템 없이 인증서 발급을 자동화할 수 있어요.

도전 과제 충족 모드(Challenge fulfillment modes) — 플러그인은 세 가지 도전 과제 충족 모드를 지원해요.

  • 수동/외부 충족(Manual/External Fulfillment, 기본 동작): 플러그인이 외부 시스템이 충족할 수 있도록 API 엔드포인트로 도전 과제를 노출해요. 외부 시스템은 Vault에서 도전 토큰을 검색하고 DNS 레코드나 HTTP 토큰을 수동으로 만들어요.
  • 자동 DNS 충족(Automatic DNS Fulfillment): Vault가 구성된 프로바이더 자격 증명으로 DNS 레코드를 자동으로 관리해요. 구성된 DNS 프로바이더에 대해 요청된 모든 식별자와 일치하는 주문을 받으면 Vault가 전체 ACME 인증서 요청 과정을 처리해요.
  • 혼합 충족(Hybrid Fulfillment): 일부 도전 과제는 자동으로, 일부는 수동 충족으로 구성할 수 있어요. 혼합 충족은 일부 도메인에 DNS 프로바이더가 구성되고 다른 도메인은 그렇지 않을 때 유용해요.

DNS 프로바이더 구성(DNS provider configuration) — config/dns/<provider-type>/<name> 엔드포인트로 DNS 프로바이더를 독립적으로 관리하고 glob 패턴으로 지원 식별자를 지정해요. ACME 도전 과제 평가 중 어떤 ACME 계정의 어떤 주문에 대해서도 Vault는:

  1. 주문의 식별자에 기반해 일치하는 DNS 프로바이더 하나 이상을 선택해요.
  2. 일치하는 프로바이더가 없는 도전 과제는 수동 충족을 요구해요.
  3. DNS 프로바이더로 자동 충족할 도전 과제를 식별해요.
  4. 수동 도전 과제 충족을 기다린 뒤, 자동 충족 자격이 있는 도전 과제에 대해 자동 DNS 업데이트를 수행해요.

지원되는 DNS 프로바이더(Supported DNS providers) — 플러그인은 여러 DNS 프로바이더를 지원해요. AWS Route53(Amazon DNS 서비스), Azure DNS(Microsoft Azure DNS 서비스), Google Cloud DNS(Google Cloud Platform DNS 서비스), RFC2136(BIND 및 기타 RFC2136 호환 서버용 동적 DNS 업데이트).

DNS 프로바이더 구성하기(Configure DNS providers) — DNS 프로바이더의 지원 인수에 대한 자세한 내용은 API-docs 페이지를 참조하세요.

AWS Route53을 DNS 프로바이더로 구성:

$ vault write pki-external-ca/config/dns/aws-route53/production \
    identifiers="*.example.com,*.prod.example.com" \
    access_key_id="«redacted:AKIA…»" \
    secret_access_key="wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY" \
    region="us-east-1"

Azure DNS를 DNS 프로바이더로 구성:

$ vault write pki-external-ca/config/dns/azure-dns/production \
    identifiers="*.example.com" \
    zone_name="my-zone-id" \
    client_id="00000000-0000-0000-0000-000000000000" \
    client_secret="your-client-secret"

Google Cloud DNS를 DNS 프로바이더로 구성:

$ vault write pki-external-ca/config/dns/google-cloud-dns/production \
    [email protected]

RFC2136을 동적 DNS 업데이트용으로 구성:

$ vault write pki-external-ca/config/dns/rfc2136/bind-dns \
    nameserver="ns1.example.com:53" \
    tsig_key_name="my-key" \
    tsig_secret="base64-encoded-secret" \
    tsig_algorithm="hmac-sha256"

DNS 프로바이더 테스트하기(Testing DNS providers) — ACME 워크플로 내에서 DNS 프로바이더를 사용하기 전에 test API 엔드포인트를 사용해 인증서를 요청하지 않고도 구성이 작동하는지 확인할 수 있어요. 예를 들어 다음 명령은 DNS 프로바이더 자격 증명이 올바른지, Vault가 DNS 레코드를 만들고 삭제할 수 있는지, DNS 프로바이더가 지정된 식별자를 서비스할 수 있는지 확인해요.

$ vault write pki-external-ca/dns/test/workflow \
    provider_type="aws-route53" \
    provider_name="production" \
    identifier="test.example.com"

인증서 워크플로(Certificate workflows)

PKI External CA 시크릿 엔진은 표준 PKI 시크릿 엔진의 sign과 issue 엔드포인트와 유사한, 인증서를 요청하는 두 가지 뚜렷한 워크플로를 지원해요. 보안 요구 사항과 운영 필요에 가장 잘 맞는 워크플로를 선택할 수 있어요.

워크플로 비교(Workflow comparison):

기능 CSR 워크플로 Identifier 워크플로
복잡성 더 높음 — 클라이언트가 CSR과 연결 키를 생성해야 함 더 낮음 — 클라이언트는 식별자만 제공
키 전송 클라이언트를 떠나지 않음 Vault가 생성 후 클라이언트에 전송
인증서 캐싱 지원하지 않음 Vault가 인증서와 개인 키를 모두 캐시

각 워크플로를 언제 사용할까(When to use each workflow):

CSR 워크플로를 사용할 때:

  • 기존 키 관리 프로세스와 인프라가 있을 때.
  • 키 전송을 금지하는 보안 정책을 준수해야 할 때.
  • 키 공유가 필요 없는 단일 인스턴스 애플리케이션을 배포할 때.

Identifier 워크플로를 사용할 때:

  • 최소한의 클라이언트 복잡성으로 운영 단순성을 우선시할 때.
  • Vault가 키 생성과 관리를 처리하길 원할 때.
  • 나중에 인증서와 키를 모두 검색해야 할 때.

인증서를 요청하는 방법(How to request certificates) — 새 주문을 만들고 공개 인증서(그리고 선택적으로 개인 키)를 검색하려면 다음 단계가 수행되어야 해요.

Identifier 워크플로 — 이 워크플로에서는 도메인 이름을 제공해요. Vault가 개인 키를 인증서와 함께 생성·캐시해서 필요할 때 키를 나중에 검색할 수 있어요.

  1. 새 주문을 만들고 Vault가 반환한 order_id를 저장해요.
    $ vault write pki-external-ca/role/web-servers/new-order \
        identifiers="www.example.com,api.example.com"
    
  2. 주문 상태를 확인해 도전 과제 충족 준비가 될 때까지 기다려요(상태 awaiting_challenge_fulfillment). 참고: 식별자가 사전 승인된 경우 같은 상황에서는 도전 과제 충족 없이 주문이 바로 completed로 건너뛸 수 있어요.
    $ vault read pki-external-ca/role/web-servers/order/01936d8e-7c3a-7890-b123-456789abcdef/status
    
  3. DNS 프로바이더가 구성되어 있으면 Vault가 일치하는 식별자에 대한 DNS-01 도전 과제를 자동으로 처리해요. 5단계로 건너뛰세요. DNS 프로바이더가 없거나 식별자가 일치하지 않으면 각 식별자에 대한 도전 토큰과 auth 키를 Vault에서 가져와요.
    $ vault read pki-external-ca/role/web-servers/order/01936d8e-7c3a-7890-b123-456789abcdef/challenge \
        identifier="www.example.com" \
        challenge_type="http-01"
    
  4. ACME 서버가 기대하는 대로 도전 과제를 충족한(HTTP 토큰 또는 DNS 레코드 배치) 뒤 Vault에 알려요.
    $ vault write pki-external-ca/role/web-servers/order/01936d8e-7c3a-7890-b123-456789abcdef/fulfilled-challenge
    
  5. 인증서 발급이 완료될 때까지 기다렸다(상태 completed), 준비되면 인증서를 검색해요.
    $ vault read pki-external-ca/role/web-servers/order/01936d8e-7c3a-7890-b123-456789abcdef/fetch-cert
    

CSR 워크플로 — 이 워크플로에서는 자신의 개인 키와 CSR을 생성하고 Vault가 서명된 인증서를 반환해요.

  1. 로컬에서 개인 키와 CSR을 생성해요.
    $ openssl req -new -newkey rsa:2048 -nodes \
        -keyout server.key \
        -out server.csr \
        -subj "/CN=www.example.com"
    
  2. CSR로 주문을 만들어요([email protected]).

역할 구성 옵션(Role configuration options)

각 역할 내에서 사용할 ACME 계정, 허용 도메인, 도전 과제 충족 방법을 지정할 수 있어요.

허용 도메인 제한(Limit allowed domains) — 역할은 클라이언트가 요청할 수 있는 도메인을 제어하는 여러 옵션을 지원해요. 각 옵션은 Vault가 allowed_domains 값을 new-order 요청의 식별자와 일치시키는 방법에 영향을 줘요.

  • Bare domains — bare_domains 옵션을 사용해 하위 도메인 접두사 없이 일치하는 도메인만 허용해요. 예를 들어 다음 구성은 example.com과 www.example.com을 허용하지만 api.example.com과 sub.example.com은 거부해요.

    $ vault write pki-external-ca/role/example \
        acme_account_name="letsencrypt-prod" \
        allowed_domains="example.com,www.example.com" \
        allowed_domain_options="bare_domains"
    
  • Subdomains — subdomains 옵션을 사용해 특정 도메인의 하위 도메인과 www 접두사가 있는 도메인에 명시적으로 일치해요. 예를 들어 다음 구성은 example.com의 하위 도메인을 허용해요.

    $ vault write pki-external-ca/role/example \
        allowed_domains="example.com" \
        allowed_domain_options="subdomains"
    
  • Wildcards — wildcards 옵션을 사용해 DNS-01 도전 과제를 성공하는 것을 전제로 특정 도메인의 어떤 단일 하위 도메인에도 일치해요. 예를 들어 다음 구성은 *.<subdomain>.example.com 형태의 어떤 하위 도메인도 허용하지만 손자(grandchild) 도메인(*.*.example.com)은 허용하지 않아요.

    $ vault write pki-external-ca/role/example \
        allowed_domains="*.example.com" \
        allowed_domain_options="wildcards" \
        allowed_challenge_types="dns-01"
    
  • Globs — globs 옵션을 사용해 주어진 도메인의 glob 패턴에 일치해요. 예를 들어 다음 구성은 api.prod.example.com, web.staging.example.com, 또는 prod.example.com을 허용해요.

    $ vault write pki-external-ca/role/example \
        allowed_domains="*.prod.example.com,*.staging.example.com" \
        allowed_domain_options="globs"
    
  • Identity 템플릿(Identity templating) — allowed_domains 필드에 ACL 경로 템플릿을 사용해 인증된 엔티티에 기반한 동적 도메인 허용 목록을 만들어요. 예를 들어 다음 구성은 사용자 엔티티 이름과 요청된 하위 도메인이 일치하면 <username>.users.example.com을 허용해요.

    $ vault write pki-external-ca/role/user-certs \
        allowed_domains="{{identity.entity.name}}.users.example.com"
    

    사용자 "alice"가 인증서를 요청하면 alice.users.example.com은 허용되고 bob.users.example.com은 거부돼요.

    사용 가능한 템플릿 변수:

    • {{identity.entity.id}} — 엔티티 ID
    • {{identity.entity.name}} — 엔티티 이름
    • {{identity.entity.metadata.<key>}} — 엔티티 메타데이터
    • {{identity.groups.names.<group>}} — 그룹 멤버십

모니터링 및 문제 해결(Monitoring and troubleshooting)

주문 상태 확인(Check order status) — 주문 상태:

  • new — 주문 생성됨, 아직 ACME 서버에 제출되지 않음
  • submitted — ACME 서버에 제출됨
  • awaiting-challenge-fulfillment — 클라이언트가 도전 과제를 충족하길 기다리는 중
  • vault-challenge-fulfillment — Vault가 DNS 도전 과제를 충족하는 중
  • vault-challenge-propogating — Vault가 자체적으로 한 DNS 도전 과제의 내부 검증을 기다리는 중
  • notify-acme-server-challenges-completed — ACME 서버에 알릴 준비 완료
  • processing-challenge — ACME 서버가 도전 과제를 검증하는 중
  • fetching-certificate — 발급된 인증서를 검색하는 중(ACME 서버가 도전 과제를 수락함)
  • completed — 인증서 발급 성공
  • expired — 완료 전 주문 만료
  • revoked — Vault가 인증서를 폐기함
  • error — 오류 발생

활성 주문 나열하기(List active orders):

$ vault list pki-external-ca/role/web-servers/active-orders

시리얼로 인증서 조회(Lookup certificate by serial):

$ vault read pki-external-ca/lookup/cert/03:e7:1f:a2:3d

ACME 계정 세부 정보 보기(View ACME account details):

$ vault read pki-external-ca/config/acme-account/letsencrypt-prod

알려진 제한 사항(Known limitations)

  • 로드 밸런싱된 다중 인스턴스 애플리케이션(Load-balanced applications with multiple instances) — 현재 구현은 단일 오케스트레이터 또는 인스턴스가 인증서 요청을 관리할 때 가장 잘 작동해요. 로드 밸런서 뒤에 각 인스턴스가 Vault에서 독립적으로 인증서를 요청하는 여러 인스턴스를 배포하면 어려움이 생길 수 있어요: 여러 인스턴스가 같은 식별자에 대해 동시에 별도의 주문을 만들 수 있고, 각 주문은 그 주문에 고유한 도전 토큰을 생성하며, ACME CA가 도전 정보를 받은 인스턴스와 다른 인스턴스에 대해 도전 과제를 검증해 검증 실패나 인증서 발급 문제를 일으킬 수 있어요. 권장 해결 방법:
    • 단일 오케스트레이터 패턴: 하나의 인스턴스 또는 오케스트레이터가 인증서 요청을 처리하도록 지정해요. Vault가 인증서를 발급한 뒤 필요한 모든 인스턴스에 배포해요.
    • 캐싱이 있는 Identifier 워크플로: CSR 워크플로가 아닌 identifier 워크플로를 사용해 Vault가 인증서와 개인 키를 모두 생성·캐시하게 해요. 캐시된 뒤 클라이언트는 Vault에서 같은 인증서와 키를 가져올 수 있어요.
  • IP 주소 식별자(IP address identifiers) — 플러그인은 현재 IP 주소 식별자로 인증서를 요청하는 것을 지원하지 않아요.
  • ACME 갱신 정보(ACME Renewal Information, ARI) — 플러그인은 현재 ACME 서버가 클라이언트에 인증서를 갱신해야 할 때 알릴 수 있게 하는 ARI 확장(RFC 9773)을 지원하지 않아요. 결과적으로 클라이언트는 인증서 갱신 시점을 독립적으로 결정해야 해요.
  • 자동 ACME 계정 키 회전(Automatic ACME account key rotation) — 플러그인은 키 수명을 기준으로 ACME 계정 키를 자동 회전할 수 없어요. 필요에 따라 /config/acme-account/:name/rotate-key로 계정 키를 수동 회전할 것을 권장해요.

Vault Agent 통합(Vault Agent integration)

Vault Agent를 사용해 PKI external CA 마운트의 전체 인증서 수명 주기를 자동화해요. 구성 세부 정보는 Automate certificates with Vault Agent and PKI external CA를 참조하세요.

API 문서(API documentation)

자세한 API 문서는 PKI External CA API를 참조하세요.

더 알아보기 (Learn more)

  • PKI 시크릿 엔진 개요와 ACME·발급 프로토콜 문서를 확인해 보세요.
  • PKI External CA API 및 Vault Agent 통합 가이드를 참조하세요.