이스티오 확장을 통한 맞춤형 네트워킹 구현
기본 기능으로 부족한 조직별 요구사항 충족을 위해 Envoy 프록시의 설정을 직접 수정하거나 확장한다. EnvoyFilter 리소스를 활용해 엔보이의 동작을 세부 제어하며, Lua 스크립트WebAssembly(Wasm) 모듈을 적용해 요청/응답 경로를 커스터마이징할 수 있다.

주요 확장 사례

  • 외부 인가 서비스 연동, HMAC 서명 검증 등 비표준 프로토콜 처리
  • 헤더 조작, 페이로드 보강을 위한 추가 서비스 호출
  • 조직별 보안 토큰 처리 로직 구현

이를 통해 Istio의 코어 변경 없이 특정 사용 사례에 최적화된 네트워킹 레이어를 구축할 수 있다.

 

 

실습환경 구성

#
git clone https://github.com/AcornPublishing/istio-in-action
cd istio-in-action/book-source-code-master
pwd # 각자 자신의 pwd 경로
code .

# 아래 extramounts 생략 시, myk8s-control-plane 컨테이너 sh/bash 진입 후 직접 git clone 가능
kind create cluster --name myk8s --image kindest/node:v1.23.17 --config - <<EOF
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
  extraPortMappings:
  - containerPort: 30000 # Sample Application (istio-ingrssgateway) HTTP
    hostPort: 30000
  - containerPort: 30001 # Prometheus
    hostPort: 30001
  - containerPort: 30002 # Grafana
    hostPort: 30002
  - containerPort: 30003 # Kiali
    hostPort: 30003
  - containerPort: 30004 # Tracing
    hostPort: 30004
  - containerPort: 30005 # Sample Application (istio-ingrssgateway) HTTPS
    hostPort: 30005
  - containerPort: 30006 # TCP Route
    hostPort: 30006
  - containerPort: 30007 # kube-ops-view
    hostPort: 30007
  extraMounts: # 해당 부분 생략 가능
  - hostPath: /Users/gasida/Downloads/istio-in-action/book-source-code-master # 각자 자신의 pwd 경로로 설정
    containerPath: /istiobook
networking:
  podSubnet: 10.10.0.0/16
  serviceSubnet: 10.200.0.0/22
EOF

# 설치 확인
docker ps

# 노드에 기본 툴 설치
docker exec -it myk8s-control-plane sh -c 'apt update && apt install tree psmisc lsof wget bridge-utils net-tools dnsutils tcpdump ngrep iputils-ping git vim -y'

# (옵션) kube-ops-view
helm repo add geek-cookbook https://geek-cookbook.github.io/charts/
helm install kube-ops-view geek-cookbook/kube-ops-view --version 1.2.2 --set service.main.type=NodePort,service.main.ports.http.nodePort=30007 --set env.TZ="Asia/Seoul" --namespace kube-system
kubectl get deploy,pod,svc,ep -n kube-system -l app.kubernetes.io/instance=kube-ops-view

## kube-ops-view 접속 URL 확인
open "http://localhost:30007/#scale=1.5"
open "http://localhost:30007/#scale=1.3"

# (옵션) metrics-server
helm repo add metrics-server https://kubernetes-sigs.github.io/metrics-server/
helm install metrics-server metrics-server/metrics-server --set 'args[0]=--kubelet-insecure-tls' -n kube-system
kubectl get all -n kube-system -l app.kubernetes.io/instance=metrics-server



# kind 설치 시 kind 이름의 도커 브리지가 생성된다 : 172.18.0.0/16 대역
docker network ls
docker inspect kind

# mypc 컨테이너 기동 : kind 도커 브리지를 사용하고, 컨테이너 IP를 지정 없이 혹은 지정 해서 사용
docker run -d --rm --name mypc --network kind --ip 172.18.0.100 nicolaka/netshoot sleep infinity # IP 지정 실행 시
혹은 IP 지정 실행 시 에러 발생 시 아래 처럼 IP 지정 없이 실행
docker run -d --rm --name mypc --network kind nicolaka/netshoot sleep infinity # IP 지정 없이 실행 시
docker ps

# kind network 중 컨테이너(노드) IP(대역) 확인
docker ps -q | xargs docker inspect --format '{{.Name}} {{.NetworkSettings.Networks.kind.IPAddress}}'
/mypc 172.18.0.100
/myk8s-control-plane 172.18.0.2

# 동일한 docker network(kind) 내부에서 컨테이너 이름 기반 도메인 통신 가능 확인!
docker exec -it mypc ping -c 1 172.18.0.2
docker exec -it mypc ping -c 1 myk8s-control-plane
docker exec -it myk8s-control-plane ping -c 1 mypc



# MetalLB 배포
kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.14.9/config/manifests/metallb-native.yaml

# 확인
kubectl get crd  
kubectl get pod -n metallb-system

# IPAddressPool, L2Advertisement 설정 : MetalLB 파드(speaker) 배포 정상 완료 후 아래 설정 실행하자.
cat << EOF | kubectl apply -f -
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
  name: default
  namespace: metallb-system
spec:
  addresses:
  - 172.18.255.101-172.18.255.120
---
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
  name: default
  namespace: metallb-system
spec:
  ipAddressPools:
  - default
EOF

# 확인
kubectl get IPAddressPool,L2Advertisement -A





#
cat << EOF | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx
spec:
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:latest
        ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: nginx-service
spec:
  selector:
    app: nginx
  ports:
  - port: 80
    targetPort: 80
  type: LoadBalancer
EOF


# 확인
kubectl get deploy,pod,svc,ep 
kubectl get svc nginx-service -o jsonpath='{.status.loadBalancer.ingress[0].ip}'
LBIP=$(kubectl get svc nginx-service -o jsonpath='{.status.loadBalancer.ingress[0].ip}')

# 외부?에서 각 클러스터의 Service(LB)로 접속(인입) 확인 : TCP 80 포트 사용으로 편리하다!
docker exec -it mypc curl -s $LBIP
docker exec -it mypc curl -s $LBIP -v -I


# 확인 후 삭제
kubectl delete deploy,svc --all




# myk8s-control-plane 진입 후 설치 진행
docker exec -it myk8s-control-plane bash
-----------------------------------
# (옵션) 코드 파일들 마운트 확인
tree /istiobook/ -L 1
혹은
git clone ... /istiobook

# istioctl 설치
export ISTIOV=1.17.8
echo 'export ISTIOV=1.17.8' >> /root/.bashrc

curl -s -L https://istio.io/downloadIstio | ISTIO_VERSION=$ISTIOV sh -
cp istio-$ISTIOV/bin/istioctl /usr/local/bin/istioctl

# demo 프로파일 컨트롤 플레인 배포
istioctl install --set profile=demo --set values.global.proxy.privileged=true -y

# 보조 도구 설치
kubectl apply -f istio-$ISTIOV/samples/addons

# 빠져나오기
exit
-----------------------------------

# 설치 확인 : istiod, istio-ingressgateway, crd 등
kubectl get istiooperators -n istio-system -o yaml
kubectl get all,svc,ep,sa,cm,secret,pdb -n istio-system
kubectl get cm -n istio-system istio -o yaml
kubectl get crd | grep istio.io | sort

# 실습을 위한 네임스페이스 설정
kubectl create ns istioinaction
kubectl label namespace istioinaction istio-injection=enabled
kubectl get ns --show-labels

# istio-ingressgateway 서비스 : nodeport 지정 변경 , externalTrafficPolicy 설정 (ClientIP 수집)
kubectl patch svc -n istio-system istio-ingressgateway -p '{"spec": {"type": "LoadBalancer", "ports": [{"port": 80, "targetPort": 8080, "nodePort": 30000}]}}'
kubectl patch svc -n istio-system istio-ingressgateway -p '{"spec": {"type": "LoadBalancer", "ports": [{"port": 443, "targetPort": 8443, "nodePort": 30005}]}}'
kubectl patch svc -n istio-system istio-ingressgateway -p '{"spec":{"externalTrafficPolicy": "Local"}}'
kubectl describe svc -n istio-system istio-ingressgateway

# NodePort 변경 및 nodeport 30001~30003으로 변경 : prometheus(30001), grafana(30002), kiali(30003), tracing(30004)
kubectl patch svc -n istio-system prometheus -p '{"spec": {"type": "NodePort", "ports": [{"port": 9090, "targetPort": 9090, "nodePort": 30001}]}}'
kubectl patch svc -n istio-system grafana -p '{"spec": {"type": "NodePort", "ports": [{"port": 3000, "targetPort": 3000, "nodePort": 30002}]}}'
kubectl patch svc -n istio-system kiali -p '{"spec": {"type": "NodePort", "ports": [{"port": 20001, "targetPort": 20001, "nodePort": 30003}]}}'
kubectl patch svc -n istio-system tracing -p '{"spec": {"type": "NodePort", "ports": [{"port": 80, "targetPort": 16686, "nodePort": 30004}]}}'

# Prometheus 접속 : envoy, istio 메트릭 확인
open http://127.0.0.1:30001

# Grafana 접속
open http://127.0.0.1:30002

# Kiali 접속 : NodePort
open http://127.0.0.1:30003

# tracing 접속 : 예거 트레이싱 대시보드
open http://127.0.0.1:30004

 

이스티오 고급 설정을 위한 EnvoyFilter 활용

기존 API로 구현 불가한 시나리오에서 EnvoyFilter를 사용해 엔보이 프록시의 리스너·라우트·클러스터·필터를 직접 설정한다. 이 리소스는 이스티오의 고수준 추상화를 벗어나 저수준 엔보이 기능을 제어할 수 있지만, 버전 간 호환성 보장 없어 신중한 검증이 필수다. 잘못된 구성은 데이터 플레인 전체 장애로 이어질 수 있으므로, 반드시 테스트 환경에서 검증 후 배포해야 한다. 비상 시에만 사용하는 것이 권장되며, 일반적인 경우에는 VirtualService·DestinationRule 등 표준 API를 우선 적용한다.

 


Tap 필터를 적용해 webapp 서비스의 HTTP 트래픽을 샘플링·스트리밍하며, workloadSelector로 특정 워크로드에만 적용한다.
VirtualService/DestinationRule 이후 적용되며, 엔보이 저수준 설정 변경으로 인한 메시 장애 리스크 존재해 신중한 구성이 필수다.

 

# cat ch14/tap-envoy-filter.yaml
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
  name: tap-filter
  namespace: istioinaction
spec:
  workloadSelector:
    labels:
      app: webapp # 워크로드 셀렉터
  configPatches:
  - applyTo: HTTP_FILTER # 설정할 위치
    match:
      context: SIDECAR_INBOUND
      listener:
        portNumber: 8080
        filterChain:
          filter:
            name: "envoy.filters.network.http_connection_manager"
            subFilter:
              name: "envoy.filters.http.router"
    patch: # 엔보이 설정 패치
      operation: INSERT_BEFORE
      value:
       name: envoy.filters.http.tap
       typed_config:
          "@type": "type.googleapis.com/envoy.extensions.filters.http.tap.v3.Tap"
          commonConfig:
            adminConfig:
              configId: tap_config
              
              
              
              
#
cat ch14/tap-envoy-filter.yaml
kubectl apply -f ch14/tap-envoy-filter.yaml
kubectl get envoyfilter -n istioinaction
NAME         AGE
tap-filter   10s

#
docker exec -it myk8s-control-plane istioctl proxy-config listener deploy/webapp.istioinaction
docker exec -it myk8s-control-plane istioctl proxy-config listener deploy/webapp.istioinaction --port 15006
docker exec -it myk8s-control-plane istioctl proxy-config listener deploy/webapp.istioinaction --port 15006 -o json
...
                                {
                                    "name": "envoy.filters.http.tap",
                                    "typedConfig": {
                                        "@type": "type.googleapis.com/envoy.extensions.filters.http.tap.v3.Tap",
                                        "commonConfig": {
                                            "adminConfig": {
                                                "configId": "tap_config"
                                            }
                                        }
                                    }
                                },
                                {
                                    "name": "envoy.filters.http.router",
                                    "typedConfig": {
                                        "@type": "type.googleapis.com/envoy.extensions.filters.http.router.v3.Router"
                                    }
                                }
...

EnvoyFilter 설정 요약

  1. workloadSelector로 istioinaction 네임스페이스의 특정 워크로드에 적용 대상을 지정한다.
  2. SIDECAR_INBOUND 리스너의 8080 포트 HTTP 필터 체인에서 envoy.filters.http.router 필터 앞에 tap 필터를 추가한다.
  3. tap 필터는 envoy.filters.http.tap 타입으로 설정되며, streaming_admin 출력을 통해 실시간 트래픽 디버깅이 가능하다.

 

 

 

# 터미널 1 : 포트 포워딩 설정 후 tap 시작
kubectl port-forward -n istioinaction deploy/webapp 15000 &
curl -X POST -d @./ch14/tap-config.json localhost:15000/tap

# 터미널 2 : 기존 반복 접속하는 것 활용
EXT_IP=$(kubectl -n istio-system get svc istio-ingressgateway -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
docker exec -it mypc curl -s -H "Host: webapp.istioinaction.io" http://$EXT_IP/api/catalog
docker exec -it mypc curl -s -H "x-app-tap: true" -H "Host: webapp.istioinaction.io" http://$EXT_IP/api/catalog
while true; do docker exec -it mypc curl -s -H "x-app-tap: true" -H "Host: webapp.istioinaction.io" http://$EXT_IP/api/catalog ; echo ; date "+%Y-%m-%d %H:%M:%S" ; sleep 1; echo; done

# 터미널 3 : 로그 확인
docker exec -it myk8s-control-plane istioctl proxy-config log deploy/webapp -n istioinaction --level http:debug
docker exec -it myk8s-control-plane istioctl proxy-config log deploy/webapp -n istioinaction --level tap:debug
kubectl logs -n istioinaction -l app=webapp -c istio-proxy -f
2025-05-18T05:59:47.704918Z	debug	envoy http external/envoy/source/common/http/conn_manager_impl.cc:329	[C2100] new stream	thread=31
2025-05-18T05:59:47.705028Z	debug	envoy http external/envoy/source/common/http/conn_manager_impl.cc:1049	[C2100][S11845543740779751481] request headers complete (end_stream=true):
':authority', 'webapp.istioinaction.io'
':path', '/api/catalog'
':method', 'GET'
'user-agent', 'curl/8.7.1'
'accept', '*/*'
'x-app-tap', 'true'
...
2025-05-18T05:59:47.713852Z	debug	envoy tap external/envoy/source/extensions/filters/http/tap/tap_config_impl.cc:172	submitting buffered trace sink	thread=31
2025-05-18T05:59:47.713860Z	debug	envoy tap external/envoy/source/extensions/common/tap/admin.cc:145	admin submitting buffered trace to main thread	thread=31
...

 

 

 

14.3 외부 호출로 요청 속도 제한하기

엔보이 글로벌 속도 제한은 모든 서비스 인스턴스가 동일한 외부 속도 제한 서버(예: Redis 백엔드)를 호출해 요청 허용 여부를 결정하며, 서비스 복제본 수와 무관하게 일관된 트래픽 제한을 적용할 수 있다.

 

속도 제한 서버 설정
x-loyalty 헤더 값에 따라 골드(10/분), 실버(5/분), 브론즈(3/분) 등급별 요청 제한을 적용하고, 미확인 등급은 1/분으로 제한한다.
Redis 백엔드를 사용해 글로벌 카운터를 관리하며, Envoy의 RateLimitService 설정을 통해 외부 서버와 통합한다.

 

# cat ch14/rate-limit/rlsconfig.yaml 
apiVersion: v1
kind: ConfigMap
metadata:
  name: catalog-ratelimit-config
  namespace: istioinaction
data:
  config.yaml: |
    domain: catalog-ratelimit
    descriptors:
      - key: header_match
        value: no_loyalty
        rate_limit:
          unit: MINUTE
          requests_per_unit: 1
      - key: header_match
        value: gold_request
        rate_limit:
          unit: MINUTE
          requests_per_unit: 10
      - key: header_match
        value: silver_request
        rate_limit:
          unit: MINUTE
          requests_per_unit: 5
      - key: header_match
        value: bronze_request
        rate_limit:
          unit: MINUTE
          requests_per_unit: 3

 

요청 경로에 속도 제한 걸기* CONFIGURING THE REQUEST PATH FOR RATE LIMITING

EnvoyFilter를 통해 /items 경로 요청의 x-loyalty 헤더를 포착하는 rate-limit action을 정의해, 특정 로열티 등급별로 속도 제한 서버에 전달할 descriptor 생성을 구성한다.
VirtualService 라우트에 rate_limits 섹션 추가 없이, EnvoyFilter의 header_value_match 액션으로 헤더 기반 속성 추출 및 Redis 기반 글로벌 제한 정책을 적용한다.

# cat ch14/rate-limit/catalog-ratelimit-actions.yaml 
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
  name: catalog-ratelimit-actions
  namespace: istioinaction
spec:
  workloadSelector:
    labels:
      app: catalog
  configPatches:
    - applyTo: VIRTUAL_HOST
      match:
        context: SIDECAR_INBOUND
        routeConfiguration:
          vhost:
            route:
              action: ANY
      patch:
        operation: MERGE
        # Applies the rate limit rules.
        value:
          rate_limits: # 속도 제한 조치
            - actions:
              - header_value_match:
                  descriptor_value: no_loyalty
                  expect_match: false
                  headers:
                  - name: "x-loyalty"
            - actions:
              - header_value_match:
                  descriptor_value: bronze_request
                  headers:
                  - name: "x-loyalty"
                    exact_match: bronze
            - actions:
              - header_value_match:
                  descriptor_value: silver_request
                  headers:
                  - name: "x-loyalty"
                    exact_match: silver
            - actions:
              - header_value_match:
                  descriptor_value: gold_request
                  headers:
                  - name: "x-loyalty"
                    exact_match: gold
                    
                    
                    
#
tree ch14/rate-limit
ch14/rate-limit
├── catalog-ratelimit-actions.yaml
├── catalog-ratelimit.yaml
├── rls.yaml
└── rlsconfig.yaml

cat ch14/rate-limit/rlsconfig.yaml
cat ch14/rate-limit/rls.yaml

#
kubectl apply -f ch14/rate-limit/rlsconfig.yaml -n istioinaction
kubectl apply -f ch14/rate-limit/rls.yaml -n istioinaction

# 확인
kubectl get cm -n istioinaction catalog-ratelimit-config
kubectl get pod -n istioinaction                                
NAME                       READY   STATUS    RESTARTS      AGE
ratelimit-99d5d9c5-q4zcm   1/1     Running   2 (25s ago)   34s
redis-6cf4ff9768-x97m8     1/1     Running   0             34s
...





# 기존에 반복 호출은 취소해두자

# 
cat ch14/rate-limit/catalog-ratelimit.yaml
cat ch14/rate-limit/catalog-ratelimit-actions.yaml
kubectl apply -f ch14/rate-limit/catalog-ratelimit.yaml -n istioinaction
kubectl apply -f ch14/rate-limit/catalog-ratelimit-actions.yaml -n istioinaction
kubectl get envoyfilter -A
NAMESPACE       NAME                        AGE
istioinaction   catalog-ratelimit-actions   27s
istioinaction   catalog-ratelimit-filter    28s
...

 

 

속도 제한 기능을 시험해보기 위해 sleep 앱을 배포하고 catalog 서비스를 호출하는 클라이언트를 시뮬레이션해보자.

# sleep 앱에서 catalog 서비스를 호출 시도 : 대략 1분에 한 번 정도 호출 성공! >> x-loyalty 헤더가 없을 때 속도 제한 값!
kubectl exec -it deploy/sleep -n istioinaction -c sleep -- curl http://catalog/items -v
...

kubectl exec -it deploy/sleep -n istioinaction -c sleep -- curl http://catalog/items -v
...
< HTTP/1.1 429 Too Many Requests
< x-envoy-ratelimited: true
...

# silver 헤더는? 
kubectl exec -it deploy/sleep -n istioinaction -c sleep -- curl -H "x-loyalty: silver" http://catalog/items -v
kubectl exec -it deploy/sleep -n istioinaction -c sleep -- curl -H "x-loyalty: silver" http://catalog/items -v
kubectl exec -it deploy/sleep -n istioinaction -c sleep -- curl -H "x-loyalty: silver" http://catalog/items -v
kubectl exec -it deploy/sleep -n istioinaction -c sleep -- curl -H "x-loyalty: silver" http://catalog/items -v
kubectl exec -it deploy/sleep -n istioinaction -c sleep -- curl -H "x-loyalty: silver" http://catalog/items -v
kubectl exec -it deploy/sleep -n istioinaction -c sleep -- curl -H "x-loyalty: silver" http://catalog/items -v
...


#
docker exec -it myk8s-control-plane istioctl proxy-config route deploy/catalog.istioinaction --name 'InboundPassthroughClusterIpv4'
docker exec -it myk8s-control-plane istioctl proxy-config route deploy/catalog.istioinaction --name 'InboundPassthroughClusterIpv4' -o json | grep actions

docker exec -it myk8s-control-plane istioctl proxy-config route deploy/catalog.istioinaction --name 'inbound|3000||'
docker exec -it myk8s-control-plane istioctl proxy-config route deploy/catalog.istioinaction --name 'inbound|3000||' -o json | grep actions
docker exec -it myk8s-control-plane istioctl proxy-config route deploy/catalog.istioinaction --name 'inbound|3000||' -o json
...
                "rateLimits": [
                    {
                        "actions": [
                            {
                                "headerValueMatch": {
                                    "descriptorValue": "no_loyalty",
                                    "expectMatch": false,
                                    "headers": [
                                        {
                                            "name": "x-loyalty"
                                        }
                                    ]
                                }
                            }
                        ]
                    },
                    {
                        "actions": [
                            {
                                "headerValueMatch": {
                                    "descriptorValue": "bronze_request",
                                    "headers": [
                                        {
                                            "name": "x-loyalty",
                                            "exactMatch": "bronze"
...

 

 

14.4 루아로 이스티오의 데이터 플레인 확장하기

Lua를 활용한 이스티오 데이터 플레인 확장
Lua 스크립트를 엔보이 필터 체인에 주입해 요청/응답 경로의 헤더 조작·바디 검사 등 커스텀 로직 구현이 가능하다.
예를 들어 A/B 테스트 그룹 분류를 위해 외부 서비스 호출 후 결과를 헤더에 추가하는 동적 라우팅을 구성할 수 있다.
단, 바디 전체 메모리 적재 등 무거운 연산은 성능 저하를 유발할 수 있으니 주의가 필요하다.

| 일전에 실 예로 내가 운영하던 서비스중 프론트/백 사이에서 데이터가 안들어온다는 개발자 확인 요청으로 Lua 활용해서 바디 전체를 로깅하는 루아 스크립트 작성했다가 뻗었던 기억이 있다.

#
cat ch14/bucket-tester-service.yaml
...
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: bucket-tester
    version: v1
  name: bucket-tester
spec:
  replicas: 1
  selector:
    matchLabels:
      app: bucket-tester
      version: v1
  template:
    metadata:
      labels:
        app: bucket-tester
        version: v1
    spec:
      containers:
      - image: hashicorp/http-echo:1.0 # 수정 https://hub.docker.com/r/hashicorp/http-echo/tags
        imagePullPolicy: IfNotPresent
        name: bucket-tester
        args:
        - "-text=dark-launch-7"        
        ports:
        - containerPort: 5678
          name: http
          protocol: TCP
        securityContext:
          privileged: false

#
kubectl apply -f ch14/httpbin.yaml -n istioinaction
kubectl apply -f ch14/bucket-tester-service.yaml -n istioinaction

# 확인
kubectl get pod -n istioinaction
NAME                             READY   STATUS    RESTARTS      AGE
bucket-tester-688c598b47-86fbr   2/2     Running   0             25s
httpbin-85d76b4bb6-dz6b5         2/2     Running   0             2m56s
...




# cat ch14/lua-filter.yaml
...
            function envoy_on_request(request_handle)
              local headers, test_bucket = request_handle:httpCall(
              "bucket_tester",
              {
                [":method"] = "GET",
                [":path"] = "/",
                [":scheme"] = "http",
                [":authority"] = "bucket-tester.istioinaction.svc.cluster.local",
                ["accept"] = "*/*"
              }, "", 5000) 

              request_handle:headers():add("x-test-cohort", test_bucket)               
            end          
            function envoy_on_response(response_handle)
              response_handle:headers():add("istioinaction", "it works!")
            end
...

 

Lua 스크립트는 envoy_on_request/envoy_on_response 함수로 요청·응답 헤더를 조작하고, httpCall()을 통해 외부 서비스와 논블로킹 통신해 동적 헤더 주입을 구현한다.

# cat ch14/lua-filter.yaml
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
  name: httpbin-lua-extension
  namespace: istioinaction
spec:
  workloadSelector:
    labels:
      app: httpbin
  configPatches:
  - applyTo: HTTP_FILTER
    match:
      context: SIDECAR_INBOUND
      listener:
        portNumber: 80
        filterChain:
          filter:
            name: "envoy.filters.network.http_connection_manager"
            subFilter:
              name: "envoy.filters.http.router"
    patch:
      operation: INSERT_BEFORE
      value: 
       name: envoy.lua
       typed_config:
          "@type": "type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua"
          inlineCode: |
            function envoy_on_request(request_handle) # 아래 줄에 코드 입력
              local headers, test_bucket = request_handle:httpCall(
              "bucket_tester",
              {
                [":method"] = "GET",
                [":path"] = "/",
                [":scheme"] = "http",
                [":authority"] = "bucket-tester.istioinaction.svc.cluster.local",
                ["accept"] = "*/*"
              }, "", 5000) 

              request_handle:headers():add("x-test-cohort", test_bucket)               
            end          
            function envoy_on_response(response_handle) # 아래 줄에 코드 입력
              response_handle:headers():add("istioinaction", "it works!")
            end
  - applyTo: CLUSTER
    match:
      context: SIDECAR_OUTBOUND
    patch:
      operation: ADD
      value: # cluster specification
        name: bucket_tester
        type: STRICT_DNS
        connect_timeout: 0.5s
        lb_policy: ROUND_ROBIN
        load_assignment:
          cluster_name: bucket_tester
          endpoints:
          - lb_endpoints:
            - endpoint:
                address:
                  socket_address:
                    protocol: TCP
                    address: bucket-tester.istioinaction.svc.cluster.local
                    port_value: 80

 

#
kubectl apply -f ch14/lua-filter.yaml
kubectl get envoyfilter -n istioinaction

# istio-proxy config 확인 내용 추가해두자


# httpbin 서비스 호출 확인! 
kubectl exec -it deploy/sleep -n istioinaction -c sleep -- curl http://httpbin.istioinaction:8000/ -v
...
< HTTP/1.1 503 Service Unavailable
< content-length: 39
< content-type: text/plain
< istioinaction: it works!
< date: Sun, 18 May 2025 07:59:34 GMT
< server: envoy
< x-envoy-upstream-service-time: 51
...
invalid header value for: x-test-cohort

kubectl exec -it deploy/sleep -n istioinaction -c sleep -- curl http://httpbin.istioinaction:8000/headers
...
invalid header value for: x-test-cohort


# 정상 실습 시..
{
    "headers": {
        "Accept": "*/*",
        "Content-Length": "0",
        "Host": "httpbin.istioinaction:8000",
        "User-Agent": "curl/7.69.1",
        "X-B3-Sampled": "1",
        "X-B3-Spanid": "1d066f4b17ee147b",
        "X-B3-Traceid": "1ec27110e4141e131d066f4b17ee147b",
        "X-Test-Cohort": "dark-launch-7" # A/B 테스트 서비스를 호출할 때 덧붙이는 새 헤더 x-test-cohort 가 보임
    }
}

 

 

 

14.5 웹어셈블리로 이스티오의 데이터 플레인 확장하기

**웹어셈블리(Wasm)**를 통해 Envoy 필터를 동적 로드해 Istio 데이터 플레인을 확장하며, WasmPlugin CRD로 OCI 레지스트리에서 모듈을 배포해 커스텀 인증·트래픽 제어 기능을 구현할 수 있다. **웹어셈블리(Wasm)**는 이식성·고성능·안전성을 갖춘 바이너리 포맷으로, 다양한 언어로 작성된 커스텀 로직을 엔보이 필터에 동적 로드해 이스티오 데이터 플레인을 확장한다.

 

웹어셈블리(Wasm) 사용 이유
C++ 종속성·정적 빌드 문제 해결: 런타임 동적 로드로 커스텀 필터를 즉시 적용하며, 다양한 언어 지원으로 유연한 확장 가능. 안전한 샌드박스 환경에서 실행되어 메모리/CPU 제어·프록시 장애 격리되며, OCI 레지스트리 통합으로 배포 관리가 간소화된다.

**WebAssembly(Wasm)**로 Envoy 필터를 생성하려면 wasme 도구를 사용해 어셈블리스크립트·러스트 등 지원 언어로 코드 작성 후 빌드하며, 실험적 기능이므로 운영 전 철저한 테스트가 필요하다.

 

Distributing WebAssembly Modules 실습

Istio의 WasmPlugin을 사용해 OCI 레지스트리에서 원격 Wasm 모듈을 동적으로 로드하며, HTTP 기본 인증 확장 등을 구성할 수 있다.
WasmPlugin 리소스로 필터 단계(phase)와 설정을 정의하면 Istio 에이전트가 자동으로 모듈을 다운로드해 Envoy에 주입한다.

# 설정 전 호출 확인
EXT_IP=$(kubectl -n istio-system get svc istio-ingressgateway -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
docker exec -it mypc curl -s -H "Host: webapp.istioinaction.io" http://$EXT_IP/api/catalog -v


#
kubectl explain wasmplugins.extensions.istio.io
...

# 
kubectl apply -f - <<EOF
apiVersion: extensions.istio.io/v1alpha1
kind: WasmPlugin
metadata:
  name: basic-auth
  namespace: istio-system # 모든 네임스페이스에 영향
spec:
  selector:
    matchLabels:
      istio: ingressgateway
  url: oci://ghcr.io/istio-ecosystem/wasm-extensions/basic_auth:1.12.0
  phase: AUTHN
  pluginConfig:
    basic_auth_rules:
      - prefix: "/api"
        request_methods:
          - "GET"
          - "POST"
        credentials:
          - "ok:test"
          - "YWRtaW4zOmFkbWluMw=="
EOF

#
kubectl get WasmPlugin -A
NAMESPACE      NAME         AGE
istio-system   basic-auth   21s



#구성된 Wasm 모듈을 확인
# 자격 증명 없이 테스트
EXT_IP=$(kubectl -n istio-system get svc istio-ingressgateway -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
docker exec -it mypc curl -s -H "Host: webapp.istioinaction.io" http://$EXT_IP/api/catalog -v


# 자격 증명으로 테스트
docker exec -it mypc curl -s -H "Authorization: Basic YWRtaW4zOmFkbWluMw==" -H "Host: webapp.istioinaction.io" http://$EXT_IP/api/catalog -v

자격증명 유무(WASM 플러그인 동작)에 따른 성공

 

 

 

Istio 에 Coraza (코라자) WAF 적용 - KrBlog

CorazaGo 언어로 개발된 **오픈소스 웹 애플리케이션 방화벽(WAF)**으로, **OWASP CRS(핵심 규칙 세트)**와 완벽 호환되며 ModSecurity SecLang 규칙을 지원한다.

 

 

 

#
kubectl apply -f - <<EOF
apiVersion: extensions.istio.io/v1alpha1
kind: WasmPlugin
metadata:
  name: coraza-ingressgateway
  namespace: istio-system
spec:
  selector:
    matchLabels:
      istio: ingressgateway
  url: oci://ghcr.io/corazawaf/coraza-proxy-wasm
  phase: AUTHN
  pluginConfig:
    default_directives: default
    directives_map:
      default:
      - Include @demo-conf
      - SecDebugLogLevel 9
      - SecRuleEngine On
      - Include @crs-setup-conf
      - Include @owasp_crs/*.conf
EOF

#
kubectl logs -n istio-system -l app=istio-ingressgateway | grep -i wasm 
2025-05-18T09:22:39.344842Z     info    wasm    fetching image corazawaf/coraza-proxy-wasm from registry ghcr.io with tag latest

#
kubectl get WasmPlugin -A
NAMESPACE      NAME                    AGE
istio-system   coraza-ingressgateway   9s
#동작 확인

#
curl -s http://webapp.istioinaction.io:30000/api/catalog

# XSS (Cross-Site Scripting) 공격 시도 : REQUEST-941-APPLICATION-ATTACK-XSS
curl -s 'http://webapp.istioinaction.io:30000/api/catalog?arg=<script>alert(0)</script>' -IL
HTTP/1.1 403 Forbidden

# 로그 모니터링
kubectl logs -n istio-system -l app=istio-ingressgateway -f
2025-05-18T09:34:21.049522Z     critical        envoy wasm external/envoy/source/extensions/common/wasm/context.cc:1157      wasm log istio-system.coraza-ingressgateway: [client "172.18.0.1"] Coraza: Access denied (phase 1). Inbound Anomaly Score Exceeded in phase 1 (Total Score: 20) [file "@owasp_crs/REQUEST-949-BLOCKING-EVALUATION.conf"] [line "11347"] [id "949111"] [rev ""] [msg "Inbound Anomaly Score Exceeded in phase 1 (Total Score: 20)"] [data ""] [severity "emergency"] [ver "OWASP_CRS/4.0.0-rc2"] [maturity "0"] [accuracy "0"] [tag "anomaly-evaluation"] [hostname "10.10.0.10"] [uri "/api/catalog?arg=<script>alert(0)</script>"] [unique_id "ztBHScSiiDnOiwJfmOy"]      thread=30
[2025-05-18T09:34:21.014Z] "HEAD /api/catalog?arg=<script>alert(0)</script> HTTP/1.1" 403 -  - "-" 0 0 35 - "172.18.0.1" "curl/8.7.1" "27f43c69-359d-9d95-99cd-553081bb9346" "webapp.istioinaction.io:30000" "-" outbound|80||webapp.istioinaction.svc.cluster.local - 10.10.0.10:8080 172.18.0.1:65166 - -


# SQLI phase 2 (reading the body request)
curl -i -X POST 'http://webapp.istioinaction.io:30000/api/catalog' --data "1%27%20ORDER%20BY%203--%2B"
HTTP/1.1 403 Forbidden

# 로그 모니터링
kubectl logs -n istio-system -l app=istio-ingressgateway -f
2025-05-18T09:42:40.613124Z     critical        envoy wasm external/envoy/source/extensions/common/wasm/context.cc:1157      wasm log istio-system.coraza-ingressgateway: [client "172.18.0.1"] Coraza: Access denied (phase 2). Inbound Anomaly Score Exceeded (Total Score: 10) [file "@owasp_crs/REQUEST-949-BLOCKING-EVALUATION.conf"] [line "11358"] [id "949110"] [rev ""] [msg "Inbound Anomaly Score Exceeded (Total Score: 10)"] [data ""] [severity "emergency"] [ver "OWASP_CRS/4.0.0-rc2"] [maturity "0"] [accuracy "0"] [tag "anomaly-evaluation"] [hostname "10.10.0.10"] [uri "/api/catalog"] [unique_id "GYSnZlurGTXFtujeNgl"]  thread=34
[2025-05-18T09:42:40.575Z] "POST /api/catalog HTTP/1.1" 403 -  - "-" 26 0 39 - "172.18.0.1" "curl/8.7.1" "c30d47e1-5631-99ae-91f2-5dfd8174b421" "webapp.istioinaction.io:30000" "10.10.0.16:8080" outbound|80||webapp.istioinaction.svc.cluster.local 10.10.0.10:44518 10.10.0.10:8080 172.18.0.1:63266 - -
...

WAF(WASM)으로 인해 악의적인 접근이 차단된것을 알 수 있다.

 

 

+ Recent posts