Skip to content

Commit

Permalink
feat(basic-auth) password encryption
Browse files Browse the repository at this point in the history
On the contrary of #527, this only allows for sha1 encryption. The
reason is that due to the current architecture, we cannot support two
types at the same time (supporting plain is a bad practice anyways).
Because a basicauth_credential has no relation to a plugin entity (they
are **not** semantically related anyways), we cannot now how the
password is stored/encrypted.

I also took the opportunity of 0.5.0 and the migration script to make
that decision. The migration script will be updated to also migrate the
current passwords.

This does a bit more than #527:

- unit tests
- support encryption in unit test mode (with a mock using a vendor sha1
  library)
- comparison of the hash at the proxy level (for actual authentication)

Resolves #33
  • Loading branch information
thibaultcha committed Sep 15, 2015
1 parent 492d5fd commit ba41444
Show file tree
Hide file tree
Showing 9 changed files with 417 additions and 37 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ drop:
@bin/kong db -c $(DEVELOPMENT_CONF) drop

lint:
@find kong spec -name '*.lua' ! -name 'invalid-module.lua' | xargs luacheck -q
@find kong spec -name '*.lua' -not -name 'invalid-module.lua' -not -path 'kong/vendor/*' | xargs luacheck -q

test:
@busted -v spec/unit
Expand Down
1 change: 1 addition & 0 deletions kong-0.5.0-1.rockspec
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ build = {
["kong.plugins.base_plugin"] = "kong/plugins/base_plugin.lua",

["kong.plugins.basic-auth.migrations.cassandra"] = "kong/plugins/basic-auth/migrations/cassandra.lua",
["kong.plugins.basic-auth.crypto"] = "kong/plugins/basic-auth/crypto.lua",
["kong.plugins.basic-auth.handler"] = "kong/plugins/basic-auth/handler.lua",
["kong.plugins.basic-auth.access"] = "kong/plugins/basic-auth/access.lua",
["kong.plugins.basic-auth.schema"] = "kong/plugins/basic-auth/schema.lua",
Expand Down
38 changes: 18 additions & 20 deletions kong/plugins/basic-auth/access.lua
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ local cache = require "kong.tools.database_cache"
local stringy = require "stringy"
local responses = require "kong.tools.responses"
local constants = require "kong.constants"
local crypto = require "kong.plugins.basic-auth.crypto"

local AUTHORIZATION = "authorization"
local PROXY_AUTHORIZATION = "proxy-authorization"
Expand Down Expand Up @@ -50,26 +51,23 @@ local function retrieve_credentials(request, header_name, conf)
return username, password
end

-- Fast lookup for credential validation depending on the type of the authentication
--
-- All methods must respect:
--
-- @param {table} credential The retrieved credential from the username passed in the request
-- @param {string} username
-- @param {string} password
-- @return {boolean} Success of authentication
local function validate_credentials(credential, username, password)
if credential then
-- TODO: No encryption yet
return credential.password == password
--- Validate a credential in the Authorization header against one fetched from the database.
-- @param credential The retrieved credential from the username passed in the request
-- @param given_password The password as given in the Authorization header
-- @return Success of authentication
local function validate_credentials(credential, given_password)
local digest, err = crypto.encrypt({consumer_id = credential.consumer_id, password = given_password})
if err then
ngx.log(ngx.ERR, "[basic-auth] "..err)
end
return credential.password == digest
end

local function load_credential(username)
local function load_credential_from_db(username)
local credential
if username then
credential = cache.get_or_set(cache.basicauth_credential_key(username), function()
local credentials, err = dao.basicauth_credentials:find_by_keys { username = username }
local credentials, err = dao.basicauth_credentials:find_by_keys {username = username}
local result
if err then
return responses.send_HTTP_INTERNAL_SERVER_ERROR(err)
Expand All @@ -91,18 +89,18 @@ function _M.execute(conf)
end

local credential
local username, password = retrieve_credentials(ngx.req, PROXY_AUTHORIZATION, conf)
if username then
credential = load_credential(username)
local given_username, given_password = retrieve_credentials(ngx.req, PROXY_AUTHORIZATION, conf)
if given_username then
credential = load_credential_from_db(given_username)
end

-- Try with the authorization header
if not credential then
username, password = retrieve_credentials(ngx.req, AUTHORIZATION, conf)
credential = load_credential(username)
given_username, given_password = retrieve_credentials(ngx.req, AUTHORIZATION, conf)
credential = load_credential_from_db(given_username)
end

if not validate_credentials(credential, username, password) then
if not credential or not validate_credentials(credential, given_password) then
ngx.ctx.stop_phases = true -- interrupt other phases of this request
return responses.send_HTTP_FORBIDDEN("Invalid authentication credentials")
end
Expand Down
53 changes: 53 additions & 0 deletions kong/plugins/basic-auth/crypto.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
---
-- Module to encrypt the basic-auth credentials password field

local utils = require "kong.tools.utils"
local format = string.format

--- Salt the password
-- Password is salted with the credential's consumer_id (long enough, unique)
-- @param credential The basic auth credential table
local function salt_password(credential)
return format("%s%s", credential.password, credential.consumer_id)
end

local in_openresty = utils.load_module_if_exists("resty.string")
if not in_openresty then
--- Mock for usage outside of Openresty (unit testing)
return {encrypt = function(credential)
local sha1 = require "kong.vendor.sha1"
local salted = salt_password(credential)
return sha1(salted)
end}
end

local resty_sha1 = require "resty.sha1"
local resty_string = require "resty.string"

--- Return a sha1 hash of the given string
-- @param string String (password) to hash
-- @return sha1 hash of the given string
local function sha1(string)
local sha1 = resty_sha1:new()
if not sha1 then
return nil, "failed to create the sha1 object"
end

local ok = sha1:update(string)
if not ok then
return nil, "failed to add data"
end

local digest = sha1:final()
return resty_string.to_hex(digest)
end

return {
--- Encrypt the password field credential table
-- @param credential The basic auth credential table
-- @return hash of the salted credential's password
encrypt = function(credential)
local salted = salt_password(credential)
return sha1(salted)
end
}
24 changes: 18 additions & 6 deletions kong/plugins/basic-auth/daos.lua
Original file line number Diff line number Diff line change
@@ -1,13 +1,25 @@
local BaseDao = require "kong.dao.cassandra.base_dao"
local crypto = require "kong.plugins.basic-auth.crypto"

local function encrypt_password(password, credential)
local encrypted, err = crypto.encrypt(credential)
if err then
return false, err
end

credential.password = encrypted

return true
end

local SCHEMA = {
primary_key = {"id"},
fields = {
id = { type = "id", dao_insert_value = true },
created_at = { type = "timestamp", dao_insert_value = true },
consumer_id = { type = "id", required = true, queryable = true, foreign = "consumers:id" },
username = { type = "string", required = true, unique = true, queryable = true },
password = { type = "string" }
id = {type = "id", dao_insert_value = true},
created_at = {type = "timestamp", dao_insert_value = true},
consumer_id = {type = "id", required = true, queryable = true, foreign = "consumers:id"},
username = {type = "string", required = true, unique = true, queryable = true},
password = {type = "string", func = encrypt_password}
}
}

Expand All @@ -20,4 +32,4 @@ function BasicAuthCredentials:new(properties)
BasicAuthCredentials.super.new(self, properties)
end

return { basicauth_credentials = BasicAuthCredentials }
return {basicauth_credentials = BasicAuthCredentials}
2 changes: 1 addition & 1 deletion kong/plugins/basic-auth/schema.lua
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
return {
no_consumer = true,
fields = {
hide_credentials = { type = "boolean", default = false }
hide_credentials = {type = "boolean", default = false}
}
}
2 changes: 1 addition & 1 deletion kong/tools/utils.lua
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ function _M.load_module_if_exists(module_name)
if status then
return true, res
-- Here we match any character because if a module has a dash '-' in its name, we would need to escape it.
elseif type(res) == "string" and string.find(res, "module '.*' not found") then
elseif type(res) == "string" and string.find(res, "module '"..module_name.."' not found", nil, true) then
return false
else
error(res)
Expand Down
Loading

0 comments on commit ba41444

Please sign in to comment.