summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRonny Berndt <ronny@apache.org>2023-02-23 09:10:55 +0100
committerNick Vatamaniuc <nickva@users.noreply.github.com>2023-04-13 15:50:18 -0400
commit05659effc7c6f080dd90e908ea693928f4f6722b (patch)
treeaa902afecda2783d744a6afff76a3bbbb03c54e4
parent87ceb1b01fa09974f008bddda868b264d9f31249 (diff)
downloadcouchdb-05659effc7c6f080dd90e908ea693928f4f6722b.tar.gz
Upgrade hash algorithm for proxy auth (#4438)
Proxy auth can now use one of the configured hash algorithms from chttpd_auth/hash_algorithms to decode authentication tokens.
-rw-r--r--src/chttpd/test/eunit/chttpd_auth_tests.erl87
-rw-r--r--src/couch/src/couch_httpd_auth.erl15
-rw-r--r--src/docs/src/api/server/authn.rst16
-rw-r--r--src/docs/src/config/auth.rst16
4 files changed, 117 insertions, 17 deletions
diff --git a/src/chttpd/test/eunit/chttpd_auth_tests.erl b/src/chttpd/test/eunit/chttpd_auth_tests.erl
index 7beda9bc7..7e7b94a12 100644
--- a/src/chttpd/test/eunit/chttpd_auth_tests.erl
+++ b/src/chttpd/test/eunit/chttpd_auth_tests.erl
@@ -12,6 +12,9 @@
-module(chttpd_auth_tests).
+-define(WORKING_HASHES, "sha256, sha512, sha, blake2s").
+-define(FAILING_HASHES, "md4, md5, ripemd160").
+
-include_lib("couch/include/couch_eunit.hrl").
-include_lib("couch/include/couch_db.hrl").
@@ -24,6 +27,27 @@ setup() ->
teardown(_Url) ->
ok.
+setup_proxy_auth() ->
+ {StartCtx, ProxyCfgFile} = start_couch_with_cfg("{chttpd_auth, proxy_authentication_handler}"),
+ config:set("chttpd", "require_valid_user", "false", false),
+ config:set("chttpd_auth", "hash_algorithms", ?WORKING_HASHES, false),
+ config:set("chttpd_auth", "proxy_use_secret", "true", false),
+ config:set("chttpd_auth", "secret", "the_secret", false),
+ HashesShouldWork = re:split(?WORKING_HASHES, "\\s*,\\s*", [
+ trim, {return, binary}
+ ]),
+ HashesShouldFail = re:split(?FAILING_HASHES, "\\s*,\\s*", [trim, {return, binary}]),
+ SupportedHashAlgorithms = crypto:supports(hashs),
+ {{StartCtx, ProxyCfgFile}, HashesShouldWork, HashesShouldFail, SupportedHashAlgorithms}.
+
+teardown_proxy_auth({{Ctx, ProxyCfgFile}, _, _, _}) ->
+ ok = file:delete(ProxyCfgFile),
+ config:delete("chttpd_auth", "hash_algorithms", false),
+ config:delete("chttpd_auth", "secret", false),
+ config:delete("chttpd_auth", "proxy_use_secret", false),
+ config:delete("chttpd", "require_valid_user", false),
+ test_util:stop_couch(Ctx).
+
require_valid_user_exception_test_() ->
{
"_up",
@@ -43,6 +67,20 @@ require_valid_user_exception_test_() ->
}
}.
+proxy_auth_test_() ->
+ {
+ "Testing hash algorithms for proxy auth",
+ {
+ setup,
+ fun setup_proxy_auth/0,
+ fun teardown_proxy_auth/1,
+ with([
+ ?TDEF(test_hash_algorithms_with_proxy_auth_should_work),
+ ?TDEF(test_hash_algorithms_with_proxy_auth_should_fail)
+ ])
+ }
+ }.
+
set_require_user_false() ->
ok = config:set("chttpd", "require_valid_user", "false", _Persist = false).
@@ -125,3 +163,52 @@ should_handle_require_valid_user_except_up_on_non_up_routes(_Url) ->
set_require_user_except_for_up_true(),
?assertThrow(ExpectAuth, chttpd_auth:party_mode_handler(NonUpRequest))
end).
+
+% Helper functions
+base_url() ->
+ Addr = config:get("chttpd", "bind_address", "127.0.0.1"),
+ Port = integer_to_list(mochiweb_socket_server:get(chttpd, port)),
+ "http://" ++ Addr ++ ":" ++ Port.
+
+append_to_cfg_chain(Cfg) ->
+ CfgDir = filename:dirname(lists:last(?CONFIG_CHAIN)),
+ CfgFile = filename:join([CfgDir, "chttpd_proxy_auth_cfg.ini"]),
+ CfgSect = io_lib:format("[chttpd]~nauthentication_handlers = ~s~n", [Cfg]),
+ ok = file:write_file(CfgFile, CfgSect),
+ ?CONFIG_CHAIN ++ [CfgFile].
+
+start_couch_with_cfg(Cfg) ->
+ CfgChain = append_to_cfg_chain(Cfg),
+ StartCtx = test_util:start_couch(CfgChain, [chttpd]),
+ ProxyCfgFile = lists:last(CfgChain),
+ {StartCtx, ProxyCfgFile}.
+
+% Test functions
+test_hash_algorithm([]) ->
+ ok;
+test_hash_algorithm([DefaultHashAlgorithm | DecodingHashAlgorithmsList] = _) ->
+ Secret = chttpd_util:get_chttpd_auth_config("secret"),
+ Token = couch_util:to_hex(couch_util:hmac(DefaultHashAlgorithm, Secret, "PROXY-USER")),
+ Headers = [
+ {"X-Auth-CouchDB-UserName", "PROXY-USER"},
+ {"X-Auth-CouchDB-Roles", "PROXY-USER-ROLE1, PROXY-USER-ROLE2"},
+ {"X-Auth-CouchDB-Token", Token}
+ ],
+ {ok, _, _, ReqBody} = test_request:get(base_url() ++ "/_session", Headers),
+ IsAuthenticatedViaProxy = couch_util:get_nested_json_value(
+ jiffy:decode(ReqBody), [<<"info">>, <<"authenticated">>]
+ ),
+ ?assertEqual(IsAuthenticatedViaProxy, <<"proxy">>),
+ test_hash_algorithm(DecodingHashAlgorithmsList).
+
+test_hash_algorithms_with_proxy_auth_should_work(
+ {_Ctx, WorkingHashes, _FailingHashes, SupportedHashAlgorithms} = _
+) ->
+ Hashes = couch_util:verify_hash_names(WorkingHashes, SupportedHashAlgorithms),
+ test_hash_algorithm(Hashes).
+
+test_hash_algorithms_with_proxy_auth_should_fail(
+ {_Ctx, _WorkingHashes, FailingHashes, SupportedHashAlgorithms} = _
+) ->
+ Hashes = couch_util:verify_hash_names(FailingHashes, SupportedHashAlgorithms),
+ ?assertThrow({not_found, _}, test_hash_algorithm(Hashes)).
diff --git a/src/couch/src/couch_httpd_auth.erl b/src/couch/src/couch_httpd_auth.erl
index 4a7b217d1..72e0cd76f 100644
--- a/src/couch/src/couch_httpd_auth.erl
+++ b/src/couch/src/couch_httpd_auth.erl
@@ -201,18 +201,21 @@ proxy_auth_user(Req) ->
undefined ->
Req#httpd{user_ctx = #user_ctx{name = ?l2b(UserName), roles = Roles}};
Secret ->
- ExpectedToken = couch_util:to_hex(
- couch_util:hmac(sha, Secret, UserName)
- ),
- case header_value(Req, XHeaderToken) of
- Token when Token == ExpectedToken ->
+ HashAlgorithms = couch_util:get_config_hash_algorithms(),
+ Token = header_value(Req, XHeaderToken),
+ VerifyTokens = fun(HashAlg) ->
+ Hmac = couch_util:hmac(HashAlg, Secret, UserName),
+ couch_passwords:verify(couch_util:to_hex(Hmac), Token)
+ end,
+ case lists:any(VerifyTokens, HashAlgorithms) of
+ true ->
Req#httpd{
user_ctx = #user_ctx{
name = ?l2b(UserName),
roles = Roles
}
};
- _ ->
+ false ->
nil
end
end;
diff --git a/src/docs/src/api/server/authn.rst b/src/docs/src/api/server/authn.rst
index bffe0bf27..5d23ddb73 100644
--- a/src/docs/src/api/server/authn.rst
+++ b/src/docs/src/api/server/authn.rst
@@ -291,22 +291,24 @@ remotely authenticated user. By default, the client just needs to pass specific
headers to CouchDB with related requests:
- :config:option:`X-Auth-CouchDB-UserName <chttpd_auth/x_auth_username>`:
- username;
+ username
- :config:option:`X-Auth-CouchDB-Roles <chttpd_auth/x_auth_roles>`:
- comma-separated (``,``) list of user roles;
+ comma-separated (``,``) list of user roles
- :config:option:`X-Auth-CouchDB-Token <chttpd_auth/x_auth_token>`:
authentication token. When
:config:option:`proxy_use_secret <chttpd_auth/proxy_use_secret>`
is set (which is strongly recommended!), this header provides an HMAC of the
username to authenticate and the secret token to prevent requests from
- untrusted sources. (Use the SHA1 of the username and sign with the secret)
+ untrusted sources. (Use one of the configured hash algorithms in
+ :config:option:`chttpd_auth/hash_algorithms <chttpd_auth/hash_algorithms>`
+ and sign the username with the secret)
**Creating the token (example with openssl)**:
.. code-block:: sh
- echo -n "foo" | openssl dgst -sha1 -hmac "the_secret"
- # (stdin)= 22047ebd7c4ec67dfbcbad7213a693249dbfbf86
+ echo -n "foo" | openssl dgst -sha256 -hmac "the_secret"
+ # (stdin)= 3f0786e96b20b0102b77f1a49c041be6977cfb3bf78c41a12adc121cd9b4e68a
**Request**:
@@ -318,7 +320,7 @@ headers to CouchDB with related requests:
Content-Type: application/json; charset=utf-8
X-Auth-CouchDB-Roles: users,blogger
X-Auth-CouchDB-UserName: foo
- X-Auth-CouchDB-Token: 22047ebd7c4ec67dfbcbad7213a693249dbfbf86
+ X-Auth-CouchDB-Token: 3f0786e96b20b0102b77f1a49c041be6977cfb3bf78c41a12adc121cd9b4e68a
**Response**:
@@ -351,7 +353,7 @@ headers to CouchDB with related requests:
}
}
-Note that you don't need to request :ref:`session <api/auth/session>`
+Note that you don't need to request a :ref:`session <api/auth/session>`
to be authenticated by this method if all required HTTP headers are provided.
.. _api/auth/jwt:
diff --git a/src/docs/src/config/auth.rst b/src/docs/src/config/auth.rst
index d43810054..7b9e5860f 100644
--- a/src/docs/src/config/auth.rst
+++ b/src/docs/src/config/auth.rst
@@ -196,14 +196,22 @@ Authentication Configuration
[chttpd_auth]
authentication_redirect = /_utils/session.html
- .. config:option:: hash_algorithms :: Supported hash algorithms for cookie auth
+ .. config:option:: hash_algorithms :: Supported hash algorithms for cookie and \
+ proxy auth
.. versionadded:: 3.3
- Sets the HMAC hash algorithm used for cookie authentication. You can provide a
- comma-separated list of hash algorithms. New cookie sessions or
+ .. note::
+ Until CouchDB version 3.3.1, :ref:`api/auth/proxy` used only the hash
+ algorithm ``sha1`` as validation of
+ :config:option:`X-Auth-CouchDB-Token <chttpd_auth/x_auth_token>`.
+
+ Sets the HMAC hash algorithm used for cookie and proxy authentication. You can
+ provide a comma-separated list of hash algorithms. New cookie sessions or
session updates are calculated with the first hash algorithm. All values in the
- list can be used to decode the cookie session. ::
+ list can be used to decode the cookie session and the token
+ :config:option:`X-Auth-CouchDB-Token <chttpd_auth/x_auth_token>` for
+ :ref:`api/auth/proxy`. ::
[chttpd_auth]
hash_algorithms = sha256, sha