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 internal_address_config: cidr_ranges: - address_prefix: 10.0.0.0 prefix_len: 8 - address_prefix: 172.16.0.0 prefix_len: 12 - address_prefix: 192.168.0.0 prefix_len: 16 - address_prefix: 127.0.0.0 prefix_len: 8 route_config: name: local_route virtual_hosts: - name: juwan_services domains: ["*"] routes: - match: path: /healthz direct_response: status: 200 body: inline_string: ok typed_per_filter_config: envoy.filters.http.ext_authz: "@type": type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute disabled: true - match: path: /api/users/login route: cluster: user_api_cluster timeout: 30s typed_per_filter_config: envoy.filters.http.ext_authz: "@type": type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute disabled: true - match: path: /api/users/register route: cluster: user_api_cluster timeout: 30s typed_per_filter_config: envoy.filters.http.ext_authz: "@type": type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute disabled: true - match: prefix: /api/users route: cluster: user_api_cluster timeout: 30s - match: path: /api/email/verification-code/send route: cluster: email_api_cluster timeout: 30s typed_per_filter_config: envoy.filters.http.ext_authz: "@type": type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute disabled: true - match: prefix: /api/email route: cluster: email_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_HEADER = "xsrf-token" local TOKEN_COOKIE = "__Host-XSRF-TOKEN" local GUARD_COOKIE = "__Host-XSRF-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 token_cookie = cookies[string.lower(TOKEN_COOKIE)] local guard_cookie = cookies[string.lower(GUARD_COOKIE)] request_handle:streamInfo():dynamicMetadata():set("csrf", "need_set_token_cookie", token_cookie == nil or token_cookie == "") request_handle:streamInfo():dynamicMetadata():set("csrf", "need_set_guard_cookie", guard_cookie == nil or guard_cookie == "") if token_cookie == nil or token_cookie == "" then token_cookie = build_token(headers:get("x-request-id")) request_handle:streamInfo():dynamicMetadata():set("csrf", "token_value", token_cookie) else request_handle:streamInfo():dynamicMetadata():set("csrf", "token_value", token_cookie) end if guard_cookie == nil or guard_cookie == "" then guard_cookie = build_token(headers:get("x-request-id")) request_handle:streamInfo():dynamicMetadata():set("csrf", "guard_value", guard_cookie) else request_handle:streamInfo():dynamicMetadata():set("csrf", "guard_value", guard_cookie) end if is_safe_method(method) then return end local token_header = headers:get(TOKEN_HEADER) if token_header == nil or token_header == "" then request_handle:respond( {[":status"] = "403", ["content-type"] = "application/json"}, '{"code":403,"message":"missing XSRF-TOKEN header"}' ) return end if token_cookie == nil or token_cookie == "" or guard_cookie == nil or guard_cookie == "" then request_handle:respond( {[":status"] = "403", ["content-type"] = "application/json"}, '{"code":403,"message":"missing csrf cookies"}' ) return end if token_header ~= token_cookie then request_handle:respond( {[":status"] = "403", ["content-type"] = "application/json"}, '{"code":403,"message":"xsrf 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=/; Max-Age=7200; SameSite=Strict; Secure" ) 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=/; Max-Age=7200; SameSite=Strict; Secure; HttpOnly" ) end end - name: envoy.filters.http.jwt_authn typed_config: "@type": type.googleapis.com/envoy.extensions.filters.http.jwt_authn.v3.JwtAuthentication providers: juwan_user_jwt: issuer: "juwan-user-rpc" from_cookies: - "JToken" local_jwks: inline_string: '{"keys":[{"kty":"oct","k":"MGUyMWE3ZDhjMTQ5ZDg1MWViOWU0MGM3OTE2NWVkYTBlOTE5ZWRkZDU1YjYzOGJjOWRiNzM0NTc4NDIyMjlkZQ","alg":"HS256","use":"sig","kid":"juwan-hs256-1"}]}' forward: false claim_to_headers: - header_name: "x-auth-user-id" claim_name: "UserId" - header_name: "x-auth-is-admin" claim_name: "IsAdmin" rules: - match: path: "/healthz" - match: path: "/api/users/login" - match: path: "/api/users/register" - match: path: "/api/email/verification-code/send" - match: prefix: "/api/users" requires: provider_name: juwan_user_jwt - match: prefix: "/api/email" requires: provider_name: juwan_user_jwt - name: envoy.filters.http.ext_authz typed_config: "@type": type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthz transport_api_version: V3 failure_mode_allow: false with_request_body: max_request_bytes: 8192 allow_partial_message: true grpc_service: envoy_grpc: cluster_name: authz_adapter_cluster timeout: 0.5s - 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 - name: authz_adapter_cluster connect_timeout: 0.5s type: STRICT_DNS lb_policy: ROUND_ROBIN http2_protocol_options: {} load_assignment: cluster_name: authz_adapter_cluster endpoints: - lb_endpoints: - endpoint: address: socket_address: address: authz-adapter-svc.juwan.svc.cluster.local port_value: 9002 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: serviceAccountName: envoy-gateway 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 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