You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@couchdb.apache.org by ro...@apache.org on 2023/02/23 08:11:03 UTC

[couchdb] branch main updated: Upgrade hash algorithm for proxy auth (#4438)

This is an automated email from the ASF dual-hosted git repository.

ronny pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/couchdb.git


The following commit(s) were added to refs/heads/main by this push:
     new ae3d2971e Upgrade hash algorithm for proxy auth (#4438)
ae3d2971e is described below

commit ae3d2971e0cdca72726703c2f6b1962f96e61975
Author: Ronny Berndt <ro...@apache.org>
AuthorDate: Thu Feb 23 09:10:55 2023 +0100

    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.
---
 src/chttpd/test/eunit/chttpd_auth_tests.erl | 87 +++++++++++++++++++++++++++++
 src/couch/src/couch_httpd_auth.erl          | 15 +++--
 src/docs/src/api/server/authn.rst           | 16 +++---
 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 89203bf4f..652fb3996 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 982c931fd..41f77e176 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 984fa05d5..1d34aab52 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