You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@couchdb.apache.org by va...@apache.org on 2023/02/24 07:31:29 UTC

[couchdb] 01/01: Use a persistent term for features

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

vatamane pushed a commit to branch persistent-term-features
in repository https://gitbox.apache.org/repos/asf/couchdb.git

commit 955069fa651fc9b3a82a849584723cfa1df29575
Author: Nick Vatamaniuc <va...@apache.org>
AuthorDate: Fri Feb 24 02:28:54 2023 -0500

    Use a persistent term for features
    
    First use is in switching between fips / non-fips mode for md5 hashing at
    runtime to avoid creating two build variants.
    
    Issue: #4442
---
 src/config/src/config.erl                | 29 +++++++++---------
 src/config/test/config_tests.erl         |  6 ++++
 src/couch/src/couch_hash.erl             | 20 +++++++++---
 src/couch/test/eunit/couch_hash_test.erl | 52 ++++++++++++++++++++++++++++++++
 4 files changed, 88 insertions(+), 19 deletions(-)

diff --git a/src/config/src/config.erl b/src/config/src/config.erl
index 7c38ff58f..658fc67d3 100644
--- a/src/config/src/config.erl
+++ b/src/config/src/config.erl
@@ -31,7 +31,7 @@
 -export([get_float/3, set_float/3, set_float/4]).
 -export([get_boolean/3, set_boolean/3, set_boolean/4]).
 
--export([features/0, enable_feature/1, disable_feature/1]).
+-export([features/0, enable_feature/1, disable_feature/1, is_enabled/1]).
 
 -export([listen_for_changes/2]).
 -export([subscribe_for_changes/1]).
@@ -42,7 +42,7 @@
 
 -export([is_sensitive/2]).
 
--define(FEATURES, "features").
+-define(FEATURES, features).
 
 -define(TIMEOUT, 30000).
 -define(INVALID_SECTION, <<"Invalid configuration section">>).
@@ -212,23 +212,22 @@ delete(Section, Key, Persist, Reason) when is_list(Section), is_list(Key) ->
     ).
 
 features() ->
-    application:get_env(config, enabled_features, []).
+    Map = persistent_term:get({?MODULE, ?FEATURES}, #{}),
+    lists:sort(maps:keys(Map)).
 
 enable_feature(Feature) when is_atom(Feature) ->
-    application:set_env(
-        config,
-        enabled_features,
-        lists:usort([Feature | features()]),
-        [{persistent, true}]
-    ).
+    Map = persistent_term:get({?MODULE, ?FEATURES}, #{}),
+    Map1 = Map#{Feature => true},
+    persistent_term:put({?MODULE, ?FEATURES}, Map1).
 
 disable_feature(Feature) when is_atom(Feature) ->
-    application:set_env(
-        config,
-        enabled_features,
-        features() -- [Feature],
-        [{persistent, true}]
-    ).
+    Map = persistent_term:get({?MODULE, ?FEATURES}, #{}),
+    Map1 = maps:remove(Feature, Map),
+    persistent_term:put({?MODULE, ?FEATURES}, Map1).
+
+is_enabled(Feature) when is_atom(Feature) ->
+    Map = persistent_term:get({?MODULE, ?FEATURES}, #{}),
+    maps:get(Feature, Map, false).
 
 listen_for_changes(CallbackModule, InitialState) ->
     config_listener_mon:subscribe(CallbackModule, InitialState).
diff --git a/src/config/test/config_tests.erl b/src/config/test/config_tests.erl
index 3d3ee9c94..90d430a87 100644
--- a/src/config/test/config_tests.erl
+++ b/src/config/test/config_tests.erl
@@ -651,11 +651,14 @@ should_enable_features() ->
 
     ?assertEqual(ok, config:enable_feature(snek)),
     ?assertEqual([snek], config:features()),
+    ?assert(config:is_enabled(snek)),
 
     ?assertEqual(ok, config:enable_feature(snek)),
     ?assertEqual([snek], config:features()),
 
     ?assertEqual(ok, config:enable_feature(dogo)),
+    ?assert(config:is_enabled(dogo)),
+    ?assert(config:is_enabled(snek)),
     ?assertEqual([dogo, snek], config:features()).
 
 should_disable_features() ->
@@ -666,9 +669,11 @@ should_disable_features() ->
     ?assertEqual([snek], config:features()),
 
     ?assertEqual(ok, config:disable_feature(snek)),
+    ?assertNot(config:is_enabled(snek)),
     ?assertEqual([], config:features()),
 
     ?assertEqual(ok, config:disable_feature(snek)),
+    ?assertNot(config:is_enabled(snek)),
     ?assertEqual([], config:features()).
 
 should_keep_features_on_config_restart() ->
@@ -678,6 +683,7 @@ should_keep_features_on_config_restart() ->
     config:enable_feature(snek),
     ?assertEqual([snek], config:features()),
     with_process_restart(config),
+    ?assert(config:is_enabled(snek)),
     ?assertEqual([snek], config:features()).
 
 should_notify_on_config_reload(Subscription, {_Apps, Pid}) ->
diff --git a/src/couch/src/couch_hash.erl b/src/couch/src/couch_hash.erl
index 842b37423..2eb123603 100644
--- a/src/couch/src/couch_hash.erl
+++ b/src/couch/src/couch_hash.erl
@@ -31,15 +31,27 @@ md5_hash_update(Context, Data) ->
 -else.
 
 md5_hash(Data) ->
-    crypto:hash(md5, Data).
+    case config:is_enabled(fips_mode) of
+        true -> erlang:md5(Data);
+        false -> crypto:hash(md5, Data)
+    end.
 
 md5_hash_final(Context) ->
-    crypto:hash_final(Context).
+    case config:is_enabled(fips_mode) of
+        true -> erlang:md5_final(Context);
+        false -> crypto:hash_final(Context)
+    end.
 
 md5_hash_init() ->
-    crypto:hash_init(md5).
+    case config:is_enabled(fips_mode) of
+        true -> erlang:md5_init();
+        false -> crypto:hash_init(md5)
+    end.
 
 md5_hash_update(Context, Data) ->
-    crypto:hash_update(Context, Data).
+    case config:is_enabled(fips_mode) of
+        true -> erlang:md5_update(Context, Data);
+        false -> crypto:hash_update(Context, Data)
+    end.
 
 -endif.
diff --git a/src/couch/test/eunit/couch_hash_test.erl b/src/couch/test/eunit/couch_hash_test.erl
new file mode 100644
index 000000000..a9e062cb4
--- /dev/null
+++ b/src/couch/test/eunit/couch_hash_test.erl
@@ -0,0 +1,52 @@
+% Licensed under the Apache License, Version 2.0 (the "License"); you may not
+% use this file except in compliance with the License. You may obtain a copy of
+% the License at
+%
+%   http://www.apache.org/licenses/LICENSE-2.0
+%
+% Unless required by applicable law or agreed to in writing, software
+% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+% License for the specific language governing permissions and limitations under
+% the License.
+
+-module(couch_hash_test).
+
+-include_lib("couch/include/couch_eunit.hrl").
+
+-define(XY_HASH, <<62, 68, 16, 113, 112, 165, 32, 88, 42, 222, 82, 47, 167, 60, 29, 21>>).
+
+couch_hash_test_() ->
+    {
+        foreach,
+        fun setup/0,
+        fun teardown/1,
+        [
+            ?TDEF_FE(t_fips_disabled),
+            ?TDEF_FE(t_fips_enabled)
+        ]
+    }.
+
+setup() ->
+    Ctx = test_util:start_couch([crypto]),
+    config:disable_feature(fips_mode),
+    Ctx.
+
+teardown(Ctx) ->
+    config:disable_feature(fips_mode),
+    test_util:stop_couch(Ctx).
+
+t_fips_disabled(_) ->
+    ?assertEqual(?XY_HASH, couch_hash:md5_hash(<<"xy">>)),
+    H = couch_hash:md5_hash_init(),
+    H1 = couch_hash:md5_hash_update(H, <<"x">>),
+    H2 = couch_hash:md5_hash_update(H1, <<"y">>),
+    ?assertEqual(?XY_HASH, couch_hash:md5_hash_final(H2)).
+
+t_fips_enabled(_) ->
+    config:enable_feature(fips_mode),
+    ?assertEqual(?XY_HASH, couch_hash:md5_hash(<<"xy">>)),
+    H = couch_hash:md5_hash_init(),
+    H1 = couch_hash:md5_hash_update(H, <<"x">>),
+    H2 = couch_hash:md5_hash_update(H1, <<"y">>),
+    ?assertEqual(?XY_HASH, couch_hash:md5_hash_final(H2)).