Skip to content

Commit

Permalink
fix(hmac): add missing www-authenticate headers
Browse files Browse the repository at this point in the history
When server returns 401 Unauthorized response it should
return WWW-Authenticate header as well with proper challenge.
HMAC auth was missing this header.

Fix: #7772
KAG-321
  • Loading branch information
nowNick committed Oct 26, 2023
1 parent 30d90f3 commit d87e18b
Show file tree
Hide file tree
Showing 4 changed files with 60 additions and 11 deletions.
3 changes: 3 additions & 0 deletions changelog/unreleased/kong/hmac_www_authenticate.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
message: Add WWW-Authenticate headers to 401 response in hmac auth plugin.
type: bugfix
scope: Plugin
25 changes: 15 additions & 10 deletions kong/plugins/hmac-auth/access.lua
Original file line number Diff line number Diff line change
Expand Up @@ -276,24 +276,29 @@ local function set_consumer(consumer, credential)
end
end

local function unauthorized(message, realm)
return { status = 401, message = message, headers = { ["WWW-Authenticate"] = 'hmac' .. ' realm="' .. realm .. '"' } }
end


local function do_authentication(conf)
local authorization = kong_request.get_header(AUTHORIZATION)
local proxy_authorization = kong_request.get_header(PROXY_AUTHORIZATION)
local realm = conf.realm or ngx.var.host

-- If both headers are missing, return 401
if not (authorization or proxy_authorization) then
return false, { status = 401, message = "Unauthorized" }
return false, unauthorized("Unauthorized", realm)
end

-- validate clock skew
if not (validate_clock_skew(X_DATE, conf.clock_skew) or
validate_clock_skew(DATE, conf.clock_skew)) then
return false, {
status = 401,
message = "HMAC signature cannot be verified, a valid date or " ..
"x-date header is required for HMAC Authentication"
}
return false, unauthorized(
"HMAC signature cannot be verified, a valid date or " ..
"x-date header is required for HMAC Authentication",
realm
)
end

-- retrieve hmac parameter from Proxy-Authorization header
Expand All @@ -313,26 +318,26 @@ local function do_authentication(conf)
local ok, err = validate_params(hmac_params, conf)
if not ok then
kong.log.debug(err)
return false, { status = 401, message = SIGNATURE_NOT_VALID }
return false, unauthorized(SIGNATURE_NOT_VALID, realm)
end

-- validate signature
local credential = load_credential(hmac_params.username)
if not credential then
kong.log.debug("failed to retrieve credential for ", hmac_params.username)
return false, { status = 401, message = SIGNATURE_NOT_VALID }
return false, unauthorized(SIGNATURE_NOT_VALID, realm)
end

hmac_params.secret = credential.secret

if not validate_signature(hmac_params) then
return false, { status = 401, message = SIGNATURE_NOT_SAME }
return false, unauthorized(SIGNATURE_NOT_SAME, realm)
end

-- If request body validation is enabled, then verify digest.
if conf.validate_request_body and not validate_body() then
kong.log.debug("digest validation failed")
return false, { status = 401, message = SIGNATURE_NOT_SAME }
return false, unauthorized(SIGNATURE_NOT_SAME, realm)
end

-- Retrieve consumer
Expand Down
5 changes: 5 additions & 0 deletions kong/plugins/hmac-auth/schema.lua
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@ return {
elements = { type = "string", one_of = ALGORITHMS },
default = ALGORITHMS,
}, },
{
realm = { description = "When authentication or authorization fails, or there is an unexpected error, the plugin sends an `WWW-Authenticate` header with the `realm` attribute value.", type = "string",
required = false,
},
},
},
},
},
Expand Down
38 changes: 37 additions & 1 deletion spec/03-plugins/19-hmac-auth/03-access_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ local openssl_hmac = require "resty.openssl.hmac"
local helpers = require "spec.helpers"
local utils = require "kong.tools.utils"
local resty_sha256 = require "resty.sha256"
local meta = require "kong.meta"

local fmt = string.format

Expand Down Expand Up @@ -47,7 +48,8 @@ for _, strategy in helpers.each_strategy() do
name = "hmac-auth",
route = { id = route1.id },
config = {
clock_skew = 3000
clock_skew = 3000,
realm = "test-realm"
}
}

Expand Down Expand Up @@ -186,6 +188,7 @@ for _, strategy in helpers.each_strategy() do
}
})
local body = assert.res_status(401, res)
assert.equal('hmac realm="test-realm"', res.headers["WWW-Authenticate"])
body = cjson.decode(body)
assert.equal("Unauthorized", body.message)
end)
Expand All @@ -211,6 +214,7 @@ for _, strategy in helpers.each_strategy() do
}
})
local body = assert.res_status(401, res)
assert.equal('hmac realm="test-realm"', res.headers["WWW-Authenticate"])
body = cjson.decode(body)
assert.equal(SIGNATURE_NOT_VALID, body.message)
end)
Expand All @@ -228,6 +232,7 @@ for _, strategy in helpers.each_strategy() do
}
})
local body = assert.res_status(401, res)
assert.equal('hmac realm="test-realm"', res.headers["WWW-Authenticate"])
body = cjson.decode(body)
assert.equal("HMAC signature does not match", body.message)
end)
Expand All @@ -242,6 +247,7 @@ for _, strategy in helpers.each_strategy() do
}
})
local body = assert.res_status(401, res)
assert.equal('hmac realm="test-realm"', res.headers["WWW-Authenticate"])
body = cjson.decode(body)
assert.equal([[HMAC signature cannot be verified, ]]
.. [[a valid date or x-date header is]]
Expand All @@ -260,6 +266,7 @@ for _, strategy in helpers.each_strategy() do
}
})
local body = assert.res_status(401, res)
assert.equal('hmac realm="test-realm"', res.headers["WWW-Authenticate"])
body = cjson.decode(body)
assert.equal(SIGNATURE_NOT_VALID, body.message)
end)
Expand All @@ -276,6 +283,7 @@ for _, strategy in helpers.each_strategy() do
}
})
local body = assert.res_status(401, res)
assert.equal('hmac realm="test-realm"', res.headers["WWW-Authenticate"])
body = cjson.decode(body)
assert.equal(SIGNATURE_NOT_VALID, body.message)
end)
Expand All @@ -293,6 +301,7 @@ for _, strategy in helpers.each_strategy() do
}
})
local body = assert.res_status(401, res)
assert.equal('hmac realm="test-realm"', res.headers["WWW-Authenticate"])
body = cjson.decode(body)
assert.equal(SIGNATURE_NOT_VALID, body.message)
end)
Expand All @@ -310,6 +319,7 @@ for _, strategy in helpers.each_strategy() do
}
})
local body = assert.res_status(401, res)
assert.equal('hmac realm="test-realm"', res.headers["WWW-Authenticate"])
body = cjson.decode(body)
assert.equal(SIGNATURE_NOT_VALID, body.message)
end)
Expand All @@ -326,6 +336,7 @@ for _, strategy in helpers.each_strategy() do
}
})
local body = assert.res_status(401, res)
assert.equal('hmac realm="test-realm"', res.headers["WWW-Authenticate"])
body = cjson.decode(body)
assert.equal(SIGNATURE_NOT_VALID, body.message)
end)
Expand All @@ -341,6 +352,7 @@ for _, strategy in helpers.each_strategy() do
}
})
local body = assert.res_status(401, res)
assert.equal('hmac realm="test-realm"', res.headers["WWW-Authenticate"])
body = cjson.decode(body)
assert.equal("Unauthorized", body.message)
end)
Expand All @@ -361,6 +373,7 @@ for _, strategy in helpers.each_strategy() do
},
})
local body = assert.res_status(401, res)
assert.equal('hmac realm="test-realm"', res.headers["WWW-Authenticate"])
body = cjson.decode(body)
assert.not_nil(body.message)
assert.matches("HMAC signature cannot be verified", body.message)
Expand All @@ -381,6 +394,7 @@ for _, strategy in helpers.each_strategy() do
},
})
local body = assert.res_status(401, res)
assert.equal('hmac realm="test-realm"', res.headers["WWW-Authenticate"])
body = cjson.decode(body)
assert.not_nil(body.message)
assert.matches("HMAC signature cannot be verified", body.message)
Expand Down Expand Up @@ -633,6 +647,7 @@ for _, strategy in helpers.each_strategy() do
})

local body = assert.res_status(401, res)
assert.equal('hmac realm="test-realm"', res.headers["WWW-Authenticate"])
body = cjson.decode(body)
assert.equal(SIGNATURE_NOT_VALID, body.message)
end)
Expand All @@ -659,6 +674,7 @@ for _, strategy in helpers.each_strategy() do
})

local body = assert.res_status(401, res)
assert.equal('hmac realm="test-realm"', res.headers["WWW-Authenticate"])
body = cjson.decode(body)
assert.equal(SIGNATURE_NOT_VALID, body.message)
end)
Expand Down Expand Up @@ -686,6 +702,7 @@ for _, strategy in helpers.each_strategy() do
})

local body = assert.res_status(401, res)
assert.equal('hmac realm="test-realm"', res.headers["WWW-Authenticate"])
body = cjson.decode(body)
assert.equal(SIGNATURE_NOT_VALID, body.message)
end)
Expand All @@ -711,6 +728,7 @@ for _, strategy in helpers.each_strategy() do
},
})
local body = assert.res_status(401, res)
assert.equal('hmac realm="test-realm"', res.headers["WWW-Authenticate"])
body = cjson.decode(body)
assert.equal(SIGNATURE_NOT_VALID, body.message)
end)
Expand All @@ -736,6 +754,7 @@ for _, strategy in helpers.each_strategy() do
},
})
local body = assert.res_status(401, res)
assert.equal('hmac realm="test-realm"', res.headers["WWW-Authenticate"])
body = cjson.decode(body)
assert.equal(SIGNATURE_NOT_VALID, body.message)
end)
Expand All @@ -761,6 +780,7 @@ for _, strategy in helpers.each_strategy() do
},
})
local body = assert.res_status(401, res)
assert.equal('hmac realm="test-realm"', res.headers["WWW-Authenticate"])
body = cjson.decode(body)
assert.equal(SIGNATURE_NOT_VALID, body.message)
end)
Expand All @@ -786,6 +806,7 @@ for _, strategy in helpers.each_strategy() do
},
})
local body = assert.res_status(401, res)
assert.equal('hmac realm="test-realm"', res.headers["WWW-Authenticate"])
body = cjson.decode(body)
assert.equal(SIGNATURE_NOT_VALID, body.message)
end)
Expand Down Expand Up @@ -834,6 +855,7 @@ for _, strategy in helpers.each_strategy() do
},
})
assert.res_status(401, res)
assert.equal('hmac realm="test-realm"', res.headers["WWW-Authenticate"])
end)

it("should pass the right headers to the upstream server", function()
Expand Down Expand Up @@ -905,6 +927,7 @@ for _, strategy in helpers.each_strategy() do
},
})
local body = assert.res_status(401, res)
assert.equal('hmac realm="test-realm"', res.headers["WWW-Authenticate"])
body = cjson.decode(body)
assert.equal([[HMAC signature cannot be verified, a valid date or]]
.. [[ x-date header is required for HMAC Authentication]], body.message)
Expand All @@ -930,6 +953,7 @@ for _, strategy in helpers.each_strategy() do
},
})
local body = assert.res_status(401, res)
assert.equal('hmac realm="test-realm"', res.headers["WWW-Authenticate"])
body = cjson.decode(body)
assert.equal([[HMAC signature cannot be verified, a valid date or]]
.. [[ x-date header is required for HMAC Authentication]], body.message)
Expand Down Expand Up @@ -1071,6 +1095,7 @@ for _, strategy in helpers.each_strategy() do
}
})
local body = assert.res_status(401, res)
assert.equal('hmac realm="hmacauth4.com"', res.headers["WWW-Authenticate"])
body = cjson.decode(body)
assert.equal("HMAC signature does not match", body.message)
end)
Expand Down Expand Up @@ -1253,6 +1278,7 @@ for _, strategy in helpers.each_strategy() do
},
})
local body = assert.res_status(401, res)
assert.equal('hmac realm="hmacauth4.com"', res.headers["WWW-Authenticate"])
body = cjson.decode(body)
assert.equal("HMAC signature does not match", body.message)
end)
Expand Down Expand Up @@ -1280,6 +1306,7 @@ for _, strategy in helpers.each_strategy() do
},
})
local body = assert.res_status(401, res)
assert.equal('hmac realm="hmacauth4.com"', res.headers["WWW-Authenticate"])
body = cjson.decode(body)
assert.equal("HMAC signature does not match", body.message)
end)
Expand Down Expand Up @@ -1307,6 +1334,7 @@ for _, strategy in helpers.each_strategy() do
}
})
local body = assert.res_status(401, res)
assert.equal('hmac realm="hmacauth4.com"', res.headers["WWW-Authenticate"])
body = cjson.decode(body)
assert.equal("HMAC signature does not match", body.message)
end)
Expand Down Expand Up @@ -1354,6 +1382,7 @@ for _, strategy in helpers.each_strategy() do
},
})
assert.res_status(401, res)
assert.equal('hmac realm="hmacauth5.com"', res.headers["WWW-Authenticate"])

encodedSignature = ngx.encode_base64(
hmac_sha1_binary("secret", "date: "
Expand All @@ -1373,6 +1402,7 @@ for _, strategy in helpers.each_strategy() do
},
})
assert.res_status(401, res)
assert.equal('hmac realm="hmacauth5.com"', res.headers["WWW-Authenticate"])
end)

it("should pass with GET with request-line having query param", function()
Expand Down Expand Up @@ -1587,6 +1617,7 @@ for _, strategy in helpers.each_strategy() do
},
})
assert.res_status(401, res)
assert.equal('hmac realm="hmacauth5.com"', res.headers["WWW-Authenticate"])
end)

it("should pass with GET with hmac-sha384", function()
Expand Down Expand Up @@ -1653,6 +1684,7 @@ for _, strategy in helpers.each_strategy() do
},
})
assert.res_status(401, res)
assert.equal('hmac realm="hmacauth6.com"', res.headers["WWW-Authenticate"])
end)

it("should return a 401 with an invalid authorization header", function()
Expand All @@ -1668,6 +1700,7 @@ for _, strategy in helpers.each_strategy() do
},
})
assert.res_status(401, res)
assert.equal('hmac realm="hmacauth6.com"', res.headers["WWW-Authenticate"])
end)

it("should pass with hmac-sha1", function()
Expand Down Expand Up @@ -1831,6 +1864,7 @@ for _, strategy in helpers.each_strategy() do
},
})
assert.response(res).has.status(401)
assert.equal('hmac realm="logical-and.com"', res.headers["WWW-Authenticate"])
end)

it("fails 401, with only the second credential provided", function()
Expand All @@ -1844,6 +1878,7 @@ for _, strategy in helpers.each_strategy() do
},
})
assert.response(res).has.status(401)
assert.equal('Key realm="' .. meta._NAME .. '"', res.headers["WWW-Authenticate"])
end)

it("fails 401, with no credential provided", function()
Expand All @@ -1855,6 +1890,7 @@ for _, strategy in helpers.each_strategy() do
},
})
assert.response(res).has.status(401)
assert.equal('Key realm="' .. meta._NAME .. '"', res.headers["WWW-Authenticate"])
end)

end)
Expand Down

0 comments on commit d87e18b

Please sign in to comment.