From e65c2388da8618c4ebfa9373531b249939c41c71 Mon Sep 17 00:00:00 2001 From: Abhishek Choudhary Date: Tue, 5 May 2026 21:44:24 +0800 Subject: [PATCH 1/3] feat(cas-auth): sign request URI cookie and tighten cookie attributes The CAS_REQUEST_URI cookie used by the plugin to remember the pre-login URL is now signed with HMAC-SHA256 and verified on the IdP callback using a constant-time comparison. The recovered redirect target is also validated as a same-origin path before being applied; on mismatch the plugin falls back to /. Cookie attributes are tightened to include Secure; SameSite=Lax. Adds a required cookie_secret field (minLength 32) to the plugin schema. The same value must be configured on every APISIX node. --- apisix/plugins/cas-auth.lua | 65 ++++++++++++++++++++++++++++-- docs/en/latest/plugins/cas-auth.md | 4 +- t/lib/keycloak_cas.lua | 1 + 3 files changed, 65 insertions(+), 5 deletions(-) diff --git a/apisix/plugins/cas-auth.lua b/apisix/plugins/cas-auth.lua index d949636f5c75..4208084d3a58 100644 --- a/apisix/plugins/cas-auth.lua +++ b/apisix/plugins/cas-auth.lua @@ -16,12 +16,16 @@ ---- local core = require("apisix.core") local http = require("resty.http") +local hmac = require("resty.openssl.hmac") +local bit = require("bit") local ngx = ngx local ngx_re_match = ngx.re.match +local ngx_encode_base64 = ngx.encode_base64 +local ngx_decode_base64 = ngx.decode_base64 local CAS_REQUEST_URI = "CAS_REQUEST_URI" local COOKIE_NAME = "CAS_SESSION" -local COOKIE_PARAMS = "; Path=/; HttpOnly" +local COOKIE_PARAMS = "; Path=/; HttpOnly; Secure; SameSite=Lax" local SESSION_LIFETIME = 3600 local STORE_NAME = "cas_sessions" @@ -35,9 +39,10 @@ local schema = { idp_uri = {type = "string"}, cas_callback_uri = {type = "string"}, logout_uri = {type = "string"}, + cookie_secret = {type = "string", minLength = 32}, }, required = { - "idp_uri", "cas_callback_uri", "logout_uri" + "idp_uri", "cas_callback_uri", "logout_uri", "cookie_secret" } } @@ -67,12 +72,59 @@ local function set_our_cookie(name, val) core.response.add_header("Set-Cookie", name .. "=" .. val .. COOKIE_PARAMS) end +local function compute_hmac(secret, val) + local h, err = hmac.new(secret, "sha256") + if not h then return nil, err end + local ok, err2 = h:update(val) + if not ok then return nil, err2 end + return h:final() +end + +local function eq_const_time(a, b) + if #a ~= #b then return false end + local diff = 0 + for i = 1, #a do + diff = bit.bor(diff, bit.bxor(a:byte(i), b:byte(i))) + end + return diff == 0 +end + +local function sign_value(secret, val) + local sig, err = compute_hmac(secret, val) + if not sig then + core.log.error("cas-auth: hmac sign failed: ", err) + return nil + end + return ngx_encode_base64(val, true) .. "." .. ngx_encode_base64(sig, true) +end + +local function verify_value(secret, signed) + if not signed then return nil end + local dot = signed:find(".", 1, true) + if not dot then return nil end + local val = ngx_decode_base64(signed:sub(1, dot - 1)) + local sig = ngx_decode_base64(signed:sub(dot + 1)) + if not val or not sig then return nil end + local expected = compute_hmac(secret, val) + if not expected or not eq_const_time(sig, expected) then return nil end + return val +end + +local function is_safe_redirect(uri) + if not uri or uri == "" then return false end + if uri:sub(1, 1) ~= "/" then return false end + if uri:sub(1, 2) == "//" then return false end + if uri:find("\\", 1, true) then return false end + if uri:find("[\r\n]") then return false end + return true +end + local function first_access(conf, ctx) local login_uri = conf.idp_uri .. "/login?" .. ngx.encode_args({ service = uri_without_ticket(conf, ctx) }) core.log.info("first access: ", login_uri, ", cookie: ", ctx.var.http_cookie, ", request_uri: ", ctx.var.request_uri) - set_our_cookie(CAS_REQUEST_URI, ctx.var.request_uri) + set_our_cookie(CAS_REQUEST_URI, sign_value(conf.cookie_secret, ctx.var.request_uri)) core.response.set_header("Location", login_uri) return ngx.HTTP_MOVED_TEMPORARILY end @@ -136,8 +188,13 @@ end local function validate_with_cas(conf, ctx, ticket) local user = validate(conf, ctx, ticket) if user and set_store_and_cookie(ticket, user) then - local request_uri = ctx.var["cookie_" .. CAS_REQUEST_URI] + local request_uri = verify_value(conf.cookie_secret, + ctx.var["cookie_" .. CAS_REQUEST_URI]) set_our_cookie(CAS_REQUEST_URI, "deleted; Max-Age=0") + if not is_safe_redirect(request_uri) then + core.log.warn("cas-auth: rejected unsafe redirect target, falling back to /") + request_uri = "/" + end core.log.info("ticket: ", ticket, ", cookie: ", ctx.var.http_cookie, ", request_uri: ", request_uri, ", user=", user) core.response.set_header("Location", request_uri) diff --git a/docs/en/latest/plugins/cas-auth.md b/docs/en/latest/plugins/cas-auth.md index 37d23b068a30..b5667d8afe86 100644 --- a/docs/en/latest/plugins/cas-auth.md +++ b/docs/en/latest/plugins/cas-auth.md @@ -40,6 +40,7 @@ to do authentication, from the SP (service provider) perspective. | `idp_uri` | string | True | URI of IdP. | | `cas_callback_uri` | string | True | redirect uri used to callback the SP from IdP after login or logout. | | `logout_uri` | string | True | logout uri to trigger logout. | +| `cookie_secret` | string | True | secret (32+ characters) used to sign the request URI cookie issued during the CAS login flow. The same value must be configured on every APISIX node. Generate with e.g. `openssl rand -base64 48`. | ## Enable Plugin @@ -64,7 +65,8 @@ curl http://127.0.0.1:9180/apisix/admin/routes/cas1 -H "X-API-KEY: $admin_key" - "cas-auth": { "idp_uri": "http://127.0.0.1:8080/realms/test/protocol/cas", "cas_callback_uri": "/anything/cas_callback", - "logout_uri": "/anything/logout" + "logout_uri": "/anything/logout", + "cookie_secret": "please-replace-with-a-32+-char-random-secret" } }, "upstream": { diff --git a/t/lib/keycloak_cas.lua b/t/lib/keycloak_cas.lua index 7e578014ce8f..b7f864f06b68 100644 --- a/t/lib/keycloak_cas.lua +++ b/t/lib/keycloak_cas.lua @@ -22,6 +22,7 @@ local default_opts = { idp_uri = "http://127.0.0.1:8080/realms/test/protocol/cas", cas_callback_uri = "/cas_callback", logout_uri = "/logout", + cookie_secret = "0123456789abcdef0123456789abcdef", } function _M.get_default_opts() From 654c7c49a29aa108c7b00fe3aa97393be592db66 Mon Sep 17 00:00:00 2001 From: Abhishek Choudhary Date: Thu, 7 May 2026 19:09:34 +0800 Subject: [PATCH 2/3] feat(cas-auth): nested cookie config, hardening from review - Move cookie options under a nested cookie object: cookie.secret, cookie.secure (default true), cookie.samesite (default Lax, enum Lax|None). Strict is intentionally omitted because it breaks the IdP->SP cross-site redirect. - Mark cookie.secret as encrypt_fields so it is encrypted at rest in etcd, matching how other plugins protect plugin secrets. - Guard first_access against sign_value returning nil so a transient HMAC failure cannot crash the request with a Lua concatenation error; fall through and let the safe-redirect fallback take over. - Capture and log the error from compute_hmac in verify_value rather than dropping it. - Build cookie attributes per-config so deployments that legitimately run over HTTP can disable Secure without giving up SameSite. - Strip session identifiers, ticket values, and full request URIs from info-level logs; demote raw SLO LogoutRequest body to debug. - Update t/lib/keycloak_cas.lua and t/plugin/security-warning.t for the new schema; add unit-style tests for the schema, the safe redirect predicate, and the HMAC sign/verify roundtrip with tampering, wrong-secret, and malformed-input cases. --- apisix/plugins/cas-auth.lua | 86 ++++++++++++------ docs/en/latest/plugins/cas-auth.md | 19 ++-- t/lib/keycloak_cas.lua | 5 +- t/plugin/cas-auth.t | 138 +++++++++++++++++++++++++++++ t/plugin/security-warning.t | 10 ++- 5 files changed, 220 insertions(+), 38 deletions(-) diff --git a/apisix/plugins/cas-auth.lua b/apisix/plugins/cas-auth.lua index 4208084d3a58..93a8c310e36e 100644 --- a/apisix/plugins/cas-auth.lua +++ b/apisix/plugins/cas-auth.lua @@ -25,7 +25,6 @@ local ngx_decode_base64 = ngx.decode_base64 local CAS_REQUEST_URI = "CAS_REQUEST_URI" local COOKIE_NAME = "CAS_SESSION" -local COOKIE_PARAMS = "; Path=/; HttpOnly; Secure; SameSite=Lax" local SESSION_LIFETIME = 3600 local STORE_NAME = "cas_sessions" @@ -39,10 +38,18 @@ local schema = { idp_uri = {type = "string"}, cas_callback_uri = {type = "string"}, logout_uri = {type = "string"}, - cookie_secret = {type = "string", minLength = 32}, + cookie = { + type = "object", + properties = { + secret = {type = "string", minLength = 32}, + secure = {type = "boolean", default = true}, + samesite = {type = "string", enum = {"Lax", "None"}, default = "Lax"}, + }, + required = {"secret"}, + }, }, required = { - "idp_uri", "cas_callback_uri", "logout_uri", "cookie_secret" + "idp_uri", "cas_callback_uri", "logout_uri", "cookie" } } @@ -50,9 +57,19 @@ local _M = { version = 0.1, priority = 2597, name = plugin_name, - schema = schema + schema = schema, + encrypt_fields = {"cookie.secret"}, } +local function cookie_attrs(conf) + local attrs = "; Path=/; HttpOnly" + if conf.cookie.secure then + attrs = attrs .. "; Secure" + end + attrs = attrs .. "; SameSite=" .. conf.cookie.samesite + return attrs +end + function _M.check_schema(conf) local check = {"idp_uri"} core.utils.check_https(check, conf, plugin_name) @@ -68,8 +85,8 @@ local function get_session_id(ctx) return ctx.var["cookie_" .. COOKIE_NAME] end -local function set_our_cookie(name, val) - core.response.add_header("Set-Cookie", name .. "=" .. val .. COOKIE_PARAMS) +local function set_our_cookie(conf, name, val) + core.response.add_header("Set-Cookie", name .. "=" .. val .. cookie_attrs(conf)) end local function compute_hmac(secret, val) @@ -105,8 +122,12 @@ local function verify_value(secret, signed) local val = ngx_decode_base64(signed:sub(1, dot - 1)) local sig = ngx_decode_base64(signed:sub(dot + 1)) if not val or not sig then return nil end - local expected = compute_hmac(secret, val) - if not expected or not eq_const_time(sig, expected) then return nil end + local expected, err = compute_hmac(secret, val) + if not expected then + core.log.error("cas-auth: hmac verify failed: ", err) + return nil + end + if not eq_const_time(sig, expected) then return nil end return val end @@ -119,37 +140,46 @@ local function is_safe_redirect(uri) return true end +-- Exposed for unit tests; not part of the plugin's public API. +_M._test_helpers = { + sign_value = sign_value, + verify_value = verify_value, + is_safe_redirect = is_safe_redirect, +} + local function first_access(conf, ctx) local login_uri = conf.idp_uri .. "/login?" .. ngx.encode_args({ service = uri_without_ticket(conf, ctx) }) - core.log.info("first access: ", login_uri, - ", cookie: ", ctx.var.http_cookie, ", request_uri: ", ctx.var.request_uri) - set_our_cookie(CAS_REQUEST_URI, sign_value(conf.cookie_secret, ctx.var.request_uri)) + core.log.info("cas-auth: redirecting unauthenticated request to IdP") + local signed = sign_value(conf.cookie.secret, ctx.var.request_uri) + if signed then + set_our_cookie(conf, CAS_REQUEST_URI, signed) + end core.response.set_header("Location", login_uri) return ngx.HTTP_MOVED_TEMPORARILY end local function with_session_id(conf, ctx, session_id) -- does the cookie exist in our store? - local user = store:get(session_id); - core.log.info("ticket=", session_id, ", user=", user) + local user = store:get(session_id) if user == nil then - set_our_cookie(COOKIE_NAME, "deleted; Max-Age=0") + set_our_cookie(conf, COOKIE_NAME, "deleted; Max-Age=0") return first_access(conf, ctx) else -- refresh the TTL store:set(session_id, user, SESSION_LIFETIME) + core.log.info("cas-auth: session refreshed for user=", user) end end -local function set_store_and_cookie(session_id, user) +local function set_store_and_cookie(conf, session_id, user) -- place cookie into cookie store local success, err, forcible = store:add(session_id, user, SESSION_LIFETIME) if success then if forcible then core.log.info("CAS cookie store is out of memory") end - set_our_cookie(COOKIE_NAME, session_id) + set_our_cookie(conf, COOKIE_NAME, session_id) else if err == "no memory" then core.log.emerg("CAS cookie store is out of memory") @@ -171,12 +201,12 @@ local function validate(conf, ctx, ticket) if res and res.status == ngx.HTTP_OK and res.body ~= nil then if core.string.find(res.body, "") then - local m = ngx_re_match(res.body, "(.*?)", "jo"); + local m = ngx_re_match(res.body, "(.*?)", "jo") if m then return m[1] end else - core.log.info("CAS serviceValidate failed: ", res.body) + core.log.info("CAS serviceValidate did not return authenticationSuccess") end else core.log.error("validate ticket failed: status=", (res and res.status), @@ -187,16 +217,15 @@ end local function validate_with_cas(conf, ctx, ticket) local user = validate(conf, ctx, ticket) - if user and set_store_and_cookie(ticket, user) then - local request_uri = verify_value(conf.cookie_secret, + if user and set_store_and_cookie(conf, ticket, user) then + local request_uri = verify_value(conf.cookie.secret, ctx.var["cookie_" .. CAS_REQUEST_URI]) - set_our_cookie(CAS_REQUEST_URI, "deleted; Max-Age=0") + set_our_cookie(conf, CAS_REQUEST_URI, "deleted; Max-Age=0") if not is_safe_redirect(request_uri) then core.log.warn("cas-auth: rejected unsafe redirect target, falling back to /") request_uri = "/" end - core.log.info("ticket: ", ticket, - ", cookie: ", ctx.var.http_cookie, ", request_uri: ", request_uri, ", user=", user) + core.log.info("cas-auth: validation succeeded for user=", user) core.response.set_header("Location", request_uri) return ngx.HTTP_MOVED_TEMPORARILY else @@ -210,9 +239,9 @@ local function logout(conf, ctx) return ngx.HTTP_UNAUTHORIZED end - core.log.info("logout: ticket=", session_id, ", cookie=", ctx.var.http_cookie) + core.log.info("cas-auth: logout invoked") store:delete(session_id) - set_our_cookie(COOKIE_NAME, "deleted; Max-Age=0") + set_our_cookie(conf, COOKIE_NAME, "deleted; Max-Age=0") core.response.set_header("Location", conf.idp_uri .. "/logout") return ngx.HTTP_MOVED_TEMPORARILY @@ -233,12 +262,13 @@ function _M.access(conf, ctx) return ngx.HTTP_BAD_REQUEST, {message = "invalid logout request from IdP, no ticket"} end - core.log.info("Back-channel logout (SLO) from IdP: LogoutRequest: ", data) + core.log.debug("cas-auth: SLO LogoutRequest body=", data) + core.log.info("cas-auth: SLO request received from IdP") local session_id = ticket - local user = store:get(session_id); + local user = store:get(session_id) if user then store:delete(session_id) - core.log.info("SLO: user=", user, ", tocket=", ticket) + core.log.info("cas-auth: SLO session deleted for user=", user) end else local session_id = get_session_id(ctx) diff --git a/docs/en/latest/plugins/cas-auth.md b/docs/en/latest/plugins/cas-auth.md index b5667d8afe86..0456b7fc3cb5 100644 --- a/docs/en/latest/plugins/cas-auth.md +++ b/docs/en/latest/plugins/cas-auth.md @@ -35,12 +35,15 @@ to do authentication, from the SP (service provider) perspective. ## Attributes -| Name | Type | Required | Description | -| ----------- | ----------- | ----------- | ----------- | -| `idp_uri` | string | True | URI of IdP. | -| `cas_callback_uri` | string | True | redirect uri used to callback the SP from IdP after login or logout. | -| `logout_uri` | string | True | logout uri to trigger logout. | -| `cookie_secret` | string | True | secret (32+ characters) used to sign the request URI cookie issued during the CAS login flow. The same value must be configured on every APISIX node. Generate with e.g. `openssl rand -base64 48`. | +| Name | Type | Required | Default | Description | +| ----------- | ----------- | ----------- | ----------- | ----------- | +| `idp_uri` | string | True | | URI of IdP. | +| `cas_callback_uri` | string | True | | redirect uri used to callback the SP from IdP after login or logout. | +| `logout_uri` | string | True | | logout uri to trigger logout. | +| `cookie` | object | True | | configuration for the cookies the plugin issues during the CAS login flow. | +| `cookie.secret` | string | True | | secret (32+ characters) used to sign the request URI cookie. The same value must be configured on every APISIX node. Generate with e.g. `openssl rand -base64 48`. | +| `cookie.secure` | boolean | False | `true` | whether to set the `Secure` attribute on the issued cookies. Set to `false` only for deployments where the protected route is not served over HTTPS (e.g. internal-only or development environments). | +| `cookie.samesite` | string | False | `"Lax"` | value for the `SameSite` cookie attribute. Allowed values are `"Lax"` and `"None"`; `"Strict"` is intentionally not supported because it breaks the IdP→SP redirect when the IdP is on a different site. | ## Enable Plugin @@ -66,7 +69,9 @@ curl http://127.0.0.1:9180/apisix/admin/routes/cas1 -H "X-API-KEY: $admin_key" - "idp_uri": "http://127.0.0.1:8080/realms/test/protocol/cas", "cas_callback_uri": "/anything/cas_callback", "logout_uri": "/anything/logout", - "cookie_secret": "please-replace-with-a-32+-char-random-secret" + "cookie": { + "secret": "please-replace-with-a-32+-char-random-secret" + } } }, "upstream": { diff --git a/t/lib/keycloak_cas.lua b/t/lib/keycloak_cas.lua index b7f864f06b68..a24be01c336d 100644 --- a/t/lib/keycloak_cas.lua +++ b/t/lib/keycloak_cas.lua @@ -22,7 +22,10 @@ local default_opts = { idp_uri = "http://127.0.0.1:8080/realms/test/protocol/cas", cas_callback_uri = "/cas_callback", logout_uri = "/logout", - cookie_secret = "0123456789abcdef0123456789abcdef", + cookie = { + secret = "0123456789abcdef0123456789abcdef", + secure = false, + }, } function _M.get_default_opts() diff --git a/t/plugin/cas-auth.t b/t/plugin/cas-auth.t index 4a2bfe7e874b..9da02e5fc5ce 100644 --- a/t/plugin/cas-auth.t +++ b/t/plugin/cas-auth.t @@ -221,3 +221,141 @@ passed assert(res.status == 200) } } + + + +=== TEST 5: schema rejects missing cookie.secret +--- config + location /t { + content_by_lua_block { + local plugin = require("apisix.plugins.cas-auth") + local ok, err = plugin.check_schema({ + idp_uri = "http://127.0.0.1:8080", + cas_callback_uri = "/cas_callback", + logout_uri = "/logout", + cookie = {}, + }) + ngx.say(ok and "passed" or err) + } + } +--- response_body_like +.*property "secret" is required.* + + + +=== TEST 6: schema rejects cookie.secret shorter than 32 chars +--- config + location /t { + content_by_lua_block { + local plugin = require("apisix.plugins.cas-auth") + local ok, err = plugin.check_schema({ + idp_uri = "http://127.0.0.1:8080", + cas_callback_uri = "/cas_callback", + logout_uri = "/logout", + cookie = { secret = "tooshort" }, + }) + ngx.say(ok and "passed" or err) + } + } +--- response_body_like +.*string too short.* + + + +=== TEST 7: schema rejects cookie.samesite=Strict +--- config + location /t { + content_by_lua_block { + local plugin = require("apisix.plugins.cas-auth") + local ok, err = plugin.check_schema({ + idp_uri = "http://127.0.0.1:8080", + cas_callback_uri = "/cas_callback", + logout_uri = "/logout", + cookie = { + secret = "0123456789abcdef0123456789abcdef", + samesite = "Strict", + }, + }) + ngx.say(ok and "passed" or err) + } + } +--- response_body_like +.*samesite.* + + + +=== TEST 8: is_safe_redirect rejects external and protocol-relative URLs +--- config + location /t { + content_by_lua_block { + local plugin = require("apisix.plugins.cas-auth") + local h = plugin._test_helpers + local cases = { + {"/foo", true}, + {"/foo?bar=baz", true}, + {"https://evil.com/x", false}, + {"//evil.com/x", false}, + {"\\\\evil.com", false}, + {"/foo\r\nLocation: x", false}, + {"", false}, + {nil, false}, + } + for _, c in ipairs(cases) do + local got = h.is_safe_redirect(c[1]) + if got ~= c[2] then + ngx.say("FAIL ", tostring(c[1]), " expected ", tostring(c[2]), + " got ", tostring(got)) + return + end + end + ngx.say("passed") + } + } +--- response_body +passed + + + +=== TEST 9: sign and verify roundtrip + tamper detection +--- config + location /t { + content_by_lua_block { + local plugin = require("apisix.plugins.cas-auth") + local h = plugin._test_helpers + local secret = "0123456789abcdef0123456789abcdef" + local signed = h.sign_value(secret, "/foo?bar=baz") + assert(signed, "sign_value returned nil") + + local got = h.verify_value(secret, signed) + if got ~= "/foo?bar=baz" then + ngx.say("FAIL roundtrip got=", tostring(got)) + return + end + + -- flip last char of the signature segment + local tampered = signed:sub(1, -2) .. + (signed:sub(-1) == "A" and "B" or "A") + if h.verify_value(secret, tampered) ~= nil then + ngx.say("FAIL tampered signature accepted") + return + end + + -- a different secret must not validate + if h.verify_value("XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", signed) ~= nil then + ngx.say("FAIL wrong secret accepted") + return + end + + -- nil and malformed inputs + if h.verify_value(secret, nil) ~= nil + or h.verify_value(secret, "no-dot-here") ~= nil + or h.verify_value(secret, "abc.def") ~= nil then + ngx.say("FAIL malformed cookie accepted") + return + end + + ngx.say("passed") + } + } +--- response_body +passed diff --git a/t/plugin/security-warning.t b/t/plugin/security-warning.t index f2d2e3ea6375..0dca62e7bbd4 100644 --- a/t/plugin/security-warning.t +++ b/t/plugin/security-warning.t @@ -164,7 +164,10 @@ Using authz-keycloak access_denied_redirect_uri with no TLS is a security risk local ok, err = plugin.check_schema({ idp_uri = "http://a.com", cas_callback_uri = "/a/b", - logout_uri = "/c/d" + logout_uri = "/c/d", + cookie = { + secret = "0123456789abcdef0123456789abcdef", + }, }) if not ok then @@ -189,7 +192,10 @@ risk local ok, err = plugin.check_schema({ idp_uri = "https://a.com", cas_callback_uri = "/a/b", - logout_uri = "/c/d" + logout_uri = "/c/d", + cookie = { + secret = "0123456789abcdef0123456789abcdef", + }, }) if not ok then ngx.say(err) From 5a1ed4cb7561a239a2351bda181c7acce3d2abaf Mon Sep 17 00:00:00 2001 From: Abhishek Choudhary Date: Fri, 8 May 2026 11:08:30 +0800 Subject: [PATCH 3/3] fix(cas-auth): place encrypt_fields on schema, drop SLO body log - Move encrypt_fields = {"cookie.secret"} from the plugin module table into the schema table. The framework's encryption pipeline reads schema.encrypt_fields (apisix/plugin.lua); declaring it on _M was silently a no-op and meant cookie.secret was being stored in plaintext at rest in etcd despite the previous claim. Other plugins (basic-auth, openid-connect, etc.) place it on the schema table for the same reason. - Drop the debug-level log of the raw SLO LogoutRequest body. Even at debug level, the body contains the SAML SessionIndex (the ticket value the plugin reuses as a session id), which is authentication material that should not be written to logs at any level. The "SLO request received from IdP" info log already captures the operational signal; raw-body inspection can be done via packet capture if ever needed. --- apisix/plugins/cas-auth.lua | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apisix/plugins/cas-auth.lua b/apisix/plugins/cas-auth.lua index 93a8c310e36e..d6462904052d 100644 --- a/apisix/plugins/cas-auth.lua +++ b/apisix/plugins/cas-auth.lua @@ -48,6 +48,7 @@ local schema = { required = {"secret"}, }, }, + encrypt_fields = {"cookie.secret"}, required = { "idp_uri", "cas_callback_uri", "logout_uri", "cookie" } @@ -58,7 +59,6 @@ local _M = { priority = 2597, name = plugin_name, schema = schema, - encrypt_fields = {"cookie.secret"}, } local function cookie_attrs(conf) @@ -262,7 +262,6 @@ function _M.access(conf, ctx) return ngx.HTTP_BAD_REQUEST, {message = "invalid logout request from IdP, no ticket"} end - core.log.debug("cas-auth: SLO LogoutRequest body=", data) core.log.info("cas-auth: SLO request received from IdP") local session_id = ticket local user = store:get(session_id)