Fold trust_store_http into this repo

It does not have any external users and we'd prefer
to evolve it together with the RabbitMQ tier 1 plugin that
depends on it.

Pair: @pjk25
This commit is contained in:
Michael Klishin 2022-07-28 15:14:32 +04:00
parent 9c33a470c7
commit 89fa75ee1b
No known key found for this signature in database
GPG Key ID: 8ADA141E1AD87C94
14 changed files with 317 additions and 1 deletions

1
.gitignore vendored
View File

@ -54,6 +54,7 @@
!/deps/rabbitmq_web_mqtt_examples/
!/deps/rabbitmq_web_stomp/
!/deps/rabbitmq_web_stomp_examples/
!/deps/trust_store_http/
/escript/
/escript.lock
/plugins/

View File

@ -88,7 +88,7 @@ suites = [
flaky = True,
runtime_deps = [
"@ct_helper//:erlang_app",
"@trust_store_http//:erlang_app",
"//deps/trust_store_http:erlang_app",
],
),
]

22
deps/trust_store_http/.gitignore vendored Normal file
View File

@ -0,0 +1,22 @@
*~
.sw?
.*.sw?
*.beam
*.coverdata
/.erlang.mk/
/cover/
/deps/
/doc/
/ebin/
/escript/
/git-revisions.txt
/logs/
/plugins/
/rebar.config
/rebar.lock
/_rel/
/sbin/
/test/ct.cover.spec
/xrefr
/trust_store_http.d

22
deps/trust_store_http/Makefile vendored Normal file
View File

@ -0,0 +1,22 @@
PROJECT = trust_store_http
PROJECT_DESCRIPTION = Trust store HTTP server
PROJECT_VERSION = 1.0.0
define PROJECT_APP_EXTRA_KEYS
{broker_version_requirements, []}
endef
LOCAL_DEPS = ssl
DEPS = cowboy jsx
DEP_EARLY_PLUGINS = rabbit_common/mk/rabbitmq-early-plugin.mk
DEP_PLUGINS = rabbit_common/mk/rabbitmq-plugin.mk
# FIXME: Use erlang.mk patched for RabbitMQ, while waiting for PRs to be
# reviewed and merged.
ERLANG_MK_REPO = https://github.com/rabbitmq/erlang.mk.git
ERLANG_MK_COMMIT = rabbitmq-tmp
include ../../rabbitmq-components.mk
include ../../erlang.mk

71
deps/trust_store_http/README.md vendored Normal file
View File

@ -0,0 +1,71 @@
# Example Trust Store HTTP Server for RabbitMQ
This tiny HTTP server serves CA certificates from a user-specified local directory.
It is meant to be used with [RabbitMQ trust store plugin](https://github.com/rabbitmq/rabbitmq-trust-store)
in its test suite and as an example.
## Endpoints
* `/`: serves a list of certificates in JSON. The format is `{"certificates":[{"id": <id>, "path": <path>}, ...]}`
* `/certs/<file_name>`: access for PEM encoded certificate files
* `/invlid`: serves invalid JSON, to be used in integration tests
```
<id> = <file_name>:<file_modification_date>
<path> = /certs/<file_name>
<file_name> = name of a PEM file in the listed directory
```
## Usage
To rebuild and run a release (requires Erlang to be installed):
```
gmake run CERT_DIR="/my/cacert/directory" PORT=8080
```
To run from the pre-built escript (requires Erlang to be installed):
```
gmake
CERT_DIR="/my/cacert/directory" PORT=8080 ./_rel/trust_store_http_release/bin/trust_store_http_release console
```
## HTTPS
To start an HTTPS server, you should provide ssl options. It can be done via
Erlang `.config` file format:
```
[{trust_store_http,
[{ssl_options, [{cacertfile,"/path/to/testca/cacert.pem"},
{certfile,"/path/to/server/cert.pem"},
{keyfile,"/path/to/server/key.pem"},
{verify,verify_peer},
{fail_if_no_peer_cert,false}]}]}]
```
This configuration can be added to `rel/sys.config`
if you're running the application from source `make run`
Or it can be specified as an environment variable:
```
CERT_DIR="/my/cacert/directory" PORT=8443 CONFIG_FILE=my_config.config ./_rel/trust_store_http_release/bin/trust_store_http_release console
```
Port and directory can be also set via config file:
```
[{trust_store_http,
[{directory, "/tmp/certs"},
{port, 8081},
{ssl_options, [{cacertfile,"/path/to/testca/cacert.pem"},
{certfile,"/path/to/server/cert.pem"},
{keyfile,"/path/to/server/key.pem"},
{verify,verify_peer},
{fail_if_no_peer_cert,false}]}]}]
```

2
deps/trust_store_http/rel/sys.config vendored Normal file
View File

@ -0,0 +1,2 @@
[
].

3
deps/trust_store_http/rel/vm.args vendored Normal file
View File

@ -0,0 +1,3 @@
-name trust_store_http@127.0.0.1
-setcookie trust_store_http
-heart

4
deps/trust_store_http/relx.config vendored Normal file
View File

@ -0,0 +1,4 @@
{release, {trust_store_http_release, "1"}, [trust_store_http, sasl, runtime_tools]}.
{extended_start_script, true}.
{sys_config, "rel/sys.config"}.
{vm_args, "rel/vm.args"}.

View File

@ -0,0 +1,12 @@
-module(trust_store_http).
-export([main/1]).
main([]) ->
io:format("~nStarting trust store server ~n", []),
application:ensure_all_started(trust_store_http),
io:format("~nTrust store server started on port ~p ~n",
[application:get_env(trust_store_http, port, undefined)]),
user_drv:start(),
timer:sleep(infinity).

View File

@ -0,0 +1,81 @@
-module(trust_store_http_app).
-behaviour(application).
-export([start/2]).
-export([stop/1]).
start(_Type, _Args) ->
init_config(),
Directory = get_directory(),
Port = get_port(),
Dispatch = cowboy_router:compile([
{'_', [
{"/", trust_store_list_handler, []},
{"/invalid", trust_store_invalid_handler, []},
{"/certs/[...]", cowboy_static,
{dir, Directory, [{mimetypes, {<<"text">>, <<"html">>, []}}]}}]}
]),
case get_ssl_options() of
undefined -> start_http(Dispatch, Port);
SslOptions -> start_https(Dispatch, Port, SslOptions)
end,
trust_store_http_sup:start_link().
stop(_State) ->
ok.
start_http(Dispatch, undefined) ->
start_http(Dispatch, 8080);
start_http(Dispatch, Port) ->
{ok, _} = cowboy:start_clear(trust_store_http_listener,
[{port, Port}],
#{env => #{dispatch => Dispatch}}).
start_https(Dispatch, undefined, SslOptions) ->
start_https(Dispatch, 8443, SslOptions);
start_https(Dispatch, Port, SslOptions) ->
{ok, _} = cowboy:start_tls(trust_store_https_listener,
[{port, Port}] ++ SslOptions,
#{env => #{dispatch => Dispatch}}).
get_directory() ->
Dir = case os:getenv("CERT_DIR") of
false ->
case application:get_env(trust_store_http, directory, undefined) of
undefined ->
{ok, CurrentDir} = file:get_cwd(),
CurrentDir;
AppDir -> AppDir
end;
EnvDir -> EnvDir
end,
application:set_env(trust_store_http, directory, Dir),
Dir.
get_ssl_options() ->
application:get_env(trust_store_http, ssl_options, undefined).
get_port() ->
case os:getenv("PORT") of
false ->
application:get_env(trust_store_http, port, undefined);
PortEnv ->
Port = list_to_integer(PortEnv),
application:set_env(trust_store_http, port, Port),
Port
end.
init_config() ->
case os:getenv("CONFIG_FILE") of
false -> ok;
ConfigFile ->
case file:consult(ConfigFile) of
{ok, [Config]} ->
lists:foreach(fun({App, Conf}) ->
[application:set_env(App, Key, Val)
|| {Key, Val} <- Conf]
end,
Config);
_ -> ok
end
end.

View File

@ -0,0 +1,17 @@
-module(trust_store_http_sup).
-behaviour(supervisor).
-export([start_link/0]).
-export([init/1]).
start_link() ->
supervisor:start_link({local, ?MODULE}, ?MODULE, []).
init([]) ->
Specs = [],
Flags = #{
strategy => one_for_one,
intensity => 1,
period => 5
},
{ok, {Flags, Specs}}.

View File

@ -0,0 +1,13 @@
-module(trust_store_invalid_handler).
-behaviour(cowboy_handler).
-export([init/2]).
-export([terminate/3]).
init(Req, State) ->
%% serves some invalid JSON
Req2 = cowboy_req:reply(200, #{<<"content-type">> => <<"application/json">>}, <<"{_1}}1}">>, Req),
{ok, Req2, State}.
terminate(_Reason, _Req, _State) ->
ok.

View File

@ -0,0 +1,68 @@
-module(trust_store_list_handler).
-behaviour(cowboy_handler).
-include_lib("kernel/include/file.hrl").
-export([init/2]).
-export([terminate/3]).
init(Req, State) ->
{ok, Directory} = application:get_env(trust_store_http, directory),
case list_files(Directory) of
{ok, Files} -> respond(Files, Req, State);
{error, Err} -> respond_error(Err, Req, State)
end.
terminate(_Reason, _Req, _State) ->
ok.
respond(Files, Req, State) ->
ResponseBody = json_encode(Files),
Headers = #{<<"content-type">> => <<"application/json">>},
Req2 = cowboy_req:reply(200, Headers, ResponseBody, Req),
{ok, Req2, State}.
respond_error(Reason, Req, State) ->
Error = io_lib:format("Error listing certificates ~p", [Reason]),
logger:log(error, "~s", [Error]),
Req2 = cowboy_req:reply(500, [], iolist_to_binary(Error), Req),
{ok, Req2, State}.
json_encode(Files) ->
Map = #{certificates => [ #{id => cert_id(FileName, FileDate, FileHash),
path => cert_path(FileName)}
|| {FileName, FileDate, FileHash} <- Files ]},
jsx:encode(Map).
cert_id(FileName, FileDate, FileHash) ->
iolist_to_binary(io_lib:format("~s:~p:~p", [FileName, FileDate, FileHash])).
cert_path(FileName) ->
iolist_to_binary(["/certs/", FileName]).
-spec list_files(string()) -> [{string(), file:date_time(), integer()}].
list_files(Directory) ->
case file:list_dir(Directory) of
{ok, FileNames} ->
PemFiles = [ FileName || FileName <- FileNames,
filename:extension(FileName) == ".pem" ],
{ok, lists:map(
fun(FileName) ->
FullName = filename:join(Directory, FileName),
{ok, Mtime} = modification_time(FullName, posix),
Hash = file_content_hash(FullName),
{FileName, Mtime, Hash}
end,
PemFiles)};
{error, Err} -> {error, Err}
end.
modification_time(FileName, Type) ->
case file:read_file_info(FileName, [{time, Type}]) of
{ok, #file_info{mtime = Mtime}} -> {ok, Mtime};
{error, Reason} -> {error, Reason}
end.
file_content_hash(Path) ->
{ok, Data} = file:read_file(Path),
erlang:phash2(Data).