사설 CA 를 도구마다 믿게 하기
목표
사설 루트 CA 와 서버 인증서를 만들어 사내 이름으로 HTTPS 를 띄우고, 운영체제·파이썬·Node 의 신뢰 저장소에 각각 넣는다. 어느 도구가 어느 저장소를 보는지 직접 재서 표로 남기고, 중간 CA 체인까지 서버가 보내게 한다.
왜 중요한가
사내 미러가 HTTPS 인 순간 모든 빌드 도구가 그 CA 를 믿어야 한다. 그런데 신뢰 저장소는 운영체제, certifi, Node 내장 목록, JVM 으로 나뉘어 있어서 한 곳에만 넣으면 일부 도구만 된다. 안 되는 도구에서 검증을 끄는 것이 가장 흔한 사고다. 이 실습 파드는 바깥에 닿지 않는다(DNS 만). 사설 CA 는 폐쇄망 안의 일이기 때문이다.
단계
/root/pki/ca.key·/root/pki/ca.crt로 CN 이Airgap Internal Root CA인 루트 CA 를 만든다(CA:TRUE, keyCertSign)./root/pki/server.key·/root/pki/server.crt로repo.airgap.internal서버 인증서를 그 CA 로 발급한다(SAN 에 DNS 이름, 용도 serverAuth, CA 아님). 서명할 때 준 확장은/root/pki/server.ext로 남긴다./etc/hosts에127.0.0.1 repo.airgap.internal을 더하고,/root/pki/www/hello.txt(내용hello-airgap)를https://repo.airgap.internal:8443/로 내주는 서버를/root/pki/serve.py로 띄운다.- 루트 CA 를 운영체제 저장소에
airgap-root.crt라는 이름으로 넣고 갱신한다.curl이--cacert없이 통과해야 한다. /etc/profile.d/airgap-ca.sh에 파이썬 requests 와 httpx 가 운영체제 묶음을 쓰게 하는 환경 변수를 적는다.- 같은 파일에 Node 가 사설 루트를 믿게 하는 환경 변수를 더한다.
- 환경 변수 없이(운영체제 저장소만으로) 각 도구가 통과하는지 재서
/root/pki/trust-matrix.txt에curl·urllib·requests·httpx·node·go여섯 줄을도구=os또는도구=env로 적는다. - 중간 CA(
/root/pki/int.crt, pathlen 0)를 루트로 발급하고, 그 중간 CA 로mirror.airgap.internal인증서(/root/pki/mirror.crt)를 발급한다./etc/hosts에 이름을 더하고, 잎+중간을 이은/root/pki/mirror-chain.crt로 9443 포트에 같은 서버를 띄운다.
참고
- 인증서 들여다보기:
openssl x509 -in <파일> -noout -text· 이름 확인:openssl x509 -in <파일> -noout -checkhost <이름> - 서버가 보내는 체인:
openssl s_client -connect 127.0.0.1:8443 -servername <이름> -showcerts </dev/null - 로그인 셸로 profile.d 적용 확인:
env -i bash -lc 'echo $REQUESTS_CA_BUNDLE' - 흔한 실수 1: CN 에만 이름을 넣기. 요즘 도구는 SAN 만 봅니다.
- 흔한 실수 2:
.pem확장자로 넣기. update-ca-certificates 는.crt만 읽습니다. - 흔한 실수 3: 안 되는 도구에서 검증을 끄기(
verify=False,NODE_TLS_REJECT_UNAUTHORIZED=0). 이 실습의 채점기는 검증을 켠 채로만 잽니다.
사설 루트 CA 만들기
/root/pki/ca.key·/root/pki/ca.crt 로 CN Airgap Internal Root CA 인 루트 CA 를 만든다(CA:TRUE, keyCertSign).
openssl req 에 -x509 를 주면 요청 대신 자기 서명 인증서를 바로 만듭니다. 확장은 -addext 로 줄 수 있고, 루트 CA 에 필요한 것은 기본 제약(CA)과 키 용도(인증서 서명)입니다.
SAN 이 든 서버 인증서 발급
/root/pki/server.key·/root/pki/server.crt 로 repo.airgap.internal 서버 인증서를 루트 CA 로 발급한다(SAN DNS, serverAuth, CA 아님). 서명할 때 준 확장은 /root/pki/server.ext 로 남긴다.
키와 서명 요청(CSR)을 만든 뒤, CA 키로 서명할 때 확장 파일로 subjectAltName 과 extendedKeyUsage 를 줍니다. openssl x509 -req 는 요청의 확장을 기본으로 옮기지 않는다는 점에 주의하세요.
사내 이름으로 HTTPS 띄우기
/etc/hosts 에 127.0.0.1 repo.airgap.internal 을 더하고, /root/pki/www/hello.txt(hello-airgap)를 https://repo.airgap.internal:8443/ 로 내주는 서버를 /root/pki/serve.py 로 띄운다.
파이썬 표준 라이브러리의 http.server 를 ssl 컨텍스트로 감싸면 HTTPS 서버가 됩니다. 인증서 파일과 키 파일을 load_cert_chain 에 줍니다. 백그라운드로 띄우고, 확인은 --cacert 로 루트를 지정한 curl 로 하세요.
운영체제 저장소에 넣기
루트 CA 를 운영체제 저장소에 airgap-root.crt 로 넣고 갱신한다. curl 이 --cacert 없이 통과해야 한다.
데비안 계열은 정해진 디렉터리에 .crt 확장자로 두고 갱신 명령을 돌리면 한 파일로 이어 붙인 묶음이 다시 만들어집니다. 갱신 명령의 출력에 몇 개가 더해졌는지 나옵니다.
파이썬 requests·httpx 가 믿게 하기
/etc/profile.d/airgap-ca.sh 에 requests 와 httpx 가 운영체제 묶음을 쓰게 하는 환경 변수를 적는다.
requests 와 httpx 는 운영체제 저장소가 아니라 certifi 묶음을 씁니다. 두 라이브러리가 따르는 환경 변수는 서로 다릅니다 — 읽기 자료의 표를 보세요. 값은 운영체제가 만든 묶음 파일입니다.
Node 가 믿게 하기
/etc/profile.d/airgap-ca.sh 에 Node 가 사설 루트를 믿게 하는 환경 변수를 더한다.
Node 는 빌드에 들어간 목록을 쓰고, 그 목록에 인증서를 더하는 환경 변수가 있습니다. 프로세스가 시작할 때 한 번만 읽으므로 새 셸에서 확인하세요.
도구별 신뢰 표 재기
환경 변수 없이 각 도구가 통과하는지 재서 /root/pki/trust-matrix.txt 에 curl·urllib·requests·httpx·node·go 를 도구=os 또는 도구=env 로 적는다.
env -i 로 환경을 비우면 profile.d 의 변수도 없습니다. 그 상태에서 통과하면 운영체제 저장소만으로 되는 것(os), 실패하면 환경 변수가 필요한 것(env)입니다. Go 는 작은 프로그램을 go run 으로 돌려 보세요.
중간 CA 와 체인 보내기
중간 CA /root/pki/int.crt(pathlen 0)와 그 CA 가 발급한 mirror.airgap.internal 인증서 /root/pki/mirror.crt 를 만들고, 잎+중간을 이은 /root/pki/mirror-chain.crt 로 9443 포트에 서버를 띄운다.
중간 CA 도 CA 이므로 기본 제약이 필요하고, pathlen 으로 그 아래 CA 를 못 두게 막습니다. 서버의 인증서 파일에는 잎 인증서 뒤에 중간 CA 인증서를 이어 붙입니다. 클라이언트는 루트만 알고 있습니다.