diff --git a/etc/emqx.conf b/etc/emqx.conf index 143f93a8f..38f8bd198 100644 --- a/etc/emqx.conf +++ b/etc/emqx.conf @@ -1521,6 +1521,12 @@ listener.ssl.external.certfile = {{ platform_etc_dir }}/certs/cert.pem ## Value: File listener.ssl.external.cacertfile = {{ platform_etc_dir }}/certs/cacert.pem +## Wheter to enable OCSP for the listener. +## +## Value: boolean +## Default: false +## listener.ssl.external.enable_ocsp = true + ## URL for the OCSP responder to check the server certificate against. ## ## Value: String diff --git a/priv/emqx.schema b/priv/emqx.schema index 5c4ed24e2..ffc5130a2 100644 --- a/priv/emqx.schema +++ b/priv/emqx.schema @@ -1679,6 +1679,11 @@ end}. {datatype, {duration, ms}} ]}. +{mapping, "listener.ssl.$name.enable_ocsp", "emqx.listeners", [ + {default, false}, + {datatype, {enum, [true, false]}} +]}. + {mapping, "listener.ssl.$name.ocsp_responder_url", "emqx.listeners", [ {datatype, string} ]}. @@ -2237,6 +2242,7 @@ end}. {supported_subprotocols, string:tokens(cuttlefish:conf_get(Prefix ++ ".supported_subprotocols", Conf, ""), ", ")}, {peer_cert_as_username, cuttlefish:conf_get(Prefix ++ ".peer_cert_as_username", Conf, undefined)}, {peer_cert_as_clientid, cuttlefish:conf_get(Prefix ++ ".peer_cert_as_clientid", Conf, undefined)}, + {ocsp_enabled, cuttlefish:conf_get(Prefix ++ ".enable_ocsp", Conf, undefined)}, {ocsp_responder_url, cuttlefish:conf_get(Prefix ++ ".ocsp_responder_url", Conf, undefined)}, {ocsp_issuer_pem, cuttlefish:conf_get(Prefix ++ ".ocsp_issuer_pem", Conf, undefined)}, {ocsp_refresh_interval, cuttlefish:conf_get(Prefix ++ ".ocsp_refresh_interval", Conf, undefined)}, diff --git a/src/emqx_ocsp_cache.erl b/src/emqx_ocsp_cache.erl index 9246f6df9..281614edb 100644 --- a/src/emqx_ocsp_cache.erl +++ b/src/emqx_ocsp_cache.erl @@ -48,6 +48,11 @@ -define(CALL_TIMEOUT, 20_000). -define(RETRY_TIMEOUT, 5_000). -define(REFRESH_TIMER(LID), {refresh_timer, LID}). +-ifdef(TEST). +-define(MIN_REFRESH_INTERVAL, timer:seconds(5)). +-else. +-define(MIN_REFRESH_INTERVAL, timer:minutes(1)). +-endif. %%-------------------------------------------------------------------- %% API @@ -93,10 +98,10 @@ inject_sni_fun(Listener = #{proto := Proto, name := Name, opts := Options0}) -> %% because otherwise an anonymous function will end up in %% `app.*.config'... ListenerID = emqx_listeners:identifier(Listener), - case proplists:get_value(ocsp_responder_url, Options0, undefined) of - undefined -> + case proplists:get_bool(ocsp_enabled, Options0) of + false -> Options0; - _URL -> + true -> SSLOpts0 = proplists:get_value(ssl_options, Options0, []), SNIFun = fun(SN) -> emqx_ocsp_cache:sni_fun(SN, ListenerID) end, Options1 = proplists:delete(ssl_options, Options0), @@ -142,8 +147,7 @@ handle_call({register_listener, ListenerID}, _From, State0) -> ?LOG(debug, "registering ocsp cache for ~p", [ListenerID]), #{opts := Opts} = emqx_listeners:find_by_id(ListenerID), RefreshInterval0 = proplists:get_value(ocsp_refresh_interval, Opts), - MinimumRefrshInterval = timer:minutes(1), - RefreshInterval = max(RefreshInterval0, MinimumRefrshInterval), + RefreshInterval = max(RefreshInterval0, ?MIN_REFRESH_INTERVAL), State = State0#{{refresh_interval, ListenerID} => RefreshInterval}, {reply, ok, ensure_timer(ListenerID, State, 0)}; handle_call(Call, _From, State) -> @@ -177,7 +181,8 @@ code_change(_Vsn, State, _Extra) -> ListenersToPatch = lists:filter( fun(#{opts := Opts}) -> - undefined =/= proplists:get_value(ocsp_responder_url, Opts) + undefined =/= proplists:get_value(ocsp_responder_url, Opts) andalso + false =/= proplists:get_bool(ocsp_enabled, Opts) end, emqx:get_env(listeners, [])), PatchedListeners = [L#{opts => ?MODULE:inject_sni_fun(L)} || L <- ListenersToPatch], diff --git a/test/emqx_ocsp_cache_SUITE.erl b/test/emqx_ocsp_cache_SUITE.erl index d5d52718d..ac2fd2b90 100644 --- a/test/emqx_ocsp_cache_SUITE.erl +++ b/test/emqx_ocsp_cache_SUITE.erl @@ -96,10 +96,10 @@ init_per_testcase(t_openssl_client, Config) -> , {cacertfile, CACert} ]), Opts1 = proplists:delete(ssl_options, Opts0), - Opts2 = [ {ocsp_responder_url, "http://127.0.0.1:9877"} - , {ocsp_issuer_pem, IssuerPem} - , {ssl_options, SSLOpts2} - | Opts1], + Opts2 = emqx_misc:merge_opts(Opts1, [ {ocsp_enabled, true} + , {ocsp_responder_url, "http://127.0.0.1:9877"} + , {ocsp_issuer_pem, IssuerPem} + , {ssl_options, SSLOpts2}]), Listeners = [ SSLListener0#{opts => Opts2} | Listeners1], application:set_env(emqx, listeners, Listeners), @@ -109,7 +109,18 @@ init_per_testcase(t_openssl_client, Config) -> end, OCSPResponderPort = spawn_openssl_ocsp_responder(Config), {os_pid, OCSPOSPid} = erlang:port_info(OCSPResponderPort, os_pid), - ensure_port_open(9877), + %%%%%%%% Warning!!! + %% Apparently, openssl 3.0.7 introduced a bug in the responder + %% that makes it hang forever if one probes the port with + %% `gen_tcp:open' / `gen_tcp:close'... Comment this out if + %% openssl gets updated in CI or in your local machine. + case openssl_version() of + "3." ++ _ -> + %% hope that the responder has started... + ok; + _ -> + ensure_port_open(9877) + end, ct:sleep(1_000), emqx_ct_helpers:start_apps([], Handler), ct:sleep(1_000), @@ -134,6 +145,7 @@ init_per_testcase(_TestCase, Config) -> , name => "test_ocsp" , opts => [ {ssl_options, [{certfile, filename:join(DataDir, "server.pem")}]} + , {ocsp_enabled, true} , {ocsp_responder_url, "http://localhost:9877"} , {ocsp_issuer_pem, filename:join(DataDir, "ocsp-issuer.pem")} @@ -291,6 +303,11 @@ get_sni_fun(ListenerID) -> SSLOpts = proplists:get_value(ssl_options, Opts), proplists:get_value(sni_fun, SSLOpts). +openssl_version() -> + "OpenSSL " ++ Res = os:cmd("openssl version"), + {match, [Version]} = re:run(Res, "^([^ ]+)", [{capture, first, list}]), + Version. + %%-------------------------------------------------------------------- %% Test cases %%-------------------------------------------------------------------- @@ -417,7 +434,7 @@ t_refresh_periodically(_Config) -> false end, _NEvents = 2, - _Timeout = 5_000), + _Timeout = 10_000), ok = emqx_ocsp_cache:register_listener(ListenerID), ?assertMatch({ok, [_, _]}, snabbkaffe:receive_events(SubRef)), assert_http_get(2),