证书不同、规格不同 — 搭建对外网关
한국어 원문으로 표시합니다.
목표
두 대외 기관과 상호 TLS 로 붙는 대외계를 만든다. 기관별 인증서를 발급받고, 기관 프로필을 데이터로 두고, 기관 규격으로 번역해 보내고, 허용 주소에서만 받고, 인증서 만료를 감시한다.
왜 중요한가
대외 기관은 우리 표준에 맞춰 주지 않는다. 규격·인증서·허용 주소의 차이를 대외계 한 곳에 가두지 않으면 기관이 늘 때마다 안쪽 시스템을 고치게 된다. 그리고 대외 장애의 단골 — 인증서 만료, 인증서 뒤바뀜, 길이 기준 착각 — 은 전부 이 계층에서 막거나 일찍 알아챌 수 있다.
단계
python3 /opt/lab/fixtures/eaimw/fep/pki.py init /root/eaimw/fep/partners로 두 기관의 CA 를 만든다. 기관마다 개인키와 CSR(주체에O=LabHub Bank, CN 에labhub포함)을 만들고python3 /opt/lab/fixtures/eaimw/fep/pki.py sign A <CSR> /root/eaimw/fep/a.crt,… sign B <CSR> /root/eaimw/fep/b.crt로 서명받는다. 키는/root/eaimw/fep/a.key·/root/eaimw/fep/b.key.- 정의서
/opt/lab/fixtures/eaimw/fep/ORGS.md를 보고/root/eaimw/fep/orgs.json을 쓴다. 최상위 열쇠는 기관 코드(201·301), 값은host·port·format(fixed/json)·encoding(euc_kr/utf-8)·len_includes_self(A 는 true, B 는 null)·ca·cert·key(절대경로)·allow_ips(목록). - 기관 A 서버를 띄우고(
nohup python3 /opt/lab/fixtures/eaimw/fep/org_server.py A > /root/eaimw/fep/a.out 2>&1 &, 9441)openssl s_client로 a.crt·a.key·기관 A CA 를 주고PING한 줄을 보내, 받은PONG <지문>을/root/eaimw/fep/a-ping.txt에 저장한다. - 같은 명령에 b.crt·b.key 를 끼워 기관 A 에 붙어 보고, 표준출력과 표준오류를 함께(
2>&1)/root/eaimw/fep/wrong.txt에 저장한다. 핸드셰이크가 거절돼야 한다. /root/eaimw/fep/fepgw.py send <기관코드> <요청JSON파일>: 프로필(FEP_ORGS, 기본/root/eaimw/fep/orgs.json)에서 기관을 찾아 그 기관의 인증서로 상호 TLS 연결하고, 기관 서버 인증서를 그 기관 CA 로 검증한다. 포트는 환경변수FEP_PORT_<기관코드>가 있으면 그것을 쓴다. 201 은 요청{guid,date,amount,acct,name}을 A 규격 고정길이로(길이가 자기 포함) 보내고 결과를{"org","rspCode","guid","realName","clientFp"}JSON 한 줄로, 301 은{guid,custId}를POST /v1/credit-check로 보내{"org","rspCode","guid","grade","clientFp"}로 출력한다. 응답코드는 LH-STD 로(00→0000, 14→B202, 30→E102, 그 밖 E500)./root/eaimw/fep/inbound.py --port <P>: 대외 기관이 거는 연결(평문 TCP, A 규격)을 받는다. 출발지 주소가 어느 기관의allow_ips에도 없으면 읽지 않고 끊고FEP_LOG(기본/root/eaimw/fep/inbound.log)에DENY <주소>한 줄을 남긴다. 허용된 주소면 A 규격 응답(전문종별0210, 같은 거래고유번호, 응답코드00, 실명 칸 공백 20, 지문 칸-64개, 길이는 자기 포함)을 돌려준다./root/eaimw/fep/certcheck.py --warn-days <N>: 프로필의 기관마다<기관코드> <만료일 YYYY-MM-DD> <남은일수> <OK|WARN>한 줄을 기관 코드순으로 출력한다. 남은 일수가 N 미만이면 WARN 이고, WARN 이 하나라도 있으면 0 이 아닌 코드로 끝난다.
참고
- 키·CSR:
openssl req -newkey ec -pkeyopt ec_paramgen_curve:prime256v1 -nodes -keyout a.key -out a.csr -subj "/O=LabHub Bank/CN=labhub-fep-a" - 연결 확인:
printf 'PING\n' | openssl s_client -connect 127.0.0.1:9441 -cert a.crt -key a.key -CAfile partners/A/ca.crt -quiet - 파이썬 상호 TLS:
ctx = ssl.create_default_context(cafile=CA); ctx.load_cert_chain(CERT, KEY)뒤ctx.wrap_socket(sock, server_hostname="127.0.0.1"). 기관 서버 인증서에는 IP 127.0.0.1 이 SAN 으로 들어 있다. HTTPS 는urllib.request.urlopen(req, context=ctx). - 인증서 지문:
openssl x509 -in a.crt -outform DER | sha256sum. 만료일:openssl x509 -in a.crt -noout -enddate의 값을 파이썬ssl.cert_time_to_seconds()로 읽는다. - 흔한 실수: 기관 A 의 길이에 자기 자신 4바이트를 빼고 쓰는 것, 기관 서버 인증서 검증을 끄는 것(
CERT_NONE), 모든 기관에 인증서 하나를 쓰는 것.
기관별 인증서를 발급받는다
두 기관 CA 를 만들고, 기관마다 키·CSR 을 만들어 서명받아 a.crt/a.key·b.crt/b.key 를 둔다.
개인키는 우리 쪽에 두고 CSR 만 보냅니다. 기관 A 에는 A 가, B 에는 B 가 서명해야 합니다 — 한 CA 가 둘 다 서명하면 기관이 다른 쪽 인증서를 거절하지 못합니다.
기관 프로필을 데이터로
ORGS.md 를 보고 /root/eaimw/fep/orgs.json 에 두 기관의 주소·형식·인코딩·길이 기준·인증서·허용 IP 를 쓴다.
코드에 기관 이름이 등장하지 않게, 기관마다 다른 값은 전부 이 파일로 옮깁니다. 길이 기준은 JSON 기관에는 해당이 없으니 null 입니다.
기관 A 와 상호 TLS 로 붙는다
기관 A 서버를 띄우고 a.crt 로 PING 을 보내 PONG <지문> 을 /root/eaimw/fep/a-ping.txt 에 저장한다.
openssl s_client 에 -cert·-key(내 신원)와 -CAfile(기관 CA, 서버 검증)을 함께 줍니다. -quiet 면 응답 본문만 남습니다. 지문은 기관이 본 내 인증서입니다.
인증서를 바꿔 끼우면 거절된다
b.crt·b.key 로 기관 A 에 붙어 보고, 출력과 오류를 함께 /root/eaimw/fep/wrong.txt 에 저장한다.
3단계 명령에서 인증서와 키만 바꿉니다. 거절 사유(TLS alert)는 표준오류로 나오니 2>&1 로 함께 저장하세요.
기관마다 골라 보낸다
/root/eaimw/fep/fepgw.py send <기관> 이 기관별 인증서·규격으로 보내고 결과를 LH-STD 코드로 정규화한 JSON 한 줄을 낸다.
프로필에서 format 을 보고 어댑터를 고릅니다. A 는 본문을 다 만든 뒤 len(본문)+4 를 길이로 씁니다(자기 포함). 서버 검증은 create_default_context(cafile=기관CA), 내 신원은 load_cert_chain.
들어오는 쪽 — 허용 주소에서만
/root/eaimw/fep/inbound.py --port P 가 프로필의 allow_ips 에서 온 연결만 받아 A 규격으로 답하고, 나머지는 끊고 DENY 를 기록한다.
socketserver 핸들러의 self.client_address[0] 가 출발지 주소입니다. 허용 목록은 모든 기관 프로필의 allow_ips 를 합친 것입니다. 거절할 때는 읽지 말고 바로 돌아오세요.
만료를 먼저 안다
/root/eaimw/fep/certcheck.py --warn-days N 이 기관마다 만료일·남은 일수·OK/WARN 을 출력하고, WARN 이 있으면 0 이 아닌 코드로 끝난다.
인증서의 notAfter 를 읽어 지금(UTC)과의 차이를 일수로 셉니다. 교체에 몇 주가 걸리는 기관이 있으니 기준은 넉넉하게 — 감시 도구는 종료 코드로 알아챕니다.