23.06.15
DEVOTEE를 활성화 시키면
지금 작성한 커뮤니티 글에 대해 1개의 댓글을 달아줍니다.
버튼을 누르면 글 수정 시 ChatGPT가 작성한 댓글이 수정됩니다.
| 컨텐츠 유형 | 제목 | 저장일 | 삭제 |
|---|
본인인증 로그인에 실패하였습니다.
회원이 아니시거나 본인인증 등록이
완료되지 않은 사용자입니다.
가상화를 이용한 Micro Service 들이 많아지면서 그만큼 Network Mesh 또한 복잡해지고 있습니다.
이번 블로그는 제가 운영하고 있는 모바일지갑 플랫폼에서 Pod별 방화벽 설정을 적용하면서 좌충우돌 했던 내용을 적어 봅니다.
Istio에 익숙하신 분들은 바로 공식 문서를 참고 하셔도 됩니다.
https://istio.io/latest/docs/ops/configuration/traffic-management/network-topologies/
현재 모바일지갑에서 사용하는 대략적인 네트워크 흐름도 입니다.
전체 아키텍처 참고
AWS의 ALB(Application Load Balancer)를 사용하고 있고, Istio gateway를 sidecar 방식으로 사용하고 있습니다.
Istio와 ALB 연결 참고
기본적인 방화벽은 ALB의 Security Group에서 제어 합니다.
하지만 아키텍처에 따라 하나의 ALB에서 여러 서비스들이 사용한다면 제약이 따르게 됩니다.
위 그림과 같이 하나의 ALB 아래에 Any Open 서비스와 방화벽 설정이 필요한 경우를 가정했습니다.
물론 방화벽 설정에 있어 다양한 방법이 가능합니다. 오늘은 Pod에 방화벽 설정을 목표로 합니다.
Load Balancer를 분리하여 Security Group을 설정하는 방법이 가장 확실합니다.
동일한 ALB내에서 아래와 같이 Admin Service의 port를 8443과 같은 방법으로 분리하여 별도의 Listeners 설정도 가능합니다.
테스트를 위해 Istio 공식 sample pod인 httpbin을 deploy 합니다.
Istio sample pod
https://github.com/istio/istio/blob/master/samples/httpbin/httpbin.yaml
그리고 Gateway를 붙여야 하는데, 저는 아래와 같이 istio에서 제공하는 gateway를 사용하지 않고 저희 환경과 맞추기 위해 실제 저희 환경에서 사용하는 Gateway와 Virtual Service에 세팅했습니다.
Istio sample gateway
https://github.com/istio/istio/blob/master/samples/httpbin/httpbin-gateway.yaml
정상 설치 되었다면 아래와 같이 pod가 보입니다. (저희는 ArgoCD를 사용합니다.)
ArgoCD 설치 및 사용법
기본 Curl을 보내봅니다.
% curl --location 'https://domain.com/httpbin/get?show_env=true'아래와 같이 응답이 옵니다.
{
"args": {
"show_env": "true"
},
"headers": {
"Accept": "*/*",
"Host": "domain.com",
"User-Agent": "curl/7.87.0",
"X-Amzn-Trace-Id": "Root=1-642f69b9-491858ca6438aaf71c08dcb6",
"X-B3-Parentspanid": "d55053c40dac8f68",
"X-B3-Sampled": "0",
"X-B3-Spanid": "f3de16cd08c6d4a1",
"X-B3-Traceid": "e92bc2ec07d92c74d55053c40dac8f68",
"X-Envoy-Attempt-Count": "1",
"X-Envoy-External-Address": "192.168.1.20",
"X-Envoy-Original-Path": "/httpbin/get?show_env=true",
"X-Forwarded-Client-Cert": "By=spiffe://cluster.local/ns/default/sa/httpbin;Hash=c10549f62e61cff75232ce46e4dc0980837b1d7d457ca76dfaaf26f8969aceab;Subject=\"\";URI=spiffe://cluster.local/ns/istio-system/sa/istio-ingressgateway-service-account",
"X-Forwarded-For": "20.1.1.1,192.168.1.20",
"X-Forwarded-Port": "443",
"X-Forwarded-Proto": "http",
"X-Request-Id": "3ca5d2dd-c2fa-4439-8fff-4f7365d026d7"
},
"origin": "20.1.1.1,192.168.1.20",
"url": "http://domain.com/get?show_env=true"
}여기서 주목할 부분은 X-Forwarded-For 와 X-Envoy-External-Address 두개 입니다.
Istio 공식 문서에 있는 IP기반 화이트 리스트 작성하기를 참고합니다.
IP-based allow list and deny list
ipBlocks 과 remoteIpBlocks 두가지를 지원하는데, 저희는 HTTP/HTTPS 기반의 ALB 이므로 remoteIpBlocks를 사용합니다.
아래와 같이 20.1.1.1 만 허용하도록 config를 작성하여 배포합니다.
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
name: httpbin-firewall
spec:
selector:
matchLabels:
app: httpbin
action: ALLOW
rules:
- from:
- source:
remoteIpBlocks: ["20.1.1.1"]테스트를 위해 20.1.1.1 client에서 curl을 보내봅니다.
% curl --location 'https://domain.com/httpbin/get?show_env=true'분명히 제대로 세팅했는데 403 Forbidden 응답이 옵니다.
RBAC: access deniedistio proxy의 Log를 보면 분명 "20.1.1.1,192.168.1.20" 가 제대로 들어왔는데, 안되고 있습니다.
"GET /httpbin/get?show_env=true HTTP/1.1" 403 - rbac_access_denied_matched_policy[none] - "-" 0 19 0 - "20.1.1.1,192.168.1.20" "PostmanRuntime/7.31.3" "4df34c0d-4f55-454c-a9aa-edf17.....혹시나 싶어 화이트리스트 방화벽을 192.168.1.20 으로 수정했더니 잘됩니다.
즉 현재 istio는 RemoteIP 를 제일 마지막 ip인 192.168.1.20를 바라보고 있습니다.
하지만 또 이상한건 192.168.1.20 는 EC2 의 Private IP이고 istio proxy container의 IP는 아니라는 점입니다.
제일 첫 그림에서 나와있지만 192.168.2.20가 istio proxy 주소이기 때문에 이론적으로 3개의 ip가 있어야 합니다.
이부분은 X-Forwarded-For 관련 문서를 참고하셔야 합니다. 기본적으로 아래와 같은 형식으로 Client IP를 포함해서 Proxy의 모든 IP가 찍혀야 합니다.
X-Forwarded-For: client, proxy1, proxy2
클라이언트 IP ⟶ Proxy 서버 및 장비 ⟶ 웹 서버
X-Forwarded-For: 203.0.113.195, 70.41.3.18, 150.172.238.178
https://developer.mozilla.org/ko/docs/Web/HTTP/Headers/X-Forwarded-For
믿을건 구글신뿐(이제 ChatGPT 갓님인가??), 검색에 들어갑니다.
sidecar.istio.io/interceptionMode: TPROXY 세팅하기
참고 : https://341123.tistory.com/42 (ISTIO 환경에서 Pod가 Client IP를 가져올 수 없을 때)
적용 결과 : 실패
numTrustedProxies 적용하기
적용 결과 : 실패
numTrustedProxies는 Load Balancer Type이 Network(packet source address) 일때 동작 하는 것으로 보인다.
계속 검색에 들어갔더니 저와 같은 istio 공식 issue에 동일한 사례가 있습니다.
Incorrect RemoteIP when Authorization Policy is applied to Injected Istio Proxy #30166
여기서 단서를 발견합니다.
use_remote_address
Envoy의 공식 문서를 보면 use_remote_address의 세팅을 true로 해야지 client ip를 사용할 수 있다고 합니다.
use_remote_address
(BoolValue) If set to true, the connection manager will **use the real remote address of the client connection **when determining internal versus external origin and manipulating various headers. If set to false or absent, the connection manager will use the x-forwarded-for HTTP header. See the documentation for x-forwarded-for, x-envoy-internal, and x-envoy-external-address for more information.
문서에 나와 있는 EnvoyFilter를 적용하는 예제를 참고하여 아래와 같이 적용했습니다.
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
name: fix-xff
namespace: istio-system
spec:
#workloadSelector:
#labels:
#app: istio-ingressgateway
configPatches:
- applyTo: NETWORK_FILTER
match:
context: SIDECAR_INBOUND
listener:
filterChain:
filter:
name: envoy.filters.network.http_connection_manager
patch:
operation: MERGE
value:
typed_config:
"@type": >-
type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
xff_num_trusted_hops: 0
use_remote_address: true디시 테스트를 위해 20.1.1.1 client에서 curl을 보내봅니다.
% curl --location 'https://domain.com/httpbin/get?show_env=true'하지만 여전히 403 Forbidden 응답이 옵니다.
RBAC: access denied방화벽을 해제하고 다시 확인해 봅니다.
{
"args": {
"show_env": "true"
},
"headers": {
"Accept": "*/*",
"Accept-Encoding": "gzip, deflate, br",
"Host": "domain.com",
"Postman-Token": "b63b4e94-89a8-4a01-88c7-89b3ba589c51",
"User-Agent": "PostmanRuntime/7.31.3",
"X-Amzn-Trace-Id": "Root=1-642f77b5-7d524fb530a4a5b7496c9980",
"X-B3-Parentspanid": "37a6030f41ef91af",
"X-B3-Sampled": "0",
"X-B3-Spanid": "6d6123e03af86ece",
"X-B3-Traceid": "b965f742ce381a4f37a6030f41ef91af",
"X-Envoy-Attempt-Count": "1",
"X-Envoy-External-Address": "192.168.2.20",
"X-Envoy-Original-Path": "/httpbin/get?show_env=true",
"X-Forwarded-Client-Cert": "By=spiffe://cluster.local/ns/default/sa/httpbin;Hash=a7d5080606efe88f485e9cad795ddf7a23ab2676691f8e61a20fdb143f00af6a;Subject=\"\";URI=spiffe://cluster.local/ns/istio-system/sa/istio-ingressgateway-service-account",
"X-Forwarded-For": "20.1.1.1,192.168.1.20,192.168.2.20",
"X-Forwarded-Port": "443",
"X-Forwarded-Proto": "https",
"X-Request-Id": "6286cef0-0b1c-43bd-b017-8981a8001920"
},
"origin": "20.1.1.1,192.168.1.20,192.168.2.20",
"url": "https://domain.com/get?show_env=true"
}이전과 분명 달라졌습니다.
"X-Forwarded-For": "20.1.1.1,192.168.1.20,192.168.2.20" 에 istio proxy의 주소가 제대로 찍히기 시작했습니다.
다시 한번 공부를 해봅니다. 위에서 추가한 설정 중 xff_num_trusted_hops : 0 이 있습니다.
xff_num_trusted_hops
(uint32) *The number of additional ingress proxy hops from the right side of the x-forwarded-for HTTP header to trust *when determining the origin client’s IP address. The default is zero if this option is not specified. See the documentation for x-forwarded-for for more information.
실제 remote IP가 어디에 있는지 지정해 주는 옵션입니다. 현재는 0으로 세팅했기 때문에 제일 마지막값인 192.168.2.20 를 바라보고 있습니다.
"X-Envoy-External-Address": "192.168.2.20",우리는 실제 IP가 2개의 proxy를 지나서 있습니다.
"20.1.1.1,192.168.1.20,192.168.2.20"
즉 위와 같이 X-Forwarded-For의 값에서 오른쪽에서 2번째에 있는 20.1.1.1 가 진짜 remote ip 임을 세팅해야 합니다.
아래와 같이 세팅하고 배포합니다.
xff_num_trusted_hops: 2다세 테스트를 했더니 제대로 동합니다.
{
"args": {
"show_env": "true"
},
"headers": {
"Accept": "*/*",
"Accept-Encoding": "gzip, deflate, br",
"Host": "domain.com",
"Postman-Token": "b63b4e94-89a8-4a01-88c7-89b3ba589c51",
"User-Agent": "PostmanRuntime/7.31.3",
"X-Amzn-Trace-Id": "Root=1-642f77b5-7d524fb530a4a5b7496c9980",
"X-B3-Parentspanid": "37a6030f41ef91af",
"X-B3-Sampled": "0",
"X-B3-Spanid": "6d6123e03af86ece",
"X-B3-Traceid": "b965f742ce381a4f37a6030f41ef91af",
"X-Envoy-Attempt-Count": "1",
"X-Envoy-External-Address": "20.1.1.1",
"X-Envoy-Original-Path": "/httpbin/get?show_env=true",
"X-Forwarded-Client-Cert": "By=spiffe://cluster.local/ns/default/sa/httpbin;Hash=a7d5080606efe88f485e9cad795ddf7a23ab2676691f8e61a20fdb143f00af6a;Subject=\"\";URI=spiffe://cluster.local/ns/istio-system/sa/istio-ingressgateway-service-account",
"X-Forwarded-For": "20.1.1.1,192.168.1.20,192.168.2.20",
"X-Forwarded-Port": "443",
"X-Forwarded-Proto": "https",
"X-Request-Id": "6286cef0-0b1c-43bd-b017-8981a8001920"
},
"origin": "20.1.1.1,192.168.1.20,192.168.2.20",
"url": "https://domain.com/get?show_env=true"
}이제 Pod에 적용한 화이트 리스트 IP 기반으로 정상 동작합니다.
네트워크의 세계는 참으로 어렵고 넓습니다.
참고
https://istio.io/latest/docs/reference/config/security/authorization-policy/
위와 같이 IP 기반 설정 말고도 많은 옵션이 있습니다.
아래와 같이 When 옵션을 이용하면 header 값을 검사하여 방화벽 설정도 가능합니다.
- when:
- key: request.headers[x-forwarded-for]
values:
- "220.100.50.170*"
- "203.236.*"
- "10.20.30.4*"저희는 서비스 오픈 초기부터 사용하고 있지만 Outbound 방화벽도 istio를 통해 적용 가능합니다.
기존 LB(Load Balancer)기반 L4 outbound 방화벽은 IP 적용만 가능한데, 문제점은 AWS등의 ALB에 cname mapping된 요즘 도메인의 IP는 동적으로 변경이 됩니다.
그래서 IP를 특정할 수 없는 문제점이 있습니다. 이때 유용하게 사용할 수 있습니다.
아래와 같이 ServiceEntry 를 통해 hosts 도메인 방화벽 적용도 가능해집니다.
apiVersion: networking.istio.io/v1beta1
kind: ServiceEntry
metadata:
name: pgadmin-ext
spec:
hosts:
- security.ubuntu.com # ubuntu apt update Server
- archive.ubuntu.com # ubuntu apt update Server
- www.pgadmin.org # pgadmin Server
- ftp.postgresql.org
ports:
- number: 80
name: http
protocol: HTTP
- number: 443
name: https
protocol: HTTPS
resolution: DNS
location: MESH_EXTERNAL물론 IP를 적용할 수도 있습니다.
apiVersion: networking.istio.io/v1beta1
kind: ServiceEntry
metadata:
name: validator-ext-ip
spec:
hosts:
- dummy.com
addresses:
- 1.1.1.1
ports:
- number: 1
name: dummy
protocol: TCP
resolution: NONE
location: MESH_EXTERNALinbound/outbound 모두 Istio에서 적용 하시면 편리한 DevOps 가 가능합니다.
DEVOTEE를 활성화 시키면
지금 작성한 댓글에 AI가 댓글을 달아줍니다.