apiVersion: v1 kind: ConfigMap metadata: name: envoy-config namespace: juwan data: envoy.yaml: | static_resources: listeners: - name: ingress_http address: socket_address: address: 0.0.0.0 port_value: 8080 filter_chains: - filters: - name: envoy.filters.network.http_connection_manager typed_config: "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager stat_prefix: ingress_http codec_type: AUTO generate_request_id: true use_remote_address: true route_config: name: local_route virtual_hosts: - name: juwan_services domains: ["*"] routes: - match: path: /healthz direct_response: status: 200 body: inline_string: ok - match: prefix: /api/email route: cluster: email_api_cluster timeout: 30s - match: prefix: /api/users route: cluster: user_api_cluster timeout: 30s - match: prefix: / direct_response: status: 404 body: inline_string: "gateway route not found" http_filters: - name: envoy.filters.http.lua typed_config: "@type": type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua inline_code: | local TOKEN_COOKIE = "csrf_token" local GUARD_COOKIE = "csrf_guard" local TOKEN_HEADER = "x-csrf-token" local GUARD_HEADER = "x-csrf-guard" local seeded = false local function seed_random() if seeded then return end seeded = true math.randomseed(os.time()) end local function split_cookie(header) local out = {} if not header then return out end for pair in string.gmatch(header, "([^;]+)") do local key, value = string.match(pair, "^%s*([^=]+)=?(.*)$") if key ~= nil and value ~= nil then out[string.lower(key)] = value end end return out end local function is_safe_method(method) return method == "GET" or method == "HEAD" or method == "OPTIONS" end local function build_token(request_id) seed_random() local rnd = tostring(math.random(100000, 999999)) local rid = request_id or "rid" return tostring(os.time()) .. "-" .. rid .. "-" .. rnd end function envoy_on_request(request_handle) local headers = request_handle:headers() local method = headers:get(":method") local cookie_header = headers:get("cookie") local cookies = split_cookie(cookie_header) local csrf_token_cookie = cookies[TOKEN_COOKIE] local csrf_guard_cookie = cookies[GUARD_COOKIE] request_handle:streamInfo():dynamicMetadata():set("csrf", "need_set_token_cookie", csrf_token_cookie == nil or csrf_token_cookie == "") request_handle:streamInfo():dynamicMetadata():set("csrf", "need_set_guard_cookie", csrf_guard_cookie == nil or csrf_guard_cookie == "") if csrf_token_cookie == nil or csrf_token_cookie == "" then csrf_token_cookie = build_token(headers:get("x-request-id")) request_handle:streamInfo():dynamicMetadata():set("csrf", "token_value", csrf_token_cookie) else request_handle:streamInfo():dynamicMetadata():set("csrf", "token_value", csrf_token_cookie) end if csrf_guard_cookie == nil or csrf_guard_cookie == "" then csrf_guard_cookie = build_token(headers:get("x-request-id")) request_handle:streamInfo():dynamicMetadata():set("csrf", "guard_value", csrf_guard_cookie) else request_handle:streamInfo():dynamicMetadata():set("csrf", "guard_value", csrf_guard_cookie) end if is_safe_method(method) then return end local csrf_token_header = headers:get(TOKEN_HEADER) local csrf_guard_header = headers:get(GUARD_HEADER) if csrf_token_header == nil or csrf_guard_header == nil then request_handle:respond( {[":status"] = "403", ["content-type"] = "application/json"}, '{"code":403,"message":"missing csrf headers"}' ) return end if csrf_token_cookie == nil or csrf_guard_cookie == nil then request_handle:respond( {[":status"] = "403", ["content-type"] = "application/json"}, '{"code":403,"message":"missing csrf cookies"}' ) return end if csrf_token_header ~= csrf_token_cookie or csrf_guard_header ~= csrf_guard_cookie then request_handle:respond( {[":status"] = "403", ["content-type"] = "application/json"}, '{"code":403,"message":"csrf token mismatch"}' ) return end end function envoy_on_response(response_handle) local metadata = response_handle:streamInfo():dynamicMetadata():get("csrf") if metadata == nil then return end local token_value = metadata["token_value"] local guard_value = metadata["guard_value"] if metadata["need_set_token_cookie"] == true and token_value ~= nil and token_value ~= "" then response_handle:headers():add( "set-cookie", TOKEN_COOKIE .. "=" .. token_value .. "; Path=/; SameSite=Strict" ) end if metadata["need_set_guard_cookie"] == true and guard_value ~= nil and guard_value ~= "" then response_handle:headers():add( "set-cookie", GUARD_COOKIE .. "=" .. guard_value .. "; Path=/; SameSite=Strict" ) end end - name: envoy.filters.http.router typed_config: "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router clusters: - name: user_api_cluster connect_timeout: 2s type: STRICT_DNS lb_policy: ROUND_ROBIN load_assignment: cluster_name: user_api_cluster endpoints: - lb_endpoints: - endpoint: address: socket_address: address: user-api-svc.juwan.svc.cluster.local port_value: 8888 - name: email_api_cluster connect_timeout: 2s type: STRICT_DNS lb_policy: ROUND_ROBIN load_assignment: cluster_name: email_api_cluster endpoints: - lb_endpoints: - endpoint: address: socket_address: address: email-api-svc.juwan.svc.cluster.local port_value: 8888 admin: access_log_path: /tmp/admin_access.log address: socket_address: address: 0.0.0.0 port_value: 9901 --- apiVersion: apps/v1 kind: Deployment metadata: name: envoy-gateway namespace: juwan labels: app: envoy-gateway spec: replicas: 2 revisionHistoryLimit: 5 selector: matchLabels: app: envoy-gateway template: metadata: labels: app: envoy-gateway spec: containers: - name: envoy image: envoyproxy/envoy:v1.31-latest imagePullPolicy: IfNotPresent command: ["/usr/local/bin/envoy"] args: - "-c" - "/etc/envoy/envoy.yaml" - "--log-level" - "info" ports: - containerPort: 8080 name: http - containerPort: 9901 name: admin livenessProbe: httpGet: path: /healthz port: 8080 initialDelaySeconds: 10 periodSeconds: 15 readinessProbe: httpGet: path: /healthz port: 8080 initialDelaySeconds: 5 periodSeconds: 10 resources: requests: cpu: 100m memory: 128Mi limits: cpu: 500m memory: 512Mi volumeMounts: - name: envoy-config mountPath: /etc/envoy volumes: - name: envoy-config configMap: name: envoy-config --- apiVersion: v1 kind: Service metadata: name: envoy-gateway namespace: juwan spec: selector: app: envoy-gateway ports: - name: http port: 80 targetPort: 8080 - name: admin port: 9901 targetPort: 9901 type: ClusterIP