feat: add authz-adapter service and Envoy ext_authz integration
- Implemented authz-adapter deployment and service for Envoy gRPC authorization. - Created PowerShell script to generate JWK for JWT authentication. - Documented the integration of ext_authz with user-rpc.ValidateToken in ENVOY_EXT_AUTHZ_ADAPTER.md. - Added comprehensive Envoy Gateway configuration guide with JWT authentication and access control in ENVOY_GATEWAY_GUIDE.md.
This commit is contained in:
+138
-34
@@ -21,6 +21,16 @@ data:
|
||||
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:
|
||||
@@ -33,31 +43,68 @@ data:
|
||||
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:
|
||||
prefix: /api/email
|
||||
path: /api/users/login
|
||||
route:
|
||||
cluster: email_api_cluster
|
||||
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_COOKIE = "csrf_token"
|
||||
local GUARD_COOKIE = "csrf_guard"
|
||||
local TOKEN_HEADER = "x-csrf-token"
|
||||
local GUARD_HEADER = "x-csrf-guard"
|
||||
local TOKEN_HEADER = "xsrf-token"
|
||||
local TOKEN_COOKIE = "__Host-XSRF-TOKEN"
|
||||
local GUARD_COOKIE = "__Host-XSRF-GUARD"
|
||||
|
||||
local seeded = false
|
||||
|
||||
@@ -100,42 +147,41 @@ data:
|
||||
|
||||
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]
|
||||
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", 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 == "")
|
||||
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 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)
|
||||
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", csrf_token_cookie)
|
||||
request_handle:streamInfo():dynamicMetadata():set("csrf", "token_value", 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)
|
||||
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", csrf_guard_cookie)
|
||||
request_handle:streamInfo():dynamicMetadata():set("csrf", "guard_value", 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)
|
||||
local token_header = headers:get(TOKEN_HEADER)
|
||||
|
||||
if csrf_token_header == nil or csrf_guard_header == nil then
|
||||
if token_header == nil or token_header == "" then
|
||||
request_handle:respond(
|
||||
{[":status"] = "403", ["content-type"] = "application/json"},
|
||||
'{"code":403,"message":"missing csrf headers"}'
|
||||
'{"code":403,"message":"missing XSRF-TOKEN header"}'
|
||||
)
|
||||
return
|
||||
end
|
||||
|
||||
if csrf_token_cookie == nil or csrf_guard_cookie == nil then
|
||||
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"}'
|
||||
@@ -143,10 +189,10 @@ data:
|
||||
return
|
||||
end
|
||||
|
||||
if csrf_token_header ~= csrf_token_cookie or csrf_guard_header ~= csrf_guard_cookie then
|
||||
if token_header ~= token_cookie then
|
||||
request_handle:respond(
|
||||
{[":status"] = "403", ["content-type"] = "application/json"},
|
||||
'{"code":403,"message":"csrf token mismatch"}'
|
||||
'{"code":403,"message":"xsrf token mismatch"}'
|
||||
)
|
||||
return
|
||||
end
|
||||
@@ -164,17 +210,65 @@ data:
|
||||
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"
|
||||
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=/; SameSite=Strict"
|
||||
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
|
||||
@@ -193,6 +287,7 @@ data:
|
||||
socket_address:
|
||||
address: user-api-svc.juwan.svc.cluster.local
|
||||
port_value: 8888
|
||||
|
||||
- name: email_api_cluster
|
||||
connect_timeout: 2s
|
||||
type: STRICT_DNS
|
||||
@@ -207,6 +302,21 @@ data:
|
||||
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:
|
||||
@@ -233,6 +343,7 @@ spec:
|
||||
labels:
|
||||
app: envoy-gateway
|
||||
spec:
|
||||
serviceAccountName: envoy-gateway
|
||||
containers:
|
||||
- name: envoy
|
||||
image: envoyproxy/envoy:v1.31-latest
|
||||
@@ -260,13 +371,6 @@ spec:
|
||||
port: 8080
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 10
|
||||
resources:
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 128Mi
|
||||
limits:
|
||||
cpu: 500m
|
||||
memory: 512Mi
|
||||
volumeMounts:
|
||||
- name: envoy-config
|
||||
mountPath: /etc/envoy
|
||||
|
||||
Reference in New Issue
Block a user