diff options
author | Ronny Berndt <ronny@apache.org> | 2023-02-23 09:10:55 +0100 |
---|---|---|
committer | Nick Vatamaniuc <nickva@users.noreply.github.com> | 2023-04-13 15:50:18 -0400 |
commit | 05659effc7c6f080dd90e908ea693928f4f6722b (patch) | |
tree | aa902afecda2783d744a6afff76a3bbbb03c54e4 | |
parent | 87ceb1b01fa09974f008bddda868b264d9f31249 (diff) | |
download | couchdb-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.erl | 87 | ||||
-rw-r--r-- | src/couch/src/couch_httpd_auth.erl | 15 | ||||
-rw-r--r-- | src/docs/src/api/server/authn.rst | 16 | ||||
-rw-r--r-- | src/docs/src/config/auth.rst | 16 |
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 |