Add SameSite=None support and conditionally omit SameSite=None for backwards compatibility
This commit is contained in:
parent
5d05e19cc3
commit
1b523390bb
11 changed files with 249 additions and 6 deletions
|
|
@ -2,6 +2,7 @@ local balancer_resty = require("balancer.resty")
|
|||
local ck = require("resty.cookie")
|
||||
local ngx_balancer = require("ngx.balancer")
|
||||
local split = require("util.split")
|
||||
local same_site = require("util.same_site")
|
||||
|
||||
local _M = balancer_resty:new()
|
||||
local DEFAULT_COOKIE_NAME = "route"
|
||||
|
|
@ -43,6 +44,20 @@ function _M.set_cookie(self, value)
|
|||
cookie_path = ngx.var.location_path
|
||||
end
|
||||
|
||||
local cookie_samesite = self.cookie_session_affinity.samesite
|
||||
if cookie_samesite then
|
||||
local cookie_conditional_samesite_none = self.cookie_session_affinity.conditional_samesite_none
|
||||
if cookie_conditional_samesite_none
|
||||
and cookie_samesite == "None"
|
||||
and not same_site.same_site_none_compatible(ngx.var.http_user_agent) then
|
||||
cookie_samesite = nil
|
||||
end
|
||||
end
|
||||
|
||||
if cookie_samesite then
|
||||
cookie_path = cookie_path .. "; SameSite=" .. cookie_samesite
|
||||
end
|
||||
|
||||
local cookie_data = {
|
||||
key = self:cookie_name(),
|
||||
value = value,
|
||||
|
|
|
|||
|
|
@ -390,4 +390,70 @@ describe("Sticky", function()
|
|||
it("sets a cookie on the client", function() test(sticky_balanced) end)
|
||||
it("sets a cookie on the client", function() test(sticky_persistent) end)
|
||||
end)
|
||||
|
||||
describe("SameSite settings", function()
|
||||
local mocked_cookie_new = cookie.new
|
||||
|
||||
before_each(function()
|
||||
package.loaded["balancer.sticky_balanced"] = nil
|
||||
package.loaded["balancer.sticky_persistent"] = nil
|
||||
sticky_balanced = require("balancer.sticky_balanced")
|
||||
sticky_persistent = require("balancer.sticky_persistent")
|
||||
end)
|
||||
|
||||
after_each(function()
|
||||
cookie.new = mocked_cookie_new
|
||||
end)
|
||||
|
||||
local function test_set_cookie(sticky, samesite, conditional_samesite_none, expected_path)
|
||||
local s = {}
|
||||
cookie.new = function(self)
|
||||
local cookie_instance = {
|
||||
set = function(self, payload)
|
||||
assert.equal(payload.key, test_backend.sessionAffinityConfig.cookieSessionAffinity.name)
|
||||
assert.equal(payload.path, expected_path)
|
||||
assert.equal(payload.domain, nil)
|
||||
assert.equal(payload.httponly, true)
|
||||
assert.equal(payload.secure, false)
|
||||
return true, nil
|
||||
end,
|
||||
get = function(k) return false end,
|
||||
}
|
||||
s = spy.on(cookie_instance, "set")
|
||||
return cookie_instance, false
|
||||
end
|
||||
local b = get_test_backend()
|
||||
b.sessionAffinityConfig.cookieSessionAffinity.locations = {}
|
||||
b.sessionAffinityConfig.cookieSessionAffinity.locations["test.com"] = {"/"}
|
||||
b.sessionAffinityConfig.cookieSessionAffinity.samesite = samesite
|
||||
b.sessionAffinityConfig.cookieSessionAffinity.conditional_samesite_none = conditional_samesite_none
|
||||
local sticky_balancer_instance = sticky:new(b)
|
||||
assert.has_no.errors(function() sticky_balancer_instance:balance() end)
|
||||
assert.spy(s).was_called()
|
||||
end
|
||||
|
||||
it("returns a cookie with SameSite=Strict when user specifies samesite strict", function()
|
||||
test_set_cookie(sticky_balanced, "Strict", false, "/; SameSite=Strict")
|
||||
end)
|
||||
it("returns a cookie with SameSite=Strict when user specifies samesite strict and conditional samesite none", function()
|
||||
test_set_cookie(sticky_balanced, "Strict", true, "/; SameSite=Strict")
|
||||
end)
|
||||
it("returns a cookie with SameSite=Lax when user specifies samesite lax", function()
|
||||
test_set_cookie(sticky_balanced, "Lax", false, "/; SameSite=Lax")
|
||||
end)
|
||||
it("returns a cookie with SameSite=Lax when user specifies samesite lax and conditional samesite none", function()
|
||||
test_set_cookie(sticky_balanced, "Lax", true, "/; SameSite=Lax")
|
||||
end)
|
||||
it("returns a cookie with SameSite=None when user specifies samesite None", function()
|
||||
test_set_cookie(sticky_balanced, "None", false, "/; SameSite=None")
|
||||
end)
|
||||
it("returns a cookie with SameSite=None when user specifies samesite None and conditional samesite none with supported user agent", function()
|
||||
mock_ngx({ var = { location_path = "/", host = "test.com" , http_user_agent = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.2704.103 Safari/537.36"} })
|
||||
test_set_cookie(sticky_balanced, "None", true, "/; SameSite=None")
|
||||
end)
|
||||
it("returns a cookie without SameSite=None when user specifies samesite None and conditional samesite none with unsupported user agent", function()
|
||||
mock_ngx({ var = { location_path = "/", host = "test.com" , http_user_agent = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36"} })
|
||||
test_set_cookie(sticky_balanced, "None", true, "/")
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
|
|
|||
51
rootfs/etc/nginx/lua/test/util/same_site_test.lua
Normal file
51
rootfs/etc/nginx/lua/test/util/same_site_test.lua
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
describe("same_site_compatible_test", function()
|
||||
it("returns false for chrome 4", function()
|
||||
local same_site = require("util.same_site")
|
||||
assert.False(same_site.same_site_none_compatible("Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2704.103 Safari/537.36"))
|
||||
end)
|
||||
it("returns false for chrome 5", function()
|
||||
local same_site = require("util.same_site")
|
||||
assert.False(same_site.same_site_none_compatible("Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2704.103 Safari/537.36"))
|
||||
end)
|
||||
it("returns false for chrome 6", function()
|
||||
local same_site = require("util.same_site")
|
||||
assert.False(same_site.same_site_none_compatible("Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2704.103 Safari/537.36"))
|
||||
end)
|
||||
it("returns false for iPhone OS 12", function()
|
||||
local same_site = require("util.same_site")
|
||||
assert.False(same_site.same_site_none_compatible("Mozilla/5.0 (iPhone; CPU iPhone OS 12_0 like Mac OS X) AppleWebKit/602.1.50 (KHTML, like Gecko) CriOS/56.0.2924.75 Mobile/14E5239e Safari/602.1"))
|
||||
end)
|
||||
it("returns false for iPad OS 12", function()
|
||||
local same_site = require("util.same_site")
|
||||
assert.False(same_site.same_site_none_compatible("Mozilla/5.0 (iPad; CPU OS 12_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0 Mobile/15E148 Safari/604.1"))
|
||||
end)
|
||||
it("returns false for Mac 10.14 Safari", function()
|
||||
local same_site = require("util.same_site")
|
||||
assert.False(same_site.same_site_none_compatible("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.1.2 Safari/605.1.15"))
|
||||
end)
|
||||
|
||||
it("returns true for chrome 7", function()
|
||||
local same_site = require("util.same_site")
|
||||
assert.True(same_site.same_site_none_compatible("Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.2704.103 Safari/537.36"))
|
||||
end)
|
||||
it("returns true for chrome 8", function()
|
||||
local same_site = require("util.same_site")
|
||||
assert.True(same_site.same_site_none_compatible("Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.2704.103 Safari/537.36"))
|
||||
end)
|
||||
it("returns true for iPhone OS 13", function()
|
||||
local same_site = require("util.same_site")
|
||||
assert.True(same_site.same_site_none_compatible("Mozilla/5.0 (iPhone; CPU iPhone OS 13_0 like Mac OS X) AppleWebKit/602.1.50 (KHTML, like Gecko) CriOS/56.0.2924.75 Mobile/14E5239e Safari/602.1"))
|
||||
end)
|
||||
it("returns true for iPad OS 13", function()
|
||||
local same_site = require("util.same_site")
|
||||
assert.True(same_site.same_site_none_compatible("Mozilla/5.0 (iPad; CPU OS 13_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0 Mobile/15E148 Safari/604.1"))
|
||||
end)
|
||||
it("returns true for Mac 10.15 Safari", function()
|
||||
local same_site = require("util.same_site")
|
||||
assert.True(same_site.same_site_none_compatible("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_2) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.4 Safari/605.1.15"))
|
||||
end)
|
||||
it("returns true for Mac 10.14 Chrome", function()
|
||||
local same_site = require("util.same_site")
|
||||
assert.True(same_site.same_site_none_compatible("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.117 Safari/537.36"))
|
||||
end)
|
||||
end)
|
||||
36
rootfs/etc/nginx/lua/util/same_site.lua
Normal file
36
rootfs/etc/nginx/lua/util/same_site.lua
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
local _M = {}
|
||||
|
||||
-- determines whether to apply a SameSite=None attribute
|
||||
-- to a cookie, based on the user agent.
|
||||
-- returns: boolean
|
||||
--
|
||||
-- Chrome 80 treating third-party cookies as SameSite=Strict
|
||||
-- if SameSite is missing. Certain old browsers don't recognize
|
||||
-- SameSite=None and will reject cookies entirely bearing SameSite=None.
|
||||
-- This creates a situation where fixing things for
|
||||
-- Chrome >= 80 breaks things for old browsers.
|
||||
-- This function compares the user agent against known
|
||||
-- browsers which will reject SameSite=None cookies.
|
||||
-- reference: https://www.chromium.org/updates/same-site/incompatible-clients
|
||||
function _M.same_site_none_compatible(user_agent)
|
||||
if string.match(user_agent, "Chrome/4") then
|
||||
return false
|
||||
elseif string.match(user_agent, "Chrome/5") then
|
||||
return false
|
||||
elseif string.match(user_agent, "Chrome/6") then
|
||||
return false
|
||||
elseif string.match(user_agent, "CPU iPhone OS 12") then
|
||||
return false
|
||||
elseif string.match(user_agent, "iPad; CPU OS 12") then
|
||||
return false
|
||||
elseif string.match(user_agent, "Macintosh")
|
||||
and string.match(user_agent, "Intel Mac OS X 10_14")
|
||||
and string.match(user_agent, "Safari")
|
||||
and not string.match(user_agent, "Chrome") then
|
||||
return false
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
return _M
|
||||
Loading…
Add table
Add a link
Reference in a new issue