[Vault] Secret Engine - PKI

Hognod·2023년 6월 13일

1. OpenSSL을 이용한 mTLS

https://github.com/Great-Stone/vault-mtls-demo

Vault를 사용하지 않고 OpenSSL을 활용하여 mTLS 구현

1.1. Root CA 인증서

1.1.1. Root Key 생성

Root CA 인증서 생성을 위한 Root Key를 생성

cd cert
openssl genrsa -out root.key 2048

1.1.2. Root CA 요청서(CSR) 생성

root.key를 이용하여 Root CA 인증서 생성을 위한 요청서(CSR)를 생성

[XXX.conf 파일 예시]

구분작성 예
Country Name (국가코드)KR
State or Province Name (시/도의 전체이름)Seongnam-si
Locality Name (시/군/구 등의 이름)Bundang-gu
Organization (회사이름)DDIM
Organization Unit (부서명)Engineer
Common Name (SSL 인증서를 설치할 서버의 Full Domain)www.xxx.com
openssl req -config ca.conf -extensions usr_cert -new -key root.key -out ca.csr
openssl req -text -in ca.csr
Certificate Request:
    Data:
        Version: 0 (0x0)
        Subject: C=KR, ST=Seongnam-si, L=Bundang-gu, O=DDIM, OU=Engineer/emailAddress=example@example.com, CN=example root
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                RSA Public-Key: (2048 bit)
                Modulus:
                    <...생략...>
                Exponent: 65537 (0x10001)
        Attributes:
        Requested Extensions:
            X509v3 Basic Constraints:
                CA:TRUE
    Signature Algorithm: sha256WithRSAEncryption
         <...생략...>
-----BEGIN CERTIFICATE REQUEST-----
<...생략...>
-----END CERTIFICATE REQUEST-----

1.1.3. Root CA 인증서 생성

생성한 요청서(CSR)에 대해 자체 서명하여 Root CA 인증서를 생성

openssl x509 -req -days 3650 -in ca.csr -extfile ca.ext -signkey root.key -out ca.crt
  • -days: 인증서 기간 10년
  • -extfile: 서명시 추가 정보에 대한 내용
Signature ok
subject=/C=KR/ST=Seongnam-si/L=Bundang-gu/O=DDIM/OU=Engineer/emailAddress=example@example.com/CN=example root
Getting Private key
openssl x509 -text -noout -in ca.crt
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            cb:55:08:48:fa:7a:7d:61
    Signature Algorithm: sha256WithRSAEncryption
        Issuer: C=KR, ST=Seongnam-si, L=Bundang-gu, O=DDIM, OU=Engineer/emailAddress=example@example.com, CN=example root
        Validity
            Not Before: Apr  4 02:24:59 2023 GMT
            Not After : Apr  1 02:24:59 2033 GMT
        Subject: C=KR, ST=Seongnam-si, L=Bundang-gu, O=DDIM, OU=Engineer/emailAddress=example@example.com, CN=example root
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                RSA Public-Key: (2048 bit)
                Modulus:
                    <...생략...>
                Exponent: 65537 (0x10001)
        X509v3 extensions:
            X509v3 Basic Constraints:
                CA:TRUE
    Signature Algorithm: sha256WithRSAEncryption
         <...생략...>

1.1.4. Root CA 인증서 시스템 등록

[MacOS]

  1. ca.crt 더블 클릭

  2. 키체인 접근

  3. 사이드바 시스템 -> 상단 인증서 탭 -> CN 값으로 지정한 이름의 인증서 더블클릭
    Screenshot-0589715

  4. 신뢰 항목 -> 이 인증서 사용 시항상 신뢰로 변경
    Screenshot-0589900

1.2. 서비스 A 용 인증서

1.2.1. 서비스 A 용 Key 생성

서비스 A 용 인증서를 생성하기 위한 Key 생성

openssl genrsa -aes256 -out service-a-with-pw.key 2048
openssl rsa -in service-a-with-pw.key -out service-a.key
  • 패스워드 4자리 이상 입력 필요

1.2.2. 서비스 A 용 요청서(CSR) 생성

openssl req -config service-a.conf -new -key service-a.key -out service-a.csr
openssl req -text -in service-a.csr
Certificate Request:
    Data:
        Version: 0 (0x0)
        Subject: C=KR, ST=Seongnam-si, L=Bundang-gu, O=DDIM, OU=Engineer/emailAddress=example@example.com, CN=service-a.example.com
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                RSA Public-Key: (2048 bit)
                Modulus:
                    <...생략...>
                Exponent: 65537 (0x10001)
        Attributes:
            a0:00
    Signature Algorithm: sha256WithRSAEncryption
         <...생략...>
-----BEGIN CERTIFICATE REQUEST-----
<...생략...>
-----END CERTIFICATE REQUEST-----

1.2.3. 서비스 A 용 인증서 생성

서비스 A 용 인증서가 Root CA에 종속되도록 Root CA 인증서와 Root Key를 넣어 구성

openssl x509 -req -days 365 -in service-a.csr -extfile service-a.ext -CA ca.crt -CAkey root.key -CAcreateserial -out service-a.crt
  • -CA: Root CA 인증서를 지정

  • -CAkey: Roo Key를 지정

  • -CAcreateserial: 서명 작업에 Root CA가 서비스 A 용 인증서에 대한 일련번호 생성

openssl x509 -text -in service-a.crt
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            ec:71:b0:dd:72:c2:a2:52
    Signature Algorithm: sha256WithRSAEncryption
        Issuer: C=KR, ST=Seongnam-si, L=Bundang-gu, O=DDIM, OU=Engineer/emailAddress=example@example.com, CN=example root
        Validity
            Not Before: Apr  4 08:41:13 2023 GMT
            Not After : Apr  3 08:41:13 2024 GMT
        Subject: C=KR, ST=Seongnam-si, L=Bundang-gu, O=DDIM, OU=Engineer/emailAddress=example@example.com, CN=service-a.example.com
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                RSA Public-Key: (2048 bit)
                Modulus:
                    <...생략...>
                Exponent: 65537 (0x10001)
        X509v3 extensions:
            X509v3 Subject Alternative Name:
                DNS:service-a.example.com
    Signature Algorithm: sha256WithRSAEncryption
         <...생략...>
-----BEGIN CERTIFICATE-----
<...생략...>
-----END CERTIFICATE-----

1.3. 서비스 B 용 인증서

1.3.1. 서비스 B 용 Key 생성

서비스 B 용 인증서를 생성하기 위한 Key 생성

openssl genrsa -aes256 -out service-b-with-pw.key 2048
openssl rsa -in service-b-with-pw.key -out service-b.key
  • 패스워드 4자리 이상 입력 필요

1.3.2. 서비스 B 용 요청서(CSR) 생성

openssl req -config service-b.conf -new -key service-b.key -out service-b.csr
openssl req -text -in service-b.csr
Certificate Request:
    Data:
        Version: 0 (0x0)
        Subject: C=KR, ST=Seongnam-si, L=Bundang-gu, O=DDIM, OU=Engineer/emailAddress=example@example.com, CN=service-b.example.com
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                RSA Public-Key: (2048 bit)
                Modulus:
                    <...생략...>
                Exponent: 65537 (0x10001)
        Attributes:
            a0:00
    Signature Algorithm: sha256WithRSAEncryption
         <...생략...>
-----BEGIN CERTIFICATE REQUEST-----
<...생략...>
-----END CERTIFICATE REQUEST-----

1.3.3. 서비스 B 용 인증서 생성

서비스 B 용 인증서가 Root CA에 종속되도록 Root CA 인증서와 Root Key를 넣어 구성

openssl x509 -req -days 365 -in service-b.csr -extfile service-b.ext -CA ca.crt -CAkey root.key -CAcreateserial -out service-b.crt
  • -CA: Root CA 인증서를 지정

  • -CAkey: Roo Key를 지정

  • -CAcreateserial: 서명 작업에 Root CA가 서비스 A 용 인증서에 대한 일련번호 생성

openssl x509 -text -in service-b.crt
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            ec:71:b0:dd:72:c2:a2:53
    Signature Algorithm: sha256WithRSAEncryption
        Issuer: C=KR, ST=Seongnam-si, L=Bundang-gu, O=DDIM, OU=Engineer/emailAddress=example@example.com, CN=example root
        Validity
            Not Before: Apr  4 08:49:25 2023 GMT
            Not After : Apr  3 08:49:25 2024 GMT
        Subject: C=KR, ST=Seongnam-si, L=Bundang-gu, O=DDIM, OU=Engineer/emailAddress=example@example.com, CN=service-b.example.com
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                RSA Public-Key: (2048 bit)
                Modulus:
                    <...생략...>
                Exponent: 65537 (0x10001)
        X509v3 extensions:
            X509v3 Subject Alternative Name:
                DNS:service-b.example.com
    Signature Algorithm: sha256WithRSAEncryption
         <...생략...>
-----BEGIN CERTIFICATE-----
<...생략...>
-----END CERTIFICATE-----

1.4. Demo App을 이용한 Test

1.4.1. Preparation

pip install requests flask
sudo vi /etc/hosts
##
# Host Database
#
# localhost is used to configure the loopback interface
# when the system is booting.  Do not change this entry.
##
127.0.0.1	      localhost service-a.example.com service-b.example.com
255.255.255.255	broadcasthost
::1             localhost

1.4.2. Python Demo App 실행

1.4.2.1. 서비스 A Python Demo App 내용
### 생략 ###
if __name__ == "__main__":
    app.debug = True
    ssl_context = ssl.create_default_context(purpose=ssl.Purpose.CLIENT_AUTH, cafile='../cert/ca.crt')
    ssl_context.load_cert_chain(certfile=f'../cert/{src}.crt', keyfile=f'../cert/{src}.key', password='')
    # ssl_context.verify_mode = ssl.CERT_REQUIRED
    app.run(host="0.0.0.0", port=src_port, ssl_context=ssl_context, use_reloader=True, extra_files=[f'../cert/{src}.crt'])
  • ssl.create_default_context: ssl context를 정의. cafile에 Root CA 파일을 지정
  • ssl_context.load_cert_chain: 인증서 파일과 Key 파일을 지정하여 인증서 체인을 설정
  • ssl_context.verify_mode: 인증서 검증을 실행하도록 설정. 서비스 A의 경우 인증서 검증을 무시하기 위해 주석 처리
1.4.2.2. 서비스 B Python Demo App 내용
### 생략 ###
if __name__ == "__main__":
    app.debug = True
    ssl_context = ssl.create_default_context(purpose=ssl.Purpose.CLIENT_AUTH, cafile='../cert/ca.crt')
    ssl_context.load_cert_chain(certfile=f'../cert/{src}.crt', keyfile=f'../cert/{src}.key', password='')
    ssl_context.verify_mode = ssl.CERT_REQUIRED
    app.run(host="0.0.0.0", port=src_port, ssl_context=ssl_context, use_reloader=True, extra_files=[f'../cert/{src}.crt'])
  • ssl_context.verify_mode: 인증서 검증을 실행하도록 설정. 서비스 B의 경우 서비스 A와 다르게 인증서 검증 위해 주석 제거
1.4.2.3. 서비스 A 실행
cd python_service_a
python main.py
1.4.2.4. 서비스 B 실행
cd python_service_b
python main.py

1.4.3. Test API

1.4.3.1. 서비스 A Check
curl https://service-a.example.com:7443
curl --insecure 
Hello from "service-a"
  • 서비스 A의 경우 인증서 검증을 무시하기 때문에 정상 메시지 출력
1.4.3.2. 서비스 B Check
curl https://service-b.example.coom:8443
curl: (56) LibreSSL SSL_read: LibreSSL/3.3.6: error:1404C45C:SSL routines:ST_OK:reason(1116), errno 0
  • 서비스 B의 경우 인증서 검증을 수행하기 때문에 인증서 요청 에러 출력
curl --cacert ca.crt --key service-b.key --cert service-b.crt https://service-b.example.com:8443
  • Root CA, 서비스 B 용 Key, 서비스 B 용 인증서를 함께 제공하여 명령어 수행
Hello From "service-b"
  • 인증서 검증을 통과하여 정상 메시지 출력
1.4.3.3. Normal mTLS Check

서비스 A에서 서비스 B로 요청 시, 서비스 A 용 인증서, 서비스 A 용 Key, Root CA 인증서 모두 설정한 경우

[Demo App Code]

result = requests.get(f'https://{des}.example.com:{des_port}',
         cert=(f'../cert/{src}.crt', f'../cert/{src}.key'),
         verify='../cert/ca.crt')
curl https://service-a.example.com:7443/w-mtls
Hello From "service-b"
1.4.3.4. Without Cert

서비스 A에서 서비스 B로 요청 시, 서비스 A 용 인증서, 서비스 A용 Key를 설정하지 않는 경우

[Demo App Code]

result = requests.get(f'https://{des}.example.com:{des_port}',
         verify='../cert/ca.crt')
curl https://service-a.example.com:7443/wo-cert-mtls
SSLError(SSLEOFError(8, 'EOF occurred in violation of protocol (_ssl.c:2393)'))
1.4.3.5. Without Root CA

서비스 A에서 서비스 B로 요청 시, Root CA 인증서를 설정하지 않는 경우

[Demo App Code]

result = requests.get(f'https://{des}.example.com:{des_port}',
         cert=(f'../cert/{src}.crt', f'../cert/{src}.key'))
curl https://service-a.example.com:7443/wo-ca-mtls
SSLError(SSLCertVerificationError(1, '[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: self signed certificate in certificate chain (_ssl.c:992)'))
  • 자체 서명 인증서를 요구하는 에러 메시지 출력

2. Vault PKI Secret Engine을 이용한 mTLS

Vault의 PKI Secret Engine을 활용하여 mTLS 구현

2.1. Vault 설정

2.1.1. Vault Dev 서버 실행

vault server -dev -dev-root-token-id=root
  • -dev: Vault Dev 모드로 실행
  • -dev-root-token-id: Dev 모드에서만 적용. 지정한 문자열으로 Initial root token을 대체.
WARNING! dev mode is enabled! In this mode, Vault runs entirely in-memory
and starts unsealed with a single unseal key. The root token is already
authenticated to the CLI, so you can immediately begin using Vault.

You may need to set the following environment variables:

    $ export VAULT_ADDR='http://127.0.0.1:8200'

The unseal key and root token are displayed below in case you want to
seal/unseal the Vault or re-authenticate.

Unseal Key: OFT...HE=
Root Token: root

Development mode should NOT be used in production installations!

2.1.2. Vault 환경변수 설정

export VAULT_ADDR="http://127.0.0.1:8200"
vault login
Token (will be hidden): root
Success! You are now authenticated. The token information displayed below
is already stored in the token helper. You do NOT need to run "vault login"
again. Future Vault requests will automatically use this token.

Key                  Value
---                  -----
token                root
token_accessor       x2Pvp47BRlhEFyK35hACDIze
token_duration       ∞
token_renewable      false
token_policies       ["root"]
identity_policies    []
policies             ["root"]

2.2. PKI 설정

2.2.1. PKI Secret Engine 활성화

vault secrets enable pki
Success! Enabled the pki secrets engine at: pki/

2.2.2. PKI Secret Engine TTL 변경

vault secrets tune -max-lease-ttl=87600h pki
  • Default TTL은 32일(768h)
  • 10년(87600h) 으로 설정
Success! Tuned the secrets engine at: pki/

2.2.3. Root CA 생성

vault write pki/root/generate/internal \
    key_bits=2048 \
    private_key_format=pem \
    signature_bits=256 \
    country=KR \
    province=Seongnam-si \
    locality=Bundang-gu \
    organization=DDIM \
    ou=Engineer \
    common_name=example.com \
    ttl=87600h
Key              Value
---              -----
certificate      -----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----
expiration       1996496548
issuer_id        86b171c1-20fa-0641-0b9d-91f4eb4ae976
issuer_name      n/a
issuing_ca       -----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----
key_id           f0146593-889e-107e-3214-b2a2eaea36c0
key_name         n/a
serial_number    70:24:6b:a9:3b:17:11:fb:1c:b2:36:a1:27:e9:5e:06:3a:ca:49:ba
  • PKI Secret Engine pki 경로에서 role을 생성하여 발급되는 인증서들은 모두 해당 Root CA에 종속됨

2.2.4. CRL 생성

Certificate Revocation List(인증서 해지 목록) 엔드포인트 작성.
클라이언트는 CRLURL에서 CRL을 다운로드 받아 인증서의 폐기여부를 확인

Screenshot

vault write pki/config/urls \
    issuing_certificates="http://127.0.0.1:8200/v1/pki/ca" \
    crl_distribution_points="http://127.0.0.1:8200/v1/pki/crl"
Success! Data written to: pki/config/urls

2.2.5. Role 생성

https://developer.hashicorp.com/vault/api-docs/secret/pki#create-update-role

vault write pki/roles/example-dot-com \
    allowed_domains="example.com" \
    allow_bare_domains=true \
    allowed_wildcard_certificates=true \
    allow_subdomains=true \
    max_ttl="720h"
Success! Data written to: pki/roles/example-dot-com

2.2.6. 인증서 발급

vault write pki/issue/example-dot-com \
    common_name=service-a.example.com
Key                 Value
---                 -----
ca_chain            [-----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----]
certificate         -----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----
expiration          1683986821
issuing_ca          -----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----
private_key         -----BEGIN RSA PRIVATE KEY-----
...
-----END RSA PRIVATE KEY-----
private_key_type    rsa
serial_number       1b:dc:6d:1e:a4:44:41:b6:ec:41:6b:a1:3d:43:56:6c:b0:11:5e:63
  • ca_chain, issuing_ca: 2.3. 과정의 Root CA의 값과 동일(Root CA에 종속)

2.3. Vault Agent 설정

2.3.1. Vault Agent 정책 설정

cd vault_agent
vault pollicy write pki pki_policy.hcl
Success! Uploaded policy: pki

2.3.2. Vault Agent 용 approle 인증 설정

2.3.2.1. Vault Agent 용 approle 활성화
vault auth enable approle
Success! Enabled approle auth method at: approle/
2.3.2.2. Vault Agent 용 approle의 role 생성
vault write auth/approle/role/pki-agent \
	secret_id_ttl=120m \
	token_ttl=60m \
	token_max_tll=120m \
	policies="pki"
Success! Data written to: auth/approle/role/pki-agent
2.3.2.3. RoleID 저장
vault read -field=role_id auth/approle/role/pki-agent/role-id > roleid
  • -field: Command의 Output 중 특정 항목만 출력

일반적인 RoleID 정보 출력

vault read auth/approle/role/pki-agent/role-id

[Output]

Key        Value
---        -----
role_id    bc486c38-d7be-a685-4026-3b46e1ab634e
2.3.2.4. SecretID 생성 및 저장

[Command]

vault write -f -field=secret_id auth/approle/role/pki-agent/secret-id > secretid
  • -field: Command의 Output 중 특정 항목만 출력

일반적인 SecretID 생성 및 출력

[Command]

vault write -f auth/approle/role/pki-agent/secret-id

[Output]

Key                   Value
---                   -----
secret_id             2adb95a6-fa31-c248-9ab9-17bd152e7a03
secret_id_accessor    5d35140f-6158-8f2f-1af4-1f2481a17228
secret_id_num_uses    0
secret_id_ttl         2h
  • Vault Agent 재기동 시 secret_id 재발급 필요

2.3.3. Template 확인 및 수정

Vault Agent는 Template을 사용하여 Secret을 특정 파일로 랜더링 가능

[vault_agent.hcl 파일 예시]

template {
  source      = "ca-a.tpl"
  destination = "../cert/ca.crt"
}

template {
  source      = "cert-a.tpl"
  destination = "../cert/service-a.crt"
}

template {
  source      = "key-a.tpl"
  destination = "../cert/service-a.key"
}

template {
  source      = "cert-b.tpl"
  destination = "../cert/service-b.crt"
}

template {
  source      = "key-b.tpl"
  destination = "../cert/service-b.key"
}
  • Template(.tpl 파일)에 대한 랜더링 결과를 특정 파일에 저장하도록 명시

[ca-a.tpl 파일 예시]

{{- /* ca-a.tpl */ -}}
{{ with secret "pki/issue/example-dot-com" "common_name=service-a.example.com" "ttl=2m" }}
{{ .Data.issuing_ca }}{{ end }}
  • pki/issue/example-dot-com에서 common_name=service-a.example.com인 인증서를 발급
  • .Data.issuing_ca: Vault로부터 받는 결과 중 issuing_ca 값을 추출

[Command]

vi ca-a.tpl
{{- /* ca-a.tpl */ -}}
{{ with secret "pki/issue/example-dot-com" "common_name=service-a.example.com" "ttl=10h" }}
{{ .Data.issuing_ca }}{{ end }}
  • ttl 값 적당히 조절

2.3.4. Vault Agent 실행

[Command]

vault agent -config=vault_agent.hcl -log-level=debug

[Output]

2023-04-13T14:07:42.797+0900 [DEBUG] (runner) rendering "ca-a.tpl" => "../cert/ca.crt"
2023-04-13T14:07:42.797+0900 [DEBUG] (runner) checking template a04612e63b9a03a45ef968a8984a23db
2023-04-13T14:07:42.797+0900 [DEBUG] (runner) rendering "cert-a.tpl" => "../cert/service-a.crt"
2023-04-13T14:07:42.797+0900 [DEBUG] (runner) checking template 850589d81f7afe64c7c5a0a8440c8569
2023-04-13T14:07:42.797+0900 [DEBUG] (runner) rendering "key-a.tpl" => "../cert/service-a.key"
2023-04-13T14:07:42.797+0900 [DEBUG] (runner) checking template 60e7f2683d2c76a501eb54879bf89ad2
2023-04-13T14:07:42.797+0900 [DEBUG] (runner) rendering "cert-b.tpl" => "../cert/service-b.crt"
2023-04-13T14:07:42.801+0900 [INFO] (runner) rendered "cert-b.tpl" => "../cert/service-b.crt"
2023-04-13T14:07:42.801+0900 [DEBUG] (runner) checking template 1fb22b9f15857b7eeb0b68a3c9ac6d20
2023-04-13T14:07:42.802+0900 [DEBUG] (runner) rendering "key-b.tpl" => "../cert/service-b.key"
2023-04-13T14:07:42.807+0900 [INFO] (runner) rendered "key-b.tpl" => "../cert/service-b.key"
  • Vault Agent가 새로 생성한 ca.cert 파일을 키체인에 새로 등록해 주어야 한다.

Browser에서 https://service-a.example.com:7443/w-mtls 접속 시 인증서가 제대로 동작하는 것을 알 수 있다.

0개의 댓글