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:
parent
9c33a470c7
commit
89fa75ee1b
|
@ -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/
|
||||
|
|
|
@ -88,7 +88,7 @@ suites = [
|
|||
flaky = True,
|
||||
runtime_deps = [
|
||||
"@ct_helper//:erlang_app",
|
||||
"@trust_store_http//:erlang_app",
|
||||
"//deps/trust_store_http:erlang_app",
|
||||
],
|
||||
),
|
||||
]
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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}]}]}]
|
||||
```
|
|
@ -0,0 +1,2 @@
|
|||
[
|
||||
].
|
|
@ -0,0 +1,3 @@
|
|||
-name trust_store_http@127.0.0.1
|
||||
-setcookie trust_store_http
|
||||
-heart
|
|
@ -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"}.
|
|
@ -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).
|
|
@ -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.
|
|
@ -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}}.
|
|
@ -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.
|
|
@ -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).
|
Loading…
Reference in New Issue