[Linux] ๐ OpenSSL๋ก PFX ์ธ์ฆ์ ๋ณํ ํ Kubernetes TLS Secret ๋ง๋ค๊ธฐ
Windows์์ ๋ฐ๊ธ๋ฐ์ PFX(PKCS#12) ์ธ์ฆ์๋ฅผ OpenSSL๋ก PEM ํ์์ ํคยท์ธ์ฆ์๋ก ๋ถ๋ฆฌํ๊ณ , kubectl๋ก Kubernetes TLS Secret์ ์์ฑํ๋ ์ ์ฒด ๊ณผ์ ์ ์ ๋ฆฌํ์ต๋๋ค.
์ด ๊ธ์์๋ Windows IIS๋ CA์์ ๋ฐ๊ธํ PFX(PKCS#12) ์ธ์ฆ์ ํ์ผ์ OpenSSL ๋ช
๋ น์ผ๋ก ๊ฐ์ธํค(.key)์ ์ธ์ฆ์ ์ฒด์ธ(.crt) PEM ํ์ผ๋ก ๋ถ๋ฆฌํ๊ณ , ์ด๋ฅผ kubectl create secret tls๋ก Kubernetes TLS Secret ๋งค๋ํ์คํธ๋ก ๋ณํํ๋ ์ํฌํ๋ก์ฐ๋ฅผ ๋จ๊ณ๋ณ๋ก ๋ค๋ฃน๋๋ค. IngressยทArgo CD ๋ฑ์์ ์ฌ์ฉํ TLS Secret์ ๋ง๋ค ๋ ๊ทธ๋๋ก ํ์ฉํ ์ ์์ต๋๋ค.
๐ฆ PFX(PKCS#12) ํ์ผ์ด๋?
PFX๋ PKCS#12 ํ์ค์ ๋ฐ๋ฅด๋ ๋ฐ์ด๋๋ฆฌ ํ์์ ์ธ์ฆ์ ์ปจํ ์ด๋์ ๋๋ค. ํ๋์ ํ์ผ ์์ ๊ฐ์ธํค + ์ธ์ฆ์ + ์ค๊ฐ CA ์ฒด์ธ์ด ๋ชจ๋ ํจ์ค์๋๋ก ์ํธํ๋์ด ๋ค์ด ์์ด, Windows ํ๊ฒฝ์์ ์ธ์ฆ์๋ฅผ ๋ฐฑ์ ํ๊ฑฐ๋ ์ด๊ดํ ๋ ์ฃผ๋ก ์ฌ์ฉ๋ฉ๋๋ค.
๋ฐ๋ฉด Linux ์ง์(Nginx, HAProxy, Kubernetes Ingress ๋ฑ)์ ์ผ๋ฐ์ ์ผ๋ก PEM ํ์์ ๋ณ๋ ํ์ผ(.key, .crt)์ ์๊ตฌํฉ๋๋ค. ๋ฐ๋ผ์ PFX๋ฅผ ๊ทธ๋๋ก ์ฌ์ฉํ ์ ์๊ณ OpenSSL๋ก ๋ณํํด์ผ ํฉ๋๋ค.
| ํญ๋ชฉ | PFX (PKCS#12) | PEM |
|---|---|---|
| ํ์ | ๋ฐ์ด๋๋ฆฌ | Base64 ํ ์คํธ |
| ํ์ฅ์ | .pfx, .p12 | .key, .crt, .pem |
| ๊ตฌ์ฑ | ํค + ์ธ์ฆ์ + ์ฒด์ธ ํตํฉ | ํ์ผ๋ณ ๋ถ๋ฆฌ |
| ์ํธํ | ํญ์ ํจ์ค์๋ ๋ณดํธ | ์ ํ์ (ํค๋ง ์ํธํ ๊ฐ๋ฅ) |
| ์ฃผ ์ฌ์ฉ์ฒ | Windows, IIS | Linux, Nginx, Kubernetes |
๐ ๏ธ ์ฌ์ ์ค๋น
OpenSSL๊ณผ kubectl์ด ์ค์น๋์ด ์์ด์ผ ํฉ๋๋ค.
1
2
3
4
openssl version
# OpenSSL 3.0.x ์ด์ ๊ถ์ฅ
kubectl version --client
์์ ํ PFX ํ์ผ๊ณผ ๊ทธ ํ์ผ์ ํจ์ค์๋๋ฅผ ๋ฏธ๋ฆฌ ์ค๋นํด ๋ก๋๋ค. ์ด ๊ธ์์๋ ๋ค์๊ณผ ๊ฐ์ ๋ณ์๋ก ์งํํ๋ค๊ณ ๊ฐ์ ํ๊ฒ ์ต๋๋ค.
1
2
3
4
5
6
7
PFX_FILE="example.com.pfx"
PFX_PASS="ChangeMeStrongPass" # PFX ์๋ณธ ํจ์ค์๋
TMP_PASS="ChangeMeTempPass" # ์ค๊ฐ ์์
์ฉ ์์ ํจ์ค์๋
KEY_OUT="tls.key"
CRT_OUT="fullchain.crt"
NAMESPACE="argocd"
SECRET_NAME="tls-example-com"
โ ๏ธ ์ค์ ์ด์ ํ๊ฒฝ์์๋ ํจ์ค์๋๋ฅผ ์ ธ ํ์คํ ๋ฆฌ์ ๋จ๊ธฐ์ง ์๋๋ก
-passin file:...๋๋ ํ๊ฒฝ๋ณ์(-passin env:VAR)๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ด ์์ ํฉ๋๋ค.
1๏ธโฃ PFX์์ ๊ฐ์ธํค ์ถ์ถํ๊ธฐ
๋จผ์ PFX์์ ๊ฐ์ธํค๋ง ๋ถ๋ฆฌํฉ๋๋ค. ์ด ์์ ์์๋ ํค๊ฐ ์ฌ์ ํ ์์ ํจ์ค์๋(TMP_PASS)๋ก ์ํธํ๋ PEM ํ์์ผ๋ก ์ถ๋ ฅ๋ฉ๋๋ค.
1
2
3
4
5
6
openssl pkcs12 \
-passin pass:${PFX_PASS} \
-passout pass:${TMP_PASS} \
-in ${PFX_FILE} \
-nocerts \
-out tmp.key
| ์ต์ | ์ค๋ช |
|---|---|
pkcs12 | PKCS#12 ํ์ ์ฒ๋ฆฌ ์๋ธ์ปค๋งจ๋ |
-passin pass:... | ์ ๋ ฅ PFX ํ์ผ์ ํจ์ค์๋ |
-passout pass:... | ์ถ๋ ฅ PEM ํค์ ์ ์ฉํ ํจ์ค์๋ |
-in | ์ ๋ ฅ PFX ํ์ผ ๊ฒฝ๋ก |
-nocerts | ์ธ์ฆ์๋ ์ ์ธํ๊ณ ํค๋ง ์ถ์ถ |
-out | ์ถ๋ ฅ ํ์ผ ๊ฒฝ๋ก |
Tip:
-passout์ ๋นผ๋ฉด OpenSSL์ด ๋ํํ์ผ๋ก ํจ์ค์๋๋ฅผ ๋ฌผ์ด๋ด ๋๋ค. ์๋ํ ์คํฌ๋ฆฝํธ์์๋ ๋ช ์์ ์ผ๋ก ์ง์ ํ๋ ํธ์ด ์์ ์ ์ ๋๋ค.
2๏ธโฃ ๊ฐ์ธํค ํจ์ค์๋ ์ ๊ฑฐ (๋ณตํธํ)
Kubernetes TLS Secret๊ณผ ๋๋ถ๋ถ์ Ingress ์ปจํธ๋กค๋ฌ๋ ํจ์ค์๋๊ฐ ์๋ PEM ํค๋ฅผ ์๊ตฌํฉ๋๋ค. ๋ฐ๋ผ์ ์์ ํจ์ค์๋๋ก ์ํธํ๋ ํค๋ฅผ ํ๋ฌธ RSA ํค๋ก ๋ณํํฉ๋๋ค.
1
2
3
4
openssl rsa \
-passin pass:${TMP_PASS} \
-in tmp.key \
-out ${KEY_OUT}
๋ช ๋ น์ด ์ฑ๊ณตํ๋ฉด ๋ค์๊ณผ ๊ฐ์ ๋ฉ์์ง๊ฐ ์ถ๋ ฅ๋ฉ๋๋ค.
1
writing RSA key
์์ฑ๋ ${KEY_OUT} ํ์ผ์ ๋ค์๊ณผ ๊ฐ์ด ์์ํฉ๋๋ค.
1
2
3
-----BEGIN RSA PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQ...
-----END RSA PRIVATE KEY-----
โ ๏ธ ํ๋ฌธ ๊ฐ์ธํค๋ ๋ ธ์ถ ์ ์ฆ์ ์ธ์ฆ์ ํ๊ธฐ ์ฌ์ ๊ฐ ๋ฉ๋๋ค. ์์ ์ด ๋๋๋ฉด ์์ ํ์ผ(
tmp.key)์ ๋ฐ๋์ ์ญ์ ํ์ธ์.
3๏ธโฃ ์ธ์ฆ์ ์ฒด์ธ ์ถ์ถํ๊ธฐ
์ด๋ฒ์๋ ํค๋ฅผ ์ ์ธํ ํด๋ผ์ด์ธํธ ์ธ์ฆ์์ ์ค๊ฐ CA ์ธ์ฆ์๋ฅผ ํ ํ์ผ๋ก ๋ฌถ์ด ์ถ์ถํฉ๋๋ค.
1
2
3
4
5
6
openssl pkcs12 \
-passin pass:${PFX_PASS} \
-in ${PFX_FILE} \
-clcerts \
-nokeys \
-out ${CRT_OUT}
| ์ต์ | ์ค๋ช |
|---|---|
-clcerts | ํด๋ผ์ด์ธํธ(์๋ฒ) ์ธ์ฆ์๋ง ์ถ๋ ฅ |
-nokeys | ๊ฐ์ธํค๋ ์ ์ธ |
์ฒด์ธ์ด ๋ถ๋ฆฌ๋์ด ์์ด ํ์ฒด์ธ(fullchain)์ผ๋ก ๋ฌถ๊ณ ์ถ๋ค๋ฉด -clcerts ์ต์
์ ๋นผ๊ณ , ์ถ๋ ฅ๋ ์ธ์ฆ์ ๋ธ๋ก ์ค ํ์ํ ๊ฒ์ ํ์ธํด ์ ๋ ฌํฉ๋๋ค.
1
2
3
4
5
openssl pkcs12 \
-passin pass:${PFX_PASS} \
-in ${PFX_FILE} \
-nokeys \
-out fullchain.crt
Tip: Ingress์์ SSL Labs A+ ๋ฑ๊ธ์ ๋ฐ์ผ๋ ค๋ฉด ์๋ฒ ์ธ์ฆ์ + ์ค๊ฐ CA ์์๋ก ์ ๋ ฌ๋ ํ์ฒด์ธ์ด ํ์ํฉ๋๋ค.
4๏ธโฃ ์ถ์ถ ๊ฒฐ๊ณผ ๊ฒ์ฆํ๊ธฐ
๋ณํ์ด ์ ์์ ์ผ๋ก ๋๋ฌ๋์ง ๋ฐ๋์ ํ์ธํฉ๋๋ค.
์ธ์ฆ์ ์ ๋ณด ํ์ธ
1
openssl x509 -in ${CRT_OUT} -noout -subject -issuer -dates
1
2
3
4
subject=CN=example.com
issuer=CN=Internal Issuing CA
notBefore=Jan 1 00:00:00 2026 GMT
notAfter=Dec 31 23:59:59 2026 GMT
๊ฐ์ธํค์ ์ธ์ฆ์๊ฐ ํ ์์ธ์ง ํ์ธ (๋ชจ๋๋ฌ์ค ๋น๊ต)
1
2
3
diff \
<(openssl rsa -in ${KEY_OUT} -modulus -noout) \
<(openssl x509 -in ${CRT_OUT} -modulus -noout)
์ถ๋ ฅ์ด ์์ผ๋ฉด ๊ฐ์ ์์ ๋๋ค. ๋ค๋ฅด๋ฉด ์๋ชป๋ ํค/์ธ์ฆ์ ์กฐํฉ์ด๋ฏ๋ก Secret์ ๋ง๋ค์ด๋ TLS ํธ๋์ ฐ์ดํฌ๊ฐ ์คํจํฉ๋๋ค.
5๏ธโฃ Kubernetes TLS Secret YAML ๋ง๋ค๊ธฐ
kubectl create secret tls์ --dry-run=client -o yaml์ ๋ถ์ด๋ฉด ํด๋ฌ์คํฐ์ ๋ฐ์ํ์ง ์๊ณ ๋งค๋ํ์คํธ๋ง ์์ฑํ ์ ์์ต๋๋ค.
1
2
3
4
5
kubectl create secret tls ${SECRET_NAME} \
-n ${NAMESPACE} \
--key ${KEY_OUT} \
--cert ${CRT_OUT} \
--dry-run=client -o yaml > tls-example-com.yaml
์์ฑ๋ ๋งค๋ํ์คํธ๋ ๋ค์๊ณผ ๊ฐ์ ํํ์ ๋๋ค.
1
2
3
4
5
6
7
8
9
apiVersion: v1
kind: Secret
metadata:
name: tls-example-com
namespace: argocd
type: kubernetes.io/tls
data:
tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUZ...
tls.key: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpN...
Tip: Git์ ์ปค๋ฐํ๋ค๋ฉด ํ๋ฌธ Secret ๋์ Sealed Secrets๋ External Secrets Operator๋ก ์ํธํํ๋ ๊ฒ์ด ์ข์ต๋๋ค.
6๏ธโฃ ํด๋ฌ์คํฐ์ ์ ์ฉ ๋ฐ Ingress ์ฐ๊ฒฐ
์์ฑํ ๋งค๋ํ์คํธ๋ฅผ ์ ์ฉํฉ๋๋ค.
1
2
3
4
5
kubectl apply -f tls-example-com.yaml
kubectl get secret ${SECRET_NAME} -n ${NAMESPACE}
# NAME TYPE DATA AGE
# tls-example-com kubernetes.io/tls 2 5s
์ดํ Ingress ๋ฆฌ์์ค์์ tls.secretName์ผ๋ก ์ฐธ์กฐํฉ๋๋ค.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: argocd-server-ingress
namespace: argocd
spec:
ingressClassName: nginx
tls:
- hosts:
- example.com
secretName: tls-example-com
rules:
- host: example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: argocd-server
port:
number: 443
๐งน ๋ง๋ฌด๋ฆฌ ์ ๋ฆฌ
์์ ์ด ๋๋๋ฉด ๋ฏผ๊ฐ ํ์ผ์ ์์ ํ๊ฒ ์ ๋ฆฌํฉ๋๋ค.
1
shred -u tmp.key ${KEY_OUT} 2>/dev/null || rm -f tmp.key ${KEY_OUT}
โ ๏ธ
shred๋ ext4 ๋ฑ ์ผ๋ถ ํ์ผ์์คํ ์์๋ง ํจ๊ณผ์ ์ ๋๋ค. SSDยท๋ณต์ฌ๋ณธ ์บ์ยทtmpfs์์๋ ์์ ์ญ์ ๊ฐ ๋ณด์ฅ๋์ง ์์ผ๋ฏ๋ก ์์ ๋๋ ํฐ๋ฆฌ ์์ฒด๋ฅผ ์์ ํ ์์น(์:tmpfs๋ง์ดํธ)๋ก ์ก๋ ํธ์ด ์ข์ต๋๋ค.
๐ ํ ๋ฒ์ ์ฒ๋ฆฌํ๋ ์คํฌ๋ฆฝํธ
์ ๋จ๊ณ๋ฅผ ์๋ํํ ์์ ์คํฌ๋ฆฝํธ์ ๋๋ค.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#!/usr/bin/env bash
set -euo pipefail
PFX_FILE="${1:?usage: $0 <pfx> <pfx-pass> <namespace> <secret-name>}"
PFX_PASS="${2}"
NAMESPACE="${3}"
SECRET_NAME="${4}"
TMP_PASS="$(openssl rand -hex 8)"
WORKDIR="$(mktemp -d)"
trap 'rm -rf "${WORKDIR}"' EXIT
openssl pkcs12 -passin "pass:${PFX_PASS}" -passout "pass:${TMP_PASS}" \
-in "${PFX_FILE}" -nocerts -out "${WORKDIR}/tmp.key"
openssl rsa -passin "pass:${TMP_PASS}" \
-in "${WORKDIR}/tmp.key" -out "${WORKDIR}/tls.key"
openssl pkcs12 -passin "pass:${PFX_PASS}" \
-in "${PFX_FILE}" -nokeys -out "${WORKDIR}/tls.crt"
kubectl create secret tls "${SECRET_NAME}" \
-n "${NAMESPACE}" \
--key "${WORKDIR}/tls.key" \
--cert "${WORKDIR}/tls.crt" \
--dry-run=client -o yaml
โ ์์ฃผ ๋ฌป๋ ์ง๋ฌธ
Q. -nodes ์ต์
์ ํ ๋ฒ์ ์ฐ๋ฉด ๋์ง ์๋์?
๊ฐ๋ฅํฉ๋๋ค. openssl pkcs12 -in file.pfx -nodes -out all.pem ํ ์ค๋ก ํค์ ์ธ์ฆ์๋ฅผ ํ๋ฌธ์ผ๋ก ํจ๊ป ์ถ์ถํ ์ ์์ต๋๋ค. ๋ค๋ง ํค/์ธ์ฆ์๋ฅผ ํ ํ์ผ์ ์์ผ๋ฉด kubectl create secret tls์ฒ๋ผ ๋ ํ์ผ์ ์๊ตฌํ๋ ๋๊ตฌ์์ ๋ค์ ๋ถ๋ฆฌํด์ผ ํ๋ฏ๋ก, ๋จ๊ณ๋ณ๋ก ์ถ์ถํ๋ ํธ์ด ์๋ํ์ ์ ๋ฆฌํฉ๋๋ค.
Q. โMac verify error: invalid passwordโ ์ค๋ฅ๊ฐ ๋ฉ๋๋ค.
PFX ํ์ผ์ ํจ์ค์๋๊ฐ ํ๋ฆฐ ๊ฒฝ์ฐ์
๋๋ค. ์ผ๋ถ ํ๊ฒฝ์์๋ OpenSSL 3.x์์ ๊ตฌ๋ฒ์ PFX์ ํด์ ์๊ณ ๋ฆฌ์ฆ์ ๊ฑฐ๋ถํ๊ธฐ๋ ํฉ๋๋ค. ์ด ๊ฒฝ์ฐ -legacy ์ต์
์ ์ถ๊ฐํด ๋ณด์ธ์.
1
openssl pkcs12 -legacy -passin pass:... -in file.pfx -out out.pem
Q. ํ๋ฌธ RSA ํค์ PKCS#8 ํค ์ค ์ด๋ ์ชฝ์ ์จ์ผ ํ๋์?
๋๋ถ๋ถ์ Kubernetes Ingress ์ปจํธ๋กค๋ฌ๋ ๋ ํ์์ ๋ชจ๋ ์ง์ํฉ๋๋ค. ๋ค๋ง ์ผ๋ถ ์ ๊ท ๋๊ตฌ(์: Envoy ์ผ๋ถ ๋น๋)๋ PKCS#8์ ์ ํธํฉ๋๋ค. ๋ณํ์ ๋ค์๊ณผ ๊ฐ์ด ํฉ๋๋ค.
1
openssl pkcs8 -topk8 -nocrypt -in tls.key -out tls.pkcs8.key
Q. --dry-run=client์ --dry-run=server์ ์ฐจ์ด๋?
client๋ ํด๋ผ์ด์ธํธ(kubectl) ์ธก์์๋ง ๋งค๋ํ์คํธ๋ฅผ ์์ฑํ๊ณ API ์๋ฒ๋ก ๋ณด๋ด์ง ์์ต๋๋ค. server๋ API ์๋ฒ๊น์ง ๊ฐ์ ์ด๋๋ฏธ์
๊ฒ์ฆ์ ์ํํ์ง๋ง ์ค์ ๋ก ์ ์ฅํ์ง ์์ต๋๋ค. Secret YAML์ ๋ง๋ค ๋๋ client๋ก ์ถฉ๋ถํฉ๋๋ค.
Q. Secret์ ๋ง๋ ๋ค ์ธ์ฆ์๊ฐ ๊ฐฑ์ ๋๋ฉด ์ด๋ป๊ฒ ํ๋์?
๊ฐ์ ์ด๋ฆ์ผ๋ก kubectl create secret tls ... --dry-run=client -o yaml | kubectl apply -f -๋ฅผ ๋ค์ ์คํํ๋ฉด ๋ฉ๋๋ค. Ingress ์ปจํธ๋กค๋ฌ๋ Secret ๋ณ๊ฒฝ์ ๊ฐ์งํด ์๋์ผ๋ก ์ ์ธ์ฆ์๋ฅผ ๋ก๋ํฉ๋๋ค.