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 xff_num_trusted_hops: 1 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/v1/auth/login route: cluster: user_api_cluster timeout: 30s rate_limits: - actions: - generic_key: descriptor_value: login - remote_address: {} 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/v1/auth/register route: cluster: user_api_cluster timeout: 30s rate_limits: - actions: - generic_key: descriptor_value: register - remote_address: {} 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/v1/auth/reset-password 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/v1/auth/forgot-password/send route: cluster: email_api_cluster timeout: 30s rate_limits: - actions: - generic_key: descriptor_value: forgot_password_send - remote_address: {} 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/v1/email/verification-code/send route: cluster: email_api_cluster timeout: 30s rate_limits: - actions: - generic_key: descriptor_value: verify_code_send - remote_address: {} 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/v1/games headers: - name: ":method" exact_match: GET route: cluster: game_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/v1/players headers: - name: ":method" exact_match: GET route: cluster: player_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/v1/services headers: - name: ":method" exact_match: GET route: cluster: player_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/v1/shops headers: - name: ":method" exact_match: GET route: cluster: shop_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: safe_regex: google_re2: {} regex: "^/api/v1/shops/[0-9]+$" headers: - name: ":method" exact_match: GET route: cluster: shop_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: safe_regex: google_re2: {} regex: "^/api/v1/users/[0-9]+$" headers: - name: ":method" exact_match: GET 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: safe_regex: google_re2: {} regex: "^/api/v1/users/[0-9]+/posts$" headers: - name: ":method" exact_match: GET route: cluster: community_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: safe_regex: google_re2: {} regex: "^/api/v1/users/[0-9]+/shop$" headers: - name: ":method" exact_match: GET route: cluster: shop_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/v1/posts headers: - name: ":method" exact_match: GET route: cluster: community_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/v1/auth route: cluster: user_api_cluster timeout: 30s - match: prefix: /api/v1/admin route: cluster: user_api_cluster timeout: 30s - match: prefix: /api/v1/users route: cluster: user_api_cluster timeout: 30s - match: prefix: /api/v1/email route: cluster: email_api_cluster timeout: 30s - match: prefix: /api/v1/games route: cluster: game_api_cluster timeout: 30s - match: prefix: /api/v1/players route: cluster: player_api_cluster timeout: 30s - match: prefix: /api/v1/services route: cluster: player_api_cluster timeout: 30s - match: prefix: /api/v1/shops route: cluster: shop_api_cluster timeout: 30s - match: prefix: /api/v1/orders route: cluster: order_api_cluster timeout: 30s - match: prefix: /api/v1/wallet route: cluster: wallet_api_cluster timeout: 30s - match: prefix: /api/v1/posts route: cluster: community_api_cluster timeout: 30s - match: prefix: /api/v1/comments route: cluster: community_api_cluster timeout: 30s - match: path: /api/v1/upload route: cluster: objectstory_api_cluster timeout: 30s - match: prefix: /api/v1/files route: cluster: objectstory_api_cluster timeout: 30s - match: prefix: / direct_response: status: 404 body: inline_string: gateway route not found access_log: - name: envoy.access_loggers.stdout typed_config: "@type": type.googleapis.com/envoy.extensions.access_loggers.stream.v3.StdoutAccessLog log_format: json_format: start_time: "%START_TIME%" method: "%REQ(:METHOD)%" path: "%REQ(X-ENVOY-ORIGINAL-PATH?:PATH)%" protocol: "%PROTOCOL%" authority: "%REQ(:AUTHORITY)%" user_agent: "%REQ(USER-AGENT)%" request_id: "%REQ(X-REQUEST-ID)%" response_code: "%RESPONSE_CODE%" response_flags: "%RESPONSE_FLAGS%" bytes_received: "%BYTES_RECEIVED%" bytes_sent: "%BYTES_SENT%" duration_ms: "%DURATION%" upstream_cluster: "%UPSTREAM_CLUSTER%" upstream_host: "%UPSTREAM_HOST%" upstream_service_time_ms: "%RESP(X-ENVOY-UPSTREAM-SERVICE-TIME)%" route_name: "%ROUTE_NAME%" 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":"TUdVeU1XRTNaRGhqTVRRNVpEZzFNV1ZpT1dVME1HTTNPVEUyTldWa1lUQmxPVEU1WldSa1pEVTFZall6T0dKak9XUmlOek0wTlRjNE5ESXlNamxrWlE","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: prefix: /api/v1 headers: - name: ":method" exact_match: OPTIONS - match: path: /api/v1/auth/login - match: path: /api/v1/auth/register - match: path: /api/v1/auth/forgot-password - match: path: /api/v1/auth/reset-password - match: path: /api/v1/auth/forgot-password/send - match: path: /api/v1/email/verification-code/send - match: prefix: /api/v1/games headers: - name: ":method" exact_match: GET - match: prefix: /api/v1/players headers: - name: ":method" exact_match: GET - match: prefix: /api/v1/services headers: - name: ":method" exact_match: GET - match: path: /api/v1/shops headers: - name: ":method" exact_match: GET - match: safe_regex: google_re2: {} regex: "^/api/v1/shops/[0-9]+$" headers: - name: ":method" exact_match: GET - match: safe_regex: google_re2: {} regex: "^/api/v1/users/[0-9]+$" headers: - name: ":method" exact_match: GET - match: safe_regex: google_re2: {} regex: "^/api/v1/users/[0-9]+/posts$" headers: - name: ":method" exact_match: GET - match: safe_regex: google_re2: {} regex: "^/api/v1/users/[0-9]+/shop$" headers: - name: ":method" exact_match: GET - match: prefix: /api/v1/posts headers: - name: ":method" exact_match: GET - match: prefix: /api/v1 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.ratelimit typed_config: "@type": type.googleapis.com/envoy.extensions.filters.http.ratelimit.v3.RateLimit domain: api failure_mode_deny: false rate_limited_as_resource_exhausted: true enable_x_ratelimit_headers: DRAFT_VERSION_03 rate_limit_service: transport_api_version: V3 grpc_service: envoy_grpc: cluster_name: ratelimit_cluster timeout: 0.2s - 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: users-api 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 port_value: 8888 - name: player_api_cluster connect_timeout: 2s type: STRICT_DNS lb_policy: ROUND_ROBIN load_assignment: cluster_name: player_api_cluster endpoints: - lb_endpoints: - endpoint: address: socket_address: address: player-api port_value: 8888 - name: game_api_cluster connect_timeout: 2s type: STRICT_DNS lb_policy: ROUND_ROBIN load_assignment: cluster_name: game_api_cluster endpoints: - lb_endpoints: - endpoint: address: socket_address: address: game-api port_value: 8888 - name: shop_api_cluster connect_timeout: 2s type: STRICT_DNS lb_policy: ROUND_ROBIN load_assignment: cluster_name: shop_api_cluster endpoints: - lb_endpoints: - endpoint: address: socket_address: address: shop-api port_value: 8888 - name: order_api_cluster connect_timeout: 2s type: STRICT_DNS lb_policy: ROUND_ROBIN load_assignment: cluster_name: order_api_cluster endpoints: - lb_endpoints: - endpoint: address: socket_address: address: order-api port_value: 8888 - name: wallet_api_cluster connect_timeout: 2s type: STRICT_DNS lb_policy: ROUND_ROBIN load_assignment: cluster_name: wallet_api_cluster endpoints: - lb_endpoints: - endpoint: address: socket_address: address: wallet-api port_value: 8888 - name: community_api_cluster connect_timeout: 2s type: STRICT_DNS lb_policy: ROUND_ROBIN load_assignment: cluster_name: community_api_cluster endpoints: - lb_endpoints: - endpoint: address: socket_address: address: community-api port_value: 8888 - name: objectstory_api_cluster connect_timeout: 2s type: STRICT_DNS lb_policy: ROUND_ROBIN load_assignment: cluster_name: objectstory_api_cluster endpoints: - lb_endpoints: - endpoint: address: socket_address: address: objectstory-api 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 port_value: 9002 - name: ratelimit_cluster connect_timeout: 0.25s type: STRICT_DNS lb_policy: ROUND_ROBIN http2_protocol_options: {} load_assignment: cluster_name: ratelimit_cluster endpoints: - lb_endpoints: - endpoint: address: socket_address: address: ratelimit port_value: 8081 admin: access_log_path: /tmp/admin.log address: socket_address: address: 0.0.0.0 port_value: 9901