Vault를 사용하지 않고 OpenSSL을 활용하여 mTLS 구현
Root CA 인증서 생성을 위한 Root Key를 생성
cd cert
openssl genrsa -out root.key 2048
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-----
생성한 요청서(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
<...생략...>
[MacOS]
ca.crt 더블 클릭
키체인 접근
사이드바 시스템 -> 상단 인증서 탭 -> CN 값으로 지정한 이름의 인증서 더블클릭

신뢰 항목 -> 이 인증서 사용 시를 항상 신뢰로 변경

서비스 A 용 인증서를 생성하기 위한 Key 생성
openssl genrsa -aes256 -out service-a-with-pw.key 2048
openssl rsa -in service-a-with-pw.key -out service-a.key
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-----
서비스 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-----
서비스 B 용 인증서를 생성하기 위한 Key 생성
openssl genrsa -aes256 -out service-b-with-pw.key 2048
openssl rsa -in service-b-with-pw.key -out service-b.key
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-----
서비스 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-----
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
### 생략 ###
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의 경우 인증서 검증을 무시하기 위해 주석 처리### 생략 ###
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와 다르게 인증서 검증 위해 주석 제거cd python_service_a
python main.py
cd python_service_b
python main.py
curl https://service-a.example.com:7443
curl --insecure
Hello from "service-a"
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
curl --cacert ca.crt --key service-b.key --cert service-b.crt https://service-b.example.com:8443
Hello From "service-b"
서비스 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"
서비스 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)'))
서비스 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)'))
Vault의 PKI Secret Engine을 활용하여 mTLS 구현
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!
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"]
vault secrets enable pki
Success! Enabled the pki secrets engine at: pki/
vault secrets tune -max-lease-ttl=87600h pki
Success! Tuned the secrets engine at: pki/
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 경로에서 role을 생성하여 발급되는 인증서들은 모두 해당 Root CA에 종속됨Certificate Revocation List(인증서 해지 목록) 엔드포인트 작성.
클라이언트는 CRL의 URL에서 CRL을 다운로드 받아 인증서의 폐기여부를 확인

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
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
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에 종속)cd vault_agent
vault pollicy write pki pki_policy.hcl
Success! Uploaded policy: pki
vault auth enable approle
Success! Enabled approle auth method at: approle/
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
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
[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
secret_id 재발급 필요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"
}
.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 값 적당히 조절[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"
ca.cert 파일을 키체인에 새로 등록해 주어야 한다.Browser에서 https://service-a.example.com:7443/w-mtls 접속 시 인증서가 제대로 동작하는 것을 알 수 있다.