Istio 심화 — 왜 그렇게 흐르는가 · PeerAuthentication 이 Envoy 의 무엇이 되는가 · 实验
SPIFFE 인증서를 만들고 STRICT·PERMISSIVE 체인을 세운다
목표
PeerAuthentication 을 쓰고, openssl 로 메시 CA 와 SPIFFE 인증서를 만들고, 각 모드가 되는 Envoy 필터 체인을 손으로 세워 평문과 mTLS 가 어떻게 갈리는지 확인한 뒤, 인증서의 SAN 에서 AuthorizationPolicy 의 principal 을 끌어낸다.
왜 중요한가
STRICT 로 바꾼 날 끊기는 호출, 포트 하나만 평문으로 남기려다 조용히 무시되는 정책, principal 을 잘못 적어 모두가 막히는 인가 정책 — 셋 다 istioctl validate 는 통과한다. 모드가 Envoy 의 어떤 체인이 되고 신원이 인증서의 어디에서 오는지 알면 통계 한 줄과 인증서 한 장으로 원인을 짚을 수 있다.
단계
1. /root/ist2-mtls/pa.yaml 에 PeerAuthentication 을 쓰세요 — apiVersion: security.istio.io/v1, 이름 reviews-strict, 네임스페이스 default, selector.matchLabels.app: reviews, mtls.mode: STRICT. istioctl validate -f pa.yaml 의 출력과 종료 코드를 /root/ist2-mtls/01-validate.txt 에 담으세요(마지막 줄 rc=).
2. /root/ist2-mtls 에서 openssl 로 자체 서명 CA 한 장(ca.crt·ca.key)을 만들고, 그 CA 로 서명한 인증서 세 장을 만드세요 — reviews.crt/reviews.key(서버), orders.crt/orders.key(허용할 클라이언트), intruder.crt/intruder.key(같은 CA 가 서명했지만 허용하지 않을 클라이언트). 각 인증서의 SAN 은 URI 하나, spiffe://cluster.local/ns/default/sa/<이름> 입니다. 그다음 인증서에서 실제로 읽은 SAN 을 /root/ist2-mtls/02-ids.txt 에 reviews.crt=…, orders.crt=…, intruder.crt=… 세 줄로 적으세요.
3. /root/ist2-mtls/strict.yaml 에 Envoy 설정을 쓰세요 — 관리 포트 9986, 리스너 virtualInbound 가 127.0.0.1:10086 에서 듣고 리스너 필터로 envoy.filters.listener.tls_inspector 를 둡니다. 필터 체인은 하나, filter_chain_match.transport_protocol: tls 이고 DownstreamTlsContext 에 require_client_certificate: true, 서버 인증서 /root/ist2-mtls/reviews.crt·/root/ist2-mtls/reviews.key, 신뢰 CA /root/ist2-mtls/ca.crt 를 줍니다(SAN 매처는 아직 넣지 않습니다). 그 체인은 모든 경로를 클러스터 inbound|8109||(127.0.0.1:8109)로 보냅니다. 업스트림을 8109 에 ok 로 띄우고 Envoy 를 띄운 뒤 /root/ist2-mtls/03-strict.txt 에 네 줄을 적으세요 — plain=(평문 http://localhost:10086/strict 의 코드), mtls=(orders 인증서로 https://localhost:10086/strict 의 코드), mtls_body=(그 응답 본문), no_filter_chain_match=(통계 listener.127.0.0.1_10086.no_filter_chain_match 의 값).
4. /root/ist2-mtls/strict.yaml 을 /root/ist2-mtls/strict-san.yaml 로 복사한 뒤 그 사본에만 validation_context 에 match_typed_subject_alt_names 를 하나 더하세요 — san_type: URI, matcher.exact 는 2단계에서 읽은 orders.crt 의 SAN 그대로. 그 설정으로 Envoy 를 다시 띄우고 /root/ist2-mtls/04-san.txt 에 세 줄을 적으세요 — orders=(orders 인증서로 https://localhost:10086/san 의 코드), intruder=(intruder 인증서로 같은 요청의 코드), fail_verify_san=(통계 listener.127.0.0.1_10086.ssl.fail_verify_san 의 값).
5. /root/ist2-mtls/strict.yaml 을 바탕으로 /root/ist2-mtls/permissive.yaml 을 만드세요 — 3단계의 TLS 체인은 그대로 두고, filter_chain_match 도 transport_socket 도 없는 평문 체인을 하나 더합니다(같은 클러스터 inbound|8109|| 로). 그 설정으로 Envoy 를 다시 띄우고 /root/ist2-mtls/05-permissive.txt 에 세 줄을 적으세요 — plain=(평문 http://localhost:10086/permissive 의 코드), plain_body=(그 본문), mtls=(orders 인증서로 https://localhost:10086/permissive 의 코드).
6. /root/ist2-mtls/pa-port.yaml 에 문서 두 개를 쓰세요 — (1) Service reviews(네임스페이스 default, selector app: reviews, port: 80 → targetPort: 10096), (2) PeerAuthentication reviews-port(같은 selector, mtls.mode: STRICT, portLevelMtls 로 한 포트만 DISABLE). 그 포트 번호를 무엇으로 적어야 하는지는 힌트를 보세요. 그다음 /root/ist2-mtls/portlevel.yaml 에 Envoy 설정을 쓰세요 — 3단계와 같은 리스너(10086)에 additional_addresses 로 127.0.0.1:10096 를 더 듣게 하고, filter_chain_match.destination_port: 10096 인 평문 체인 하나와 3단계의 TLS 체인 하나를 둡니다. Envoy 를 다시 띄우고 /root/ist2-mtls/06-portlevel.txt 에 네 줄을 적으세요 — validate_rc=(istioctl validate -f pa-port.yaml 의 종료 코드), plain_10096=, plain_10086=(각 포트로 평문 /port 의 코드), mtls_10086=(orders 인증서로 https://localhost:10086/port 의 코드).
7. /root/ist2-mtls/orders.crt 의 SAN 을 openssl 로 읽어 /root/ist2-mtls/07-principal.txt 에 두 줄을 적으세요 — san=(읽은 URI 그대로), principal=(AuthorizationPolicy 의 principals 에 넣을 문자열). 그리고 /root/ist2-mtls/authz.yaml 에 AuthorizationPolicy 를 쓰세요 — apiVersion: security.istio.io/v1, 이름 reviews-from-orders, 네임스페이스 default, selector.matchLabels.app: reviews, action: ALLOW, 규칙 하나의 from[0].source.principals 에 그 principal 하나만. istioctl validate -f authz.yaml 이 통과해야 합니다.
8. /root/ist2-mtls/08-report.md 에 default_mode=(PeerAuthentication 이 하나도 없을 때의 모드), strict_plain=(3단계에서 평문 요청이 받은 코드), permissive_chains=(5단계 리스너의 필터 체인 수), orders_principal=(7단계의 principal) 네 줄을 적고, 그 아래 - 로 시작하는 설명을 네 줄 이상 적으세요.
참고
- 이 파드에는 진짜 istiod 도 진짜 사이드카도 없습니다. 그래서
istioctl proxy-config로 실제 생성물을 볼 수 없고, 번역 규칙을 알고 손으로 등가 Envoy 설정을 만들어 동작을 확인합니다. 같은 규칙이 운영 클러스터의proxy-config출력에 그대로 보입니다. - 인증서는 진짜 istiod 대신 여러분이 만든 CA 가 서명합니다. 모양(SAN 의 SPIFFE URI)은 istiod 가 서명해 주는 것과 같습니다.
- curl 은 평문 연결이 끊기면 코드로
000을 찍습니다. 코드는curl -s -o /dev/null -w '%{http_code}'로 받으세요. - 3·4·5·6단계는 설정 파일 이름이 서로 다릅니다. 앞 단계 파일을 고치지 말고 새 이름으로 만드세요.
- Envoy 를 띄울 때는
setsid --fork nohup envoy -c <파일> --log-level warn > <로그> 2>&1 </dev/null로 셸에서 완전히 떼어 놓으세요. 다시 띄우기 전에는pkill -x envoy로 정리합니다 (pkill -f 'envoy -c'는 그 문자열이 든 셸 자신까지 죽입니다). - 업스트림 흉내용 서버가 이미지에 있습니다:
python3 /opt/lab/envoy/upstream.py <포트> ok|fail|slow. 응답 본문은<모드>:<포트> <경로>입니다. - 설정을 고친 뒤에는 띄우기 전에
envoy --mode validate -c <파일>로 먼저 거르세요. 클러스터 이름에|가 들어가므로 YAML 에서는 반드시 따옴표로 감쌉니다.
8个步骤
- STRICT PeerAuthentication 을 쓰고 오프라인으로 거른다
- 메시 CA 와 SPIFFE 인증서 세 장을 만든다
- STRICT 는 TLS 체인 하나뿐인 리스너가 된다
- 같은 CA 가 서명해도 SAN 이 다르면 거절한다
- PERMISSIVE 는 체인 두 개다
- portLevelMtls 는 워크로드 포트에 거는 체인 하나다
- SAN 에서 spiffe:// 를 떼면 principal 이 된다
- 모드마다 Envoy 에 생기는 것을 한 장으로 정리한다