EnvoyFilter
対応バージョン: Istio 1.28+ 最終更新: February 19, 2026
EnvoyFilter は、Envoy proxy 設定を直接カスタマイズできる高度な機能です。
目次
概要
EnvoyFilter では、次のことができます。
- カスタムヘッダーの追加、変更、削除
- Rate Limiting
- 外部認可
- WASM plugin の統合
構造
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
name: custom-filter
namespace: default
spec:
workloadSelector:
labels:
app: myapp
configPatches:
- applyTo: HTTP_FILTER
match:
context: SIDECAR_OUTBOUND
listener:
filterChain:
filter:
name: "envoy.filters.network.http_connection_manager"
patch:
operation: INSERT_BEFORE
value:
name: envoy.filters.http.lua
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua
inline_code: |
function envoy_on_request(request_handle)
request_handle:headers():add("x-custom-header", "value")
end主なユースケース
1. カスタムヘッダーの追加
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
name: add-header
spec:
workloadSelector:
labels:
app: myapp
configPatches:
- applyTo: HTTP_FILTER
match:
context: SIDECAR_OUTBOUND
patch:
operation: INSERT_BEFORE
value:
name: envoy.filters.http.lua
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua
inline_code: |
function envoy_on_request(request_handle)
request_handle:headers():add("x-request-id", request_handle:headers():get(":authority"))
request_handle:headers():add("x-forwarded-proto", "https")
end2. Rate Limiting
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
name: ratelimit
spec:
workloadSelector:
labels:
app: api-service
configPatches:
- applyTo: HTTP_FILTER
match:
context: SIDECAR_INBOUND
patch:
operation: INSERT_BEFORE
value:
name: envoy.filters.http.local_ratelimit
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.local_ratelimit.v3.LocalRateLimit
stat_prefix: http_local_rate_limiter
token_bucket:
max_tokens: 100
tokens_per_fill: 10
fill_interval: 1s3. WASM Plugin
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
name: wasm-filter
spec:
workloadSelector:
labels:
app: myapp
configPatches:
- applyTo: HTTP_FILTER
match:
context: SIDECAR_INBOUND
patch:
operation: INSERT_BEFORE
value:
name: envoy.filters.http.wasm
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.wasm.v3.Wasm
config:
vm_config:
runtime: "envoy.wasm.runtime.v8"
code:
local:
filename: "/var/local/lib/wasm-filters/my_plugin.wasm"X-Forwarded-For とホップ設定
X-Forwarded-For(XFF)ヘッダーとホップ数の制御は、proxy チェーン環境で実際のクライアント IP を追跡するために重要です。
X-Forwarded-For の概要
XFF 設定オプション
1. use_remote_address 設定
use_remote_address は、Envoy が XFF ヘッダーを処理する方法を決定します。
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
name: xff-config
namespace: istio-system
spec:
workloadSelector:
labels:
istio: ingressgateway
configPatches:
- applyTo: NETWORK_FILTER
match:
context: GATEWAY
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
use_remote_address: true
xff_num_trusted_hops: 1use_remote_address のオプション:
| 設定 | 動作 | 利用シナリオ |
|---|---|---|
true | downstream アドレスを XFF に追加して信頼する | Edge Proxy(インターネットから直接アクセス) |
false | downstream アドレスを信頼せず、XFF をそのまま渡す | Internal Proxy(信頼できる proxy の背後) |
2. xff_num_trusted_hops 設定
xff_num_trusted_hops は、XFF ヘッダー内で信頼するホップ数を定義します。
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
name: xff-trusted-hops
namespace: istio-system
spec:
workloadSelector:
labels:
istio: ingressgateway
configPatches:
- applyTo: NETWORK_FILTER
match:
context: GATEWAY
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
use_remote_address: true
xff_num_trusted_hops: 2 # Trust last 2 hops
skip_xff_append: falsexff_num_trusted_hops の計算例:
X-Forwarded-For: 203.0.113.5, 10.0.1.100, 10.0.2.50, 10.244.1.10
[Client IP] [Proxy 1] [Proxy 2] [Proxy 3]
xff_num_trusted_hops: 0 -> Don't trust
-> Client IP: 10.244.1.10 (last hop)
xff_num_trusted_hops: 1 -> Trust last 1
-> Client IP: 10.0.2.50
xff_num_trusted_hops: 2 -> Trust last 2
-> Client IP: 10.0.1.100
xff_num_trusted_hops: 3 -> Trust last 3
-> Client IP: 203.0.113.5 (actual client)シナリオ別の設定
シナリオ 1: AWS ALB + Istio Gateway
設定:
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
name: gateway-xff-config
namespace: istio-system
spec:
workloadSelector:
labels:
istio: ingressgateway
configPatches:
- applyTo: NETWORK_FILTER
match:
context: GATEWAY
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
use_remote_address: true
xff_num_trusted_hops: 1 # Trust only ALB
skip_xff_append: false説明:
use_remote_address: true: Gateway は Edge Proxy として動作しますxff_num_trusted_hops: 1: ALB(最後のホップ)を信頼します- 結果: 実際のクライアント IP(
203.0.113.5)が正しく抽出されます
シナリオ 2: Client -> CloudFront -> ALB -> Gateway
設定:
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
name: gateway-xff-cf-alb
namespace: istio-system
spec:
workloadSelector:
labels:
istio: ingressgateway
configPatches:
- applyTo: NETWORK_FILTER
match:
context: GATEWAY
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
use_remote_address: true
xff_num_trusted_hops: 2 # Trust CloudFront + ALB
skip_xff_append: falseXFF の計算:
X-Forwarded-For: 203.0.113.5, 172.64.0.1, 10.0.1.100
[Actual IP] [CloudFront IP] [ALB IP]
xff_num_trusted_hops: 2 -> Trust last 2 (CloudFront, ALB)
-> Actual client IP: 203.0.113.5シナリオ 3: Client -> CloudFront -> NLB -> ALB -> Gateway
設定:
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
name: gateway-xff-cf-nlb-alb
namespace: istio-system
spec:
workloadSelector:
labels:
istio: ingressgateway
configPatches:
- applyTo: NETWORK_FILTER
match:
context: GATEWAY
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
use_remote_address: true
xff_num_trusted_hops: 2 # Trust CloudFront + ALB (NLB is L4 so not counted)
skip_xff_append: false重要: NLB は L4 load balancer であるため、XFF ヘッダーの読み取りや変更は行いません。したがって、XFF チェーンには影響しません。
XFF の計算:
X-Forwarded-For: 203.0.113.5, 172.64.0.1, 10.0.1.100
[Actual IP] [CloudFront IP] [ALB IP]
NLB has no effect on XFF (L4 LB)
xff_num_trusted_hops: 2 -> Trust last 2 (CloudFront, ALB)
-> Actual client IP: 203.0.113.5シナリオ 4: Client -> ALB -> Gateway(直接接続)
設定:
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
name: gateway-xff-alb-only
namespace: istio-system
spec:
workloadSelector:
labels:
istio: ingressgateway
configPatches:
- applyTo: NETWORK_FILTER
match:
context: GATEWAY
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
use_remote_address: true
xff_num_trusted_hops: 1 # Trust only ALB
skip_xff_append: falseXFF の計算:
X-Forwarded-For: 203.0.113.5, 10.0.1.100
[Actual IP] [ALB IP]
xff_num_trusted_hops: 1 -> Trust last 1 (ALB)
-> Actual client IP: 203.0.113.5シナリオ 5: 内部 Service 通信(Sidecar)
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
name: sidecar-xff-config
namespace: default
spec:
workloadSelector:
labels:
app: backend-service
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
use_remote_address: false # Internal proxy, trusted environment
xff_num_trusted_hops: 0
skip_xff_append: false追加の XFF オプション
skip_xff_append
現在のホップを XFF ヘッダーに追加しません。
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
name: xff-skip-append
namespace: istio-system
spec:
workloadSelector:
labels:
istio: ingressgateway
configPatches:
- applyTo: NETWORK_FILTER
match:
context: GATEWAY
patch:
operation: MERGE
value:
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
skip_xff_append: true # Don't modify XFF header利用シナリオ: 特定の XFF チェーンをデバッグまたは維持する必要がある場合
Via ヘッダーの設定
Via ヘッダーで proxy チェーンを追跡します。
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
name: via-header
namespace: istio-system
spec:
workloadSelector:
labels:
istio: ingressgateway
configPatches:
- applyTo: NETWORK_FILTER
match:
context: GATEWAY
patch:
operation: MERGE
value:
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
via: "istio-gateway"実際のクライアント IP 抽出の例
Lua スクリプトで実際のクライアント IP を抽出します。
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
name: extract-real-ip
namespace: default
spec:
workloadSelector:
labels:
app: api-service
configPatches:
- applyTo: HTTP_FILTER
match:
context: SIDECAR_INBOUND
listener:
filterChain:
filter:
name: "envoy.filters.network.http_connection_manager"
subFilter:
name: "envoy.filters.http.router"
patch:
operation: INSERT_BEFORE
value:
name: envoy.filters.http.lua
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua
inline_code: |
function envoy_on_request(request_handle)
-- Get X-Forwarded-For header
local xff = request_handle:headers():get("x-forwarded-for")
if xff then
-- First IP is the actual client IP
local client_ip = xff:match("^([^,]+)")
-- Set as custom header
request_handle:headers():add("x-real-ip", client_ip)
request_handle:logInfo("Real Client IP: " .. client_ip)
end
endXFF の検証とデバッグ
1. ヘッダーの検証
# Verify headers inside pod
kubectl exec -it <pod-name> -c istio-proxy -- curl -v localhost:15000/config_dump | \
jq '.configs[] | select(.["@type"] == "type.googleapis.com/envoy.admin.v3.ListenersConfigDump") |
.dynamic_listeners[].active_state.listener.filter_chains[].filters[] |
select(.name == "envoy.filters.network.http_connection_manager") |
.typed_config | {use_remote_address, xff_num_trusted_hops}'2. 実際のリクエストによるテスト
# Test request with XFF header
curl -H "X-Forwarded-For: 203.0.113.5, 10.0.1.100" \
http://your-gateway.example.com/api/test
# Check received headers in application logs
kubectl logs -n default <pod-name> -c app | grep -i "x-forwarded-for"3. Envoy Access Log を有効化
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
name: access-log-xff
namespace: istio-system
spec:
workloadSelector:
labels:
istio: ingressgateway
configPatches:
- applyTo: NETWORK_FILTER
match:
context: GATEWAY
patch:
operation: MERGE
value:
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
access_log:
- name: envoy.access_loggers.file
typed_config:
"@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog
path: /dev/stdout
log_format:
text_format: |
[%START_TIME%] "%REQ(:METHOD)% %REQ(X-ENVOY-ORIGINAL-PATH?:PATH)% %PROTOCOL%"
XFF: "%REQ(X-FORWARDED-FOR)%"
Real IP: "%DOWNSTREAM_REMOTE_ADDRESS%"
Status: %RESPONSE_CODE% Duration: %DURATION%msセキュリティに関する考慮事項
1. XFF スプーフィングの防止
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
name: xff-security
namespace: istio-system
spec:
workloadSelector:
labels:
istio: ingressgateway
configPatches:
- applyTo: NETWORK_FILTER
match:
context: GATEWAY
patch:
operation: MERGE
value:
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
use_remote_address: true
xff_num_trusted_hops: 1
# XFF from untrusted hops is ignored重要: Edge Gateway では、クライアントによる XFF の操作を防ぐために、use_remote_address: true を必ず設定してください。
2. 内部 Service の保護
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
name: internal-xff-strip
namespace: default
spec:
workloadSelector:
labels:
tier: backend
configPatches:
- applyTo: HTTP_FILTER
match:
context: SIDECAR_INBOUND
patch:
operation: INSERT_BEFORE
value:
name: envoy.filters.http.lua
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua
inline_code: |
function envoy_on_request(request_handle)
-- Remove XFF from requests not coming from internal network
local remote_addr = request_handle:streamInfo():downstreamRemoteAddress():ip()
-- Remove XFF if not from 10.0.0.0/8 internal network
if not remote_addr:match("^10%.") then
request_handle:headers():remove("x-forwarded-for")
request_handle:logWarn("Removed potentially spoofed XFF from: " .. remote_addr)
end
endApp 単位で選択的に適用する IP 制限(Gateway + AuthorizationPolicy)
シナリオ: 一部の App(A~E)はすべてのクライアントを許可し、特定の App(F~G)は会社の NAT IP のみを許可します。
アーキテクチャの概要
基本原則
Gateway の役割(すべての App に共通):
- XFF ヘッダーから元のクライアント IP を抽出する
- 信頼できるホップ(
xff_num_trusted_hops)を除外する - アクセス制御は実行しない - IP を正確に識別するだけです
AuthorizationPolicy の役割(特定の App に選択的に適用):
- Gateway が抽出した元の IP に基づくアクセス制御
selectorで特定の App を対象にする- policy を持たない App はすべてのクライアントを許可する
実装例
ステップ 1: Gateway での XFF 処理(すべての App に共通)
# Apply once to istio-system namespace
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
name: gateway-xff-config
namespace: istio-system
spec:
workloadSelector:
labels:
istio: ingressgateway
configPatches:
- applyTo: NETWORK_FILTER
match:
context: GATEWAY # Gateway context
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
use_remote_address: true
xff_num_trusted_hops: 1 # Trust only ALB (2 if CloudFlare present)
skip_xff_append: falseステップ 2: App F に IP 制限を適用(選択的)
# Allow only company NAT IP for App F
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
name: app-f-ip-restriction
namespace: default
spec:
selector:
matchLabels:
app: app-f # Applied only to App F
action: DENY
rules:
- from:
- source:
notRemoteIpBlocks: # Deny if not following IPs
- "203.0.113.0/24" # Company NAT IP rangeステップ 3: App G に IP 制限を適用(選択的)
# Allow same company NAT IP for App G
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
name: app-g-ip-restriction
namespace: default
spec:
selector:
matchLabels:
app: app-g # Applied only to App G
action: DENY
rules:
- from:
- source:
notRemoteIpBlocks:
- "203.0.113.0/24" # Company NAT IP rangeステップ 4: App A~E には AuthorizationPolicy がない(すべてのクライアントを許可)
# No AuthorizationPolicy created for Apps A-E
# = All client IPs allowed処理フロー
Gateway の設定が必要な理由
Gateway の XFF 設定がない場合:
remoteIpBlocksは ALB の IP を読み取る(元の IP ではない)- すべてのリクエストが同じ ALB IP からのものに見えるため、適切にフィルタリングできない
Gateway の XFF 設定がある場合:
- Gateway が XFF ヘッダーから元の IP を正確に抽出する
- AuthorizationPolicy の
remoteIpBlocksが元の IP を使用する - 各 App の AuthorizationPolicy が正しく機能する
テスト
# General user (1.2.3.4) - App A access
curl -H "Host: app-a.example.com" http://<gateway-ip>/
# Expected: 200 OK
# General user (1.2.3.4) - App F access
curl -H "Host: app-f.example.com" http://<gateway-ip>/
# Expected: 403 Forbidden (RBAC: access denied)
# Company user (203.0.113.10) - App F access
curl -H "Host: app-f.example.com" -H "X-Forwarded-For: 203.0.113.10" http://<gateway-ip>/
# Expected: 200 OK
# Check AuthorizationPolicy
kubectl get authorizationpolicy -n default
# Output:
# NAME AGE
# app-f-ip-restriction 5m
# app-g-ip-restriction 5m
# (app-a, app-b, app-c, app-d, app-e are absent)まとめ
| コンポーネント | 対象範囲 | 目的 | 必須か |
|---|---|---|---|
| Gateway EnvoyFilter | すべての App | XFF から元の IP を抽出 | 必須(1 回) |
| App F AuthorizationPolicy | App F のみ | 会社の NAT IP のみを許可 | 選択的 |
| App G AuthorizationPolicy | App G のみ | 会社の NAT IP のみを許可 | 選択的 |
| App A-E AuthorizationPolicy | なし | 制限なし(すべての IP を許可) | 不要 |
要点:
- Gateway の XFF 設定は IP を抽出するだけで、アクセス制御は行いません
- AuthorizationPolicy は特定の App にのみ選択的に適用します
- policy のない App はすべてのクライアントを自動的に許可します
XFF ベースの IP アクセス制御
X-Forwarded-For ヘッダーにある元のクライアント IP に基づくアクセス制御を実装します。
推奨方法: AuthorizationPolicy の remoteIpBlocks を使用します(EnvoyFilter よりも宣言的で安全です)。
1. AuthorizationPolicy による IP Whitelist(推奨)
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
name: ip-whitelist
namespace: default
spec:
selector:
matchLabels:
app: api-service
action: ALLOW
rules:
- from:
- source:
remoteIpBlocks: # Original IP from X-Forwarded-For
- "203.0.113.10/32"
- "203.0.113.11/32"
- "198.51.100.0/24"利点:
- 宣言的で理解しやすい
- Istio が XFF ヘッダーを自動的に解析する
- CIDR 範囲をサポートする
- Istio upgrade 時も安全
- 別途コードは不要
2. AuthorizationPolicy による IP Blacklist(推奨)
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
name: ip-blacklist
namespace: default
spec:
selector:
matchLabels:
app: api-service
action: DENY
rules:
- from:
- source:
remoteIpBlocks: # IPs to block
- "192.0.2.100/32"
- "192.0.2.101/32"
- "198.51.100.0/24"3. パス単位の IP 制限(AuthorizationPolicy)
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
name: admin-path-ip-restriction
namespace: default
spec:
selector:
matchLabels:
app: api-service
action: ALLOW
rules:
# Admin paths only specific IPs
- to:
- operation:
paths: ["/admin/*"]
from:
- source:
remoteIpBlocks:
- "203.0.113.10/32"
- "203.0.113.11/32"
# General paths all IPs
- to:
- operation:
notPaths: ["/admin/*"]
from:
- source:
remoteIpBlocks:
- "0.0.0.0/0"4. 複雑な Policy: IP + Path + Method
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
name: complex-access-control
namespace: default
spec:
selector:
matchLabels:
app: api-service
action: ALLOW
rules:
# Admin: All paths accessible
- from:
- source:
remoteIpBlocks:
- "203.0.113.10/32" # Admin IP
# Internal network: API read only
- to:
- operation:
paths: ["/api/v1/*"]
methods: ["GET"]
from:
- source:
remoteIpBlocks:
- "10.0.0.0/8" # Internal network
# Public network: Public API only
- to:
- operation:
paths: ["/api/v1/public/*"]
methods: ["GET", "POST"]
from:
- source:
remoteIpBlocks:
- "0.0.0.0/0"5. Whitelist + Blacklist の組み合わせ
# Blacklist applied first (higher priority)
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
name: ip-blacklist
namespace: default
spec:
selector:
matchLabels:
app: api-service
action: DENY
rules:
- from:
- source:
remoteIpBlocks:
- "192.0.2.100/32"
- "192.0.2.101/32"
- "198.51.100.0/24"
---
# Whitelist applied
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
name: ip-whitelist
namespace: default
spec:
selector:
matchLabels:
app: api-service
action: ALLOW
rules:
- from:
- source:
remoteIpBlocks:
- "203.0.113.0/24"
- "198.51.100.0/22" # Larger range処理順序: DENY policy が最初に評価されるため、Blacklist が優先されます。
6. テスト
# 1. Test from allowed IP
curl -H "X-Forwarded-For: 203.0.113.10" http://api-service:8080/api
# 2. Test from blocked IP
curl -H "X-Forwarded-For: 192.0.2.100" http://api-service:8080/api
# 3. IP not in Whitelist
curl -H "X-Forwarded-For: 1.2.3.4" http://api-service:8080/api
# 4. Admin path access test
curl -H "X-Forwarded-For: 203.0.113.10" http://api-service:8080/admin/users
curl -H "X-Forwarded-For: 10.0.1.100" http://api-service:8080/admin/users
# 5. Check policies
kubectl get authorizationpolicy -n default
# 6. Check logs (Envoy access logs)
kubectl logs -n default <pod-name> -c istio-proxy | grep "403"7. 高度な設定: EnvoyFilter によるカスタム拒否メッセージ(任意)
AuthorizationPolicy はデフォルトで RBAC: access denied メッセージを返します。カスタムメッセージが必要な場合にのみ EnvoyFilter を追加してください。
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
name: custom-deny-message
namespace: default
spec:
workloadSelector:
matchLabels:
app: api-service
configPatches:
- applyTo: HTTP_FILTER
match:
context: SIDECAR_INBOUND
listener:
filterChain:
filter:
name: "envoy.filters.network.http_connection_manager"
subFilter:
name: "envoy.filters.http.router"
patch:
operation: INSERT_BEFORE
value:
name: envoy.filters.http.lua
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua
inline_code: |
function envoy_on_response(response_handle)
local status = response_handle:headers():get(":status")
local body = response_handle:body():getBytes(0, 1000)
-- Detect AuthorizationPolicy's 403 response
if status == "403" and body and body:match("RBAC: access denied") then
response_handle:body():setBytes('{"error": "Access denied", "code": "IP_NOT_ALLOWED"}')
response_handle:headers():replace("content-type", "application/json")
end
endベストプラクティス
Edge Gateway の設定:
use_remote_address: truexff_num_trusted_hops: 信頼できる proxy の数に設定する
内部 Sidecar の設定:
use_remote_address: falseskip_xff_append: false
検証とテスト:
- 本番デプロイ前に XFF の動作を十分にテストする
- access log で実際のクライアント IP の抽出を確認する
セキュリティ:
- Edge で XFF スプーフィングを防止する
- 信頼できない送信元からの XFF を無視する
静的レスポンスの設定
特定のリクエストに対して、backend Service を経由せずに静的レスポンスを直接返すことができます。これは、メンテナンスモード、エラーページ、health check レスポンスなどに役立ちます。
静的レスポンスの概要
ユースケース
- メンテナンスモード: 503 Service Unavailable を返す
- Health check endpoint: 200 OK を返す
- カスタムエラーページ: JSON または HTML のエラーレスポンス
- テスト/mock レスポンス: 特定のパスに対する事前定義済みレスポンス
- 即時拒否: auth 失敗時に 401 Unauthorized を即座に返す
実装方法の選択ガイド
Istio には、静的レスポンスを実装する複数の方法があります。
| 方法 | 使用する場合 | 利点 | 欠点 |
|---|---|---|---|
| VirtualService | 単純な静的レスポンス、routing rule との統合 | 宣言的で理解しやすい | カスタマイズに制限がある |
| ProxyConfig | workload ごとの Envoy 設定 | きめ細かな制御、パフォーマンスチューニング | 設定が複雑 |
| AuthorizationPolicy | IP/ヘッダーベースのアクセス制御 | security policy と統合 | 静的レスポンス専用ではない |
| EnvoyFilter | 上記の方法では不十分な場合のみ | 最大限の柔軟性 | 複雑で、upgrade のリスクがある |
推奨: 可能な場合はまず VirtualService と AuthorizationPolicy を使用し、必要な場合にのみ EnvoyFilter を使用してください。
VirtualService による静的レスポンスの実装
1. 基本的な静的レスポンス(directResponse)
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: api-service
namespace: default
spec:
hosts:
- api-service
http:
# Maintenance mode
- match:
- uri:
prefix: "/api/v1"
directResponse:
status: 503
body:
string: |
{
"error": {
"code": "SERVICE_UNAVAILABLE",
"message": "The service is currently under maintenance",
"timestamp": "2025-11-26T10:00:00Z",
"retry_after": 3600
}
}
headers:
response:
set:
content-type: "application/json"
retry-after: "3600"結果:
$ curl -i http://api-service/api/v1/users
HTTP/1.1 503 Service Unavailable
content-type: application/json
retry-after: 3600
{
"error": {
"code": "SERVICE_UNAVAILABLE",
"message": "The service is currently under maintenance",
"timestamp": "2025-11-26T10:00:00Z",
"retry_after": 3600
}
}2. Health Check Endpoint
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: api-service-health
spec:
hosts:
- api-service
http:
# Health check path
- match:
- uri:
exact: "/health"
directResponse:
status: 200
body:
string: "OK"
# Normal traffic
- route:
- destination:
host: api-service3. 特定のパスをブロック
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: block-admin
spec:
hosts:
- api-service
http:
# Block Admin paths
- match:
- uri:
prefix: "/admin"
directResponse:
status: 403
body:
string: |
{
"error": "Access to admin endpoints is forbidden"
}
headers:
response:
set:
content-type: "application/json"
# Normal traffic
- route:
- destination:
host: api-service4. Fault Injection によるエラーシミュレーション
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: fault-injection
spec:
hosts:
- api-service
http:
- fault:
abort:
httpStatus: 503
percentage:
value: 100 # Apply to 100% traffic
route:
- destination:
host: api-serviceAuthorizationPolicy によるアクセス制御
1. 送信元 IP ベースのアクセス制御
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
name: ip-whitelist
namespace: default
spec:
selector:
matchLabels:
app: api-service
action: ALLOW
rules:
- from:
- source:
ipBlocks:
- "203.0.113.10/32"
- "203.0.113.11/32"
- "198.51.100.0/24"
---
# Default DENY policy
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
name: deny-all
namespace: default
spec:
selector:
matchLabels:
app: api-service
action: DENY
rules:
- from:
- source:
notIpBlocks:
- "203.0.113.10/32"
- "203.0.113.11/32"
- "198.51.100.0/24"2. パス単位の IP 制限
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
name: admin-ip-whitelist
namespace: default
spec:
selector:
matchLabels:
app: api-service
action: ALLOW
rules:
# Admin paths only specific IPs
- to:
- operation:
paths: ["/admin/*"]
from:
- source:
ipBlocks:
- "203.0.113.10/32"
- "203.0.113.11/32"
# General paths all IPs
- to:
- operation:
notPaths: ["/admin/*"]3. X-Forwarded-For ヘッダーベースの制御
AuthorizationPolicy は remoteIpBlocks を使用して、X-Forwarded-For ヘッダーから元の IP を確認できます。
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
name: xff-based-access
namespace: default
spec:
selector:
matchLabels:
app: api-service
action: ALLOW
rules:
- from:
- source:
remoteIpBlocks: # Original IP from X-Forwarded-For header
- "203.0.113.0/24"
- "198.51.100.0/24"重要: remoteIpBlocks を動作させるには、Gateway で xff_num_trusted_hops を正しく設定する必要があります(上記の XFF 設定 を参照)。
4. カスタム拒否レスポンス
AuthorizationPolicy によってブロックされたリクエストはデフォルトで 403 レスポンスを返しますが、EnvoyFilter と組み合わせることでカスタムレスポンスを提供できます。
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
name: block-untrusted-ips
namespace: default
spec:
selector:
matchLabels:
app: api-service
action: DENY
rules:
- from:
- source:
notRemoteIpBlocks:
- "203.0.113.0/24"
---
# Add custom body to 403 response
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
name: custom-deny-response
namespace: default
spec:
workloadSelector:
matchLabels:
app: api-service
configPatches:
- applyTo: HTTP_FILTER
match:
context: SIDECAR_INBOUND
listener:
filterChain:
filter:
name: "envoy.filters.network.http_connection_manager"
subFilter:
name: "envoy.filters.http.router"
patch:
operation: INSERT_BEFORE
value:
name: envoy.filters.http.lua
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua
inline_code: |
function envoy_on_response(response_handle)
local status = response_handle:headers():get(":status")
if status == "403" then
response_handle:body():setBytes('{"error": "Access denied", "code": "FORBIDDEN"}')
response_handle:headers():replace("content-type", "application/json")
end
endProxyConfig による Envoy 設定
ProxyConfig では、workload ごとにきめ細かな Envoy proxy 設定を行えます。
1. Workload ごとの Proxy 設定
apiVersion: networking.istio.io/v1beta1
kind: ProxyConfig
metadata:
name: api-service-config
namespace: default
spec:
selector:
matchLabels:
app: api-service
concurrency: 4 # Worker thread count
# Access log settings
accessLogging:
- providers:
- name: envoy
file:
path: /dev/stdout
format: |
[%START_TIME%] "%REQ(:METHOD)% %REQ(X-ENVOY-ORIGINAL-PATH?:PATH)% %PROTOCOL%"
Status: %RESPONSE_CODE% Duration: %DURATION%ms
Client IP: %REQ(X-FORWARDED-FOR)%
# Timeout settings
connectionTimeout: 10s
drainDuration: 5s
# Resource limits
resourceLimits:
maxConnections: 100002. 統計と Metrics の設定
apiVersion: networking.istio.io/v1beta1
kind: ProxyConfig
metadata:
name: monitoring-config
namespace: default
spec:
selector:
matchLabels:
app: api-service
# Statistics settings
stats:
inclusionPrefixes:
- "cluster.outbound"
- "http.inbound"
inclusionSuffixes:
- "upstream_rq_time"
# Tracing settings
tracing:
sampling: 100.0 # 100% sampling
maxPathTagLength: 256統合例: VirtualService + AuthorizationPolicy
実際の本番シナリオ向けの統合例です。
シナリオ: API Service の保護
# 1. IP based access control
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
name: api-access-control
namespace: default
spec:
selector:
matchLabels:
app: api-service
action: ALLOW
rules:
# Admin API only specific IPs
- to:
- operation:
paths: ["/api/v1/admin/*"]
from:
- source:
remoteIpBlocks:
- "203.0.113.10/32" # Admin IP
# Public API all trusted networks
- to:
- operation:
paths: ["/api/v1/public/*"]
from:
- source:
remoteIpBlocks:
- "0.0.0.0/0" # All IPs (in practice only trusted ranges)
---
# 2. Routing and static responses
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: api-service-routes
spec:
hosts:
- api-service
http:
# Health check
- match:
- uri:
exact: "/health"
directResponse:
status: 200
body:
string: '{"status": "healthy"}'
headers:
response:
set:
content-type: "application/json"
# Block legacy API version
- match:
- uri:
prefix: "/api/v0/"
directResponse:
status: 410
body:
string: |
{
"error": "API v0 is deprecated",
"supported_versions": ["v1", "v2"],
"migration_guide": "https://docs.example.com/migration"
}
headers:
response:
set:
content-type: "application/json"
# Normal routing
- route:
- destination:
host: api-service
port:
number: 8080
timeout: 30s
retries:
attempts: 3
perTryTimeout: 10s
---
# 3. Proxy settings
apiVersion: networking.istio.io/v1beta1
kind: ProxyConfig
metadata:
name: api-service-proxy
namespace: default
spec:
selector:
matchLabels:
app: api-service
concurrency: 4
accessLogging:
- providers:
- name: envoy
file:
path: /dev/stdoutLua による動的な静的レスポンス
Lua スクリプトでは、条件に基づいて静的レスポンスを動的に生成できます。
メンテナンス時間帯の自動検出
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
name: maintenance-window
namespace: default
spec:
workloadSelector:
labels:
app: api-service
configPatches:
- applyTo: HTTP_FILTER
match:
context: SIDECAR_INBOUND
listener:
filterChain:
filter:
name: "envoy.filters.network.http_connection_manager"
subFilter:
name: "envoy.filters.http.router"
patch:
operation: INSERT_BEFORE
value:
name: envoy.filters.http.lua
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua
inline_code: |
function envoy_on_request(request_handle)
-- Current time (UTC)
local current_hour = tonumber(os.date("!%H"))
-- Daily maintenance window 2-4 AM
if current_hour >= 2 and current_hour < 4 then
request_handle:respond(
{[":status"] = "503",
["content-type"] = "application/json",
["retry-after"] = "3600"},
'{"error": "Maintenance in progress", "window": "02:00-04:00 UTC"}'
)
end
endリクエストヘッダーベースのレスポンス
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
name: header-based-response
namespace: default
spec:
workloadSelector:
labels:
app: api-service
configPatches:
- applyTo: HTTP_FILTER
match:
context: SIDECAR_INBOUND
listener:
filterChain:
filter:
name: "envoy.filters.network.http_connection_manager"
subFilter:
name: "envoy.filters.http.router"
patch:
operation: INSERT_BEFORE
value:
name: envoy.filters.http.lua
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua
inline_code: |
function envoy_on_request(request_handle)
local api_version = request_handle:headers():get("x-api-version")
-- Unsupported API version
if api_version and api_version == "v1" then
request_handle:respond(
{[":status"] = "410",
["content-type"] = "application/json"},
'{"error": "API v1 is deprecated", "supported_versions": ["v2", "v3"]}'
)
end
endVirtualService との統合
VirtualService と EnvoyFilter は併用して、より複雑な routing シナリオを実装できます。
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: api-service
spec:
hosts:
- api-service
http:
- match:
- uri:
prefix: "/maintenance"
fault:
abort:
httpStatus: 503
percentage:
value: 100
route:
- destination:
host: api-service
- route:
- destination:
host: api-service
---
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
name: maintenance-response-body
namespace: default
spec:
workloadSelector:
labels:
app: api-service
configPatches:
- applyTo: HTTP_FILTER
match:
context: SIDECAR_INBOUND
patch:
operation: INSERT_BEFORE
value:
name: envoy.filters.http.lua
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua
inline_code: |
function envoy_on_response(response_handle)
local status = response_handle:headers():get(":status")
local path = response_handle:headers():get(":path")
-- Add custom body when VirtualService returns 503
if status == "503" and path and path:match("^/maintenance") then
response_handle:body():setBytes('{"message": "Service under maintenance"}')
response_handle:headers():replace("content-type", "application/json")
end
end実践的なシナリオ
シナリオ 1: Blue/Green Deployment 中のトラフィックをブロック
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
name: deployment-block
namespace: production
spec:
workloadSelector:
labels:
app: api-service
version: v1 # Block only old version
configPatches:
- applyTo: HTTP_ROUTE
match:
context: SIDECAR_INBOUND
patch:
operation: MERGE
value:
direct_response:
status: 503
body:
inline_string: |
{
"message": "This version is being deprecated",
"migration": {
"new_endpoint": "https://api-v2.example.com",
"cutoff_date": "2025-12-31"
}
}
response_headers_to_add:
- header:
key: "Content-Type"
value: "application/json"
- header:
key: "X-Migration-Required"
value: "true"シナリオ 2: Rate Limit 超過時の 429 レスポンス
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
name: ratelimit-response
namespace: default
spec:
workloadSelector:
labels:
app: api-service
configPatches:
# Rate Limit filter
- applyTo: HTTP_FILTER
match:
context: SIDECAR_INBOUND
listener:
filterChain:
filter:
name: "envoy.filters.network.http_connection_manager"
subFilter:
name: "envoy.filters.http.router"
patch:
operation: INSERT_BEFORE
value:
name: envoy.filters.http.local_ratelimit
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.local_ratelimit.v3.LocalRateLimit
stat_prefix: http_local_rate_limiter
token_bucket:
max_tokens: 100
tokens_per_fill: 10
fill_interval: 1s
filter_enabled:
runtime_key: local_rate_limit_enabled
default_value:
numerator: 100
denominator: HUNDRED
filter_enforced:
runtime_key: local_rate_limit_enforced
default_value:
numerator: 100
denominator: HUNDRED
response_headers_to_add:
- append: false
header:
key: x-local-rate-limit
value: 'true'
local_rate_limit_per_downstream_connection: false
# Custom 429 response
status:
code: 429
response_headers_to_add:
- header:
key: "Content-Type"
value: "application/json"
# Lua filter to add 429 response body
- applyTo: HTTP_FILTER
match:
context: SIDECAR_INBOUND
listener:
filterChain:
filter:
name: "envoy.filters.network.http_connection_manager"
subFilter:
name: "envoy.filters.http.router"
patch:
operation: INSERT_BEFORE
value:
name: envoy.filters.http.lua
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua
inline_code: |
function envoy_on_response(response_handle)
local status = response_handle:headers():get(":status")
local rate_limited = response_handle:headers():get("x-local-rate-limit")
if status == "429" and rate_limited == "true" then
local body = [[
{
"error": {
"code": "RATE_LIMIT_EXCEEDED",
"message": "Too many requests",
"retry_after": 60
}
}
]]
response_handle:body():setBytes(body)
response_handle:headers():add("Retry-After", "60")
end
endシナリオ 3: Canary Deployment のテストレスポンス
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
name: canary-test-response
namespace: default
spec:
workloadSelector:
labels:
app: api-service
version: canary
configPatches:
- applyTo: HTTP_FILTER
match:
context: SIDECAR_INBOUND
listener:
filterChain:
filter:
name: "envoy.filters.network.http_connection_manager"
subFilter:
name: "envoy.filters.http.router"
patch:
operation: INSERT_BEFORE
value:
name: envoy.filters.http.lua
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua
inline_code: |
function envoy_on_request(request_handle)
local test_header = request_handle:headers():get("x-canary-test")
-- Return predefined response if canary test header present
if test_header == "dry-run" then
request_handle:respond(
{[":status"] = "200",
["content-type"] = "application/json",
["x-canary-version"] = "v2.0.0"},
'{"message": "Canary version response", "version": "v2.0.0"}'
)
end
endテストと検証
静的レスポンスのテスト
# 1. Test basic 503 response
curl -i http://api-service:8080/api/v1
# 2. Test health check endpoint
curl -i http://api-service:8080/health
# 3. Test JSON error response
curl -i -H "Content-Type: application/json" http://api-service:8080/api/v1/users
# 4. Test maintenance window (time manipulation)
kubectl exec -it <pod-name> -c istio-proxy -- date -s "02:30:00"
curl -i http://api-service:8080/api/v1
# 5. Rate Limit test
for i in {1..150}; do
curl -i http://api-service:8080/api/v1
doneEnvoy 設定の確認
# 1. Verify static response route
istioctl proxy-config routes <pod-name> -n default -o json | \
jq '.[] | select(.virtualHosts[].routes[].directResponse != null)'
# 2. Check full route configuration
istioctl proxy-config routes <pod-name> -n default
# 3. Verify EnvoyFilter applied
kubectl get envoyfilter -n default maintenance-mode -o yaml
# 4. Verify via Envoy Admin API
kubectl port-forward <pod-name> 15000:15000
curl http://localhost:15000/config_dump | jq '.configs[] | select(.["@type"] == "type.googleapis.com/envoy.admin.v3.RoutesConfigDump")'ベストプラクティス
明確なエラーメッセージ:
- ユーザーに原因と解決策を提示する
Retry-Afterヘッダーで再試行時間を指定する
一貫したエラー形式:
- すべてのエラーレスポンスで同じ JSON schema を使用する
- HTTP status code と error code の整合性を維持する
Logging と monitoring:
- 静的レスポンスを返す際に log を記録する
- metrics で静的レスポンスの頻度を追跡する
段階的な適用:
- メンテナンスモードに切り替える際は段階的に適用する
- 全面適用前に canary deployment でテストする
Rollback 計画:
- EnvoyFilter を削除して直ちに通常のトラフィックに復帰する
- 緊急時に備えた自動 rollback script を用意する
注意事項
- 優先順位: EnvoyFilter の静的レスポンスが VirtualService より優先される場合があります
- パフォーマンス: Lua script はすべてのリクエストで実行されるため、パフォーマンスへの影響を考慮してください
- セキュリティ: エラーメッセージに機密情報を露出しないよう注意してください
- Caching: 静的レスポンスにも
Cache-Controlヘッダーの設定が必要です - Metrics: 静的レスポンスは通常のレスポンスとは異なる metrics を生成します
実践例
例 1: Request/Response Logging
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
name: request-response-logging
spec:
workloadSelector:
labels:
app: api-service
configPatches:
- applyTo: HTTP_FILTER
match:
context: SIDECAR_INBOUND
patch:
operation: INSERT_BEFORE
value:
name: envoy.filters.http.lua
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua
inline_code: |
function envoy_on_request(request_handle)
request_handle:logInfo("Request: " .. request_handle:headers():get(":path"))
end
function envoy_on_response(response_handle)
response_handle:logInfo("Response: " .. response_handle:headers():get(":status"))
end例 2: JWT 検証
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
name: jwt-auth
spec:
workloadSelector:
labels:
app: api-service
configPatches:
- applyTo: HTTP_FILTER
match:
context: SIDECAR_INBOUND
patch:
operation: INSERT_BEFORE
value:
name: envoy.filters.http.jwt_authn
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.jwt_authn.v3.JwtAuthentication
providers:
auth0:
issuer: "https://example.auth0.com/"
audiences:
- "api.example.com"
remote_jwks:
http_uri:
uri: "https://example.auth0.com/.well-known/jwks.json"
cluster: "auth0_jwks"
timeout: 5sベストプラクティス
- workloadSelector を使用する: 特定の workload にのみ適用する
- まずテスト環境で実施する: 本番前に十分にテストする
- Istio version の互換性: version ごとに API を確認する
- パフォーマンス監視: EnvoyFilter の追加後にパフォーマンスを監視する
トラブルシューティング
# Check EnvoyFilter
kubectl get envoyfilter -A
# Verify Envoy configuration
istioctl proxy-config listeners <pod-name> -n <namespace> -o json
# Check logs
kubectl logs -n <namespace> <pod-name> -c istio-proxy