소개
지원 팀이 도움말 사이트를 배포했지만 클라이언트가 HTTPS 연결을 거부합니다. HTTPS는 TLS로 트래픽을 암호화하고 응답 서버의 신원을 증명합니다. 암호화만으로는 신원을 확인할 수 없습니다. 클라이언트가 요청한 이름을 확인하고 인증서 발급자를 신뢰해야 합니다.
실제 Nginx 서버에서 합성된 교육용 인증서를 수정합니다. 기본적인 터미널 사용 경험이면 충분하며 인증서 조사와 서비스 설정은 여기서 배웁니다. Client와 Server는 같은 VM의 별도 세션과 디렉터리이며 서로 다른 컴퓨터가 아닙니다. Nginx는 준비된 컨테이너에서 실행됩니다. 선택 사항인 HTTPS Monitor는 내부 TLS 실시간 점검을 보여 주며 브라우저 자물쇠는 별도의 외부 연결을 나타냅니다.
신뢰할 수 있는 기준 문서 가져오기
이 단계에서는 정상 서비스의 문서를 가져온 후 고장 난 도움말 사이트와 비교합니다. 호스트 이름은 요청하는 서비스를 식별합니다. 모든 교육용 이름은 .lab.test로 끝나며 이 실습 안에서만 사용됩니다.
Client 탭을 사용합니다. curl은 URL을 가져옵니다. --cacert root.crt는 인증서에 서명하는 제공된 인증 기관(CA)을 신뢰합니다. 이 CA는 교육용 신뢰 기준점이며 공인 기관이 아닙니다. --resolve는 이름과 포트를 IP 주소에 연결하면서 TLS와 HTTP에는 원래 이름을 유지합니다. 127.0.0.1은 현재 VM이며 포트 8443은 준비된 Nginx 서비스에 연결됩니다. --noproxy '*'는 요청을 직접 전송하고 따옴표는 셸이 *를 확장하지 않도록 합니다. --fail은 HTTP 오류를 실패로 처리하고 --output은 나중에 사용할 실제 서비스 문서를 저장합니다.
curl --noproxy '*' --fail --cacert root.crt --resolve status.lab.test:8443:127.0.0.1 --output reference.txt https://status.lab.test:8443/
cat은 파일 내용을 표시합니다.
cat reference.txt
status service: training content available
이 다운로드는 실제 기준 문서이며 보고서가 아닙니다. 이제 원래 도움말 호스트 이름을 요청합니다. 다음 명령은 실패하고 프롬프트로 돌아오는 것이 정상입니다. 이후 계속 진행하세요.
curl --noproxy '*' --cacert root.crt --resolve help.lab.test:8443:127.0.0.1 https://help.lab.test:8443/
curl: (60) SSL: no alternative certificate subject name matches target host name 'help.lab.test'
...
같은 CA와 주소로 기준 서비스는 정상 작동했습니다. 이 오류는 서버 중지가 아니라 이름 불일치를 구체적으로 가리킵니다. 원래 호스트 이름을 유지하세요. URL을 바꾸면 다른 신원을 검사하게 됩니다.
인증서 체인 조사 및 구성
이 단계에서는 실제 제공되는 신원을 준비된 인증서와 비교하고 서버에서 사용할 묶음을 만듭니다. 인증서는 공개 키와 신원 정보를 포함합니다. 대응하는 개인 키는 서버에 남습니다. 개인 키를 보고서에 붙여 넣거나 클라이언트에 보내지 마세요.
Client에서 실제 제공되는 인증서를 조사합니다. openssl s_client는 TLS 연결을 열고 -connect는 주소를 지정합니다. -servername은 Server Name Indication(SNI)으로 요청 이름을 전송해 하나의 Nginx 리스너에서 여러 사이트를 제공할 수 있게 합니다. < /dev/null은 대화형 입력을 끝냅니다. 2>/dev/null은 이 조사에서만 연결 진단을 숨깁니다. 파이프 |는 인증서를 openssl x509로 전달합니다. -noout은 인코딩된 인증서를 숨기고 나머지 옵션은 지정한 필드를 표시합니다. 이 조사 자체가 신뢰를 증명하지는 않습니다.
openssl s_client -connect 127.0.0.1:8443 -servername help.lab.test </dev/null 2>/dev/null | openssl x509 -noout -subject -issuer -dates -ext subjectAltName
subject=CN = legacy.lab.test
issuer=CN = LabEx Training Issuing CA
notBefore=...
notAfter=...
X509v3 Subject Alternative Name:
DNS:legacy.lab.test
주체 대체 이름(SAN)은 허용되는 호스트 이름을 나열합니다. issuer는 서명자를 나타내고 날짜는 유효 기간을 제한합니다. 설정 시 현재 VM용 인증서를 새로 생성하며 리프 인증서는 365일간 유효합니다. 날짜가 유효해도 이름 오류를 보완하지 못합니다.
Server로 전환합니다. openssl x509 -in은 네트워크 응답 대신 로컬 인증서를 읽습니다.
openssl x509 -in certs/help.crt -noout -subject -issuer -dates -ext subjectAltName
SAN은 요청과 일치하는 DNS:help.lab.test입니다. 필요한 관계는 다음과 같습니다.
| 항목 | 역할 |
|---|---|
help.crt와 help.key |
도움말 사이트 인증서와 대응 개인 키 |
docs.crt와 docs.key |
문서 사이트 인증서와 대응 개인 키 |
issuing.crt |
두 사이트에 서명한 중간 CA |
../client/root.crt |
클라이언트가 신뢰하는 루트 CA |
체인은 사이트 인증서 → 중간 CA → 신뢰하는 루트 CA로 이어집니다. Nginx는 리프를 먼저 보내고 중간 인증서를 뒤에 보냅니다. 클라이언트가 이미 루트를 신뢰하므로 루트는 보낼 필요가 없습니다. cat으로 공개 묶음을 만듭니다. >는 출력을 파일에 기록하며 기존 내용을 덮어씁니다. 개인 키는 이 묶음에 포함하지 않습니다.
cat certs/help.crt certs/issuing.crt > certs/help-fullchain.pem
openssl verify는 지정한 신뢰 루트에 대해 묶음을 검사합니다. -untrusted는 중간 인증서를 가능한 연결 고리로 제공하며 신뢰 루트를 추가하지 않습니다. -verify_hostname은 원래 이름을 검사합니다.
openssl verify -CAfile ../client/root.crt -untrusted certs/help-fullchain.pem -verify_hostname help.lab.test certs/help-fullchain.pem
certs/help-fullchain.pem: OK
실패하면 입력 파일 순서와 철자를 확인하고 cat을 반복합니다. 이는 준비한 묶음의 검증이며 실행 중인 서버가 이를 사용하도록 설정하는 작업은 아직 남아 있습니다.
도움말 사이트 신원 적용
이 단계에서는 상태 사이트를 유지하면서 Nginx가 올바른 신원을 제공하도록 합니다. Server에서 cat으로 설정을 표시합니다.
cat conf/help.conf
server_name은 사이트를 선택합니다. ssl_certificate는 공개 체인, ssl_certificate_key는 개인 키를 지정합니다. /tls/로 시작하는 경로는 Nginx 컨테이너 내부에 있으며 현재 디렉터리의 certs/에 대응합니다. return 200 줄은 실제 교육용 도움말 내용을 제공합니다. 리스너, 이름, 응답은 유지하세요.
텍스트 편집기 nano로 파일을 엽니다.
nano conf/help.conf
방향키로 두 TLS 지시문을 찾습니다. 두 줄만 다음과 같이 바꿉니다.
ssl_certificate /tls/help-fullchain.pem;
ssl_certificate_key /tls/help.key;
세미콜론은 Nginx 지시문을 끝냅니다. Ctrl+O, Enter로 저장하고 Ctrl+X로 종료합니다. 경로에 오타가 있으면 같은 파일을 다시 열어 수정합니다.
sudo는 관리자 권한으로 실행합니다. docker exec https-edge는 뒤에 오는 명령을 준비된 서비스 컨테이너 안에서 실행합니다. nginx -t는 설정을 적용하지 않고 테스트합니다.
sudo docker exec https-edge nginx -t
syntax is ok와 test is successful을 찾습니다. 실패하면 표시된 파일과 줄을 먼저 수정합니다. nginx -s reload는 실행 중인 서비스에 테스트한 설정을 다시 읽도록 요청합니다.
sudo docker exec https-edge nginx -s reload
Client로 전환하고 원래 요청을 반복합니다. 다시 읽는 데 잠시 걸릴 수 있으므로 이전 인증서가 보이면 같은 요청을 재시도합니다.
curl --noproxy '*' --fail --cacert root.crt --resolve help.lab.test:8443:127.0.0.1 https://help.lab.test:8443/
help service: training content available
기준 사이트의 정상 기능도 유지합니다.
curl --noproxy '*' --fail --cacert root.crt --resolve status.lab.test:8443:127.0.0.1 https://status.lab.test:8443/
이 성공은 응답을 수락하기 전에 실제 호스트 이름, 체인, 유효 날짜를 검사했다는 뜻입니다. 사용하지 않는 인증서 파일만 편집해서는 이 결과를 얻을 수 없습니다.
누락된 중간 인증서 수정
이 단계에서는 불완전한 체인과 이미 수정한 이름 불일치를 구분합니다. Client에서 준비된 문서 사이트를 요청합니다. 이 실패는 의도된 것입니다.
curl --noproxy '*' --cacert root.crt --resolve docs.lab.test:8443:127.0.0.1 https://docs.lab.test:8443/
curl: (60) SSL certificate problem: unable to get local issuer certificate
...
두 실패 모두 종료 코드 60을 사용하므로 함께 표시된 설명을 읽어야 합니다. 같은 형식의 명령으로 실제 인증서를 조사합니다.
openssl s_client -connect 127.0.0.1:8443 -servername docs.lab.test </dev/null 2>/dev/null | openssl x509 -noout -subject -issuer -dates -ext subjectAltName
이번 SAN은 DNS:docs.lab.test입니다. 이름은 맞지만 Nginx가 리프만 보내므로 클라이언트가 발급자를 루트에 연결할 수 없습니다.
한 번의 통제된 비교를 위해 -k로 인증서 검증을 생략합니다. 신뢰가 깨져 있어도 내용을 가져올 수 있다는 것을 보여 줍니다.
curl --noproxy '*' -k --resolve docs.lab.test:8443:127.0.0.1 https://docs.lab.test:8443/
여기서 docs service: training content available이 보여도 수정된 것은 아닙니다. Server로 돌아가 사용 중인 파일을 조사하고 엽니다.
cat conf/docs.conf
nano conf/docs.conf
준비된 docs-fullchain.pem은 리프와 중간 인증서를 순서대로 포함합니다. ssl_certificate 줄만 바꿉니다.
ssl_certificate /tls/docs-fullchain.pem;
개인 키는 /tls/docs.key로 유지합니다. 앞서 배운 방법으로 저장하고 종료한 다음 테스트하고 다시 읽습니다.
sudo docker exec https-edge nginx -t
sudo docker exec https-edge nginx -s reload
Client에서 세 이름을 모두 -k 없이 요청합니다. 반복문은 각 값을 차례로 host에 넣고 $host는 해당 값을 삽입합니다. 큰따옴표는 생성된 각 인수를 하나로 유지합니다.
for host in help docs status; do
curl --noproxy '*' --fail --cacert root.crt --resolve "$host.lab.test:8443:127.0.0.1" "https://$host.lab.test:8443/"
done
각각의 서비스 응답 세 개가 나와야 합니다. HTTPS Monitor를 새로 고쳐 독립적인 실시간 점검과 비교할 수도 있습니다. 오류는 해당 설정으로 돌아가 수정한 뒤 테스트하고 다시 읽습니다. 클라이언트의 신뢰 루트나 원래 호스트 이름은 바꾸지 마세요. 실제 배포에서는 조직의 발급 절차로 승인된 인증서를 받습니다. 이 실습은 진단에 집중하도록 합성 후보를 제공합니다.
요약
신뢰할 수 있는 기준 문서를 가져오고, 실제 인증서 이름과 발급자를 조사하고, 리프와 중간 인증서 묶음을 구성한 뒤 테스트한 Nginx 재읽기로 적용했습니다. 정상 서비스를 유지하며 이름 불일치와 불완전한 체인을 수정했습니다. 다음 챌린지에서는 다른 포털에 이 판단들을 함께 적용합니다.



