summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorMagnus Ahltorp <map@kth.se>2015-02-26 16:50:41 +0100
committerMagnus Ahltorp <map@kth.se>2015-02-27 14:08:08 +0100
commit662ea802f40062d6f095bdeea61e69d7b665de25 (patch)
tree8e8e9d232bdf506bbac9c30663a2b6509654b3dd /src
parentba2547e910703ce71a4e8feba983734bc25982e3 (diff)
Added authentication
Diffstat (limited to 'src')
-rw-r--r--src/http_auth.erl138
-rw-r--r--src/plop.erl44
-rw-r--r--src/plop_app.erl1
3 files changed, 177 insertions, 6 deletions
diff --git a/src/http_auth.erl b/src/http_auth.erl
new file mode 100644
index 0000000..6a076fa
--- /dev/null
+++ b/src/http_auth.erl
@@ -0,0 +1,138 @@
+%%% Copyright (c) 2014, NORDUnet A/S.
+%%% See LICENSE for licensing information.
+
+-module(http_auth).
+-export([verify_auth/4, create_auth/3, init_key_table/0]).
+
+-define(KEY_TABLE, http_auth_keys).
+
+init_key_table() ->
+ case ets:info(?KEY_TABLE) of
+ undefined ->
+ ok;
+ _ ->
+ ets:delete(?KEY_TABLE)
+ end,
+ ets:new(?KEY_TABLE, [set, public, named_table]),
+ read_key_table().
+
+read_key_table() ->
+ PublickeyDir = application:get_env(plop, publickey_path, none),
+ ServersACL = application:get_env(plop, allowed_servers, []),
+ ClientsACL = application:get_env(plop, allowed_clients, []),
+ Keys = sets:from_list(
+ lists:flatmap(fun ({_, Keys}) ->
+ case Keys of
+ noauth ->
+ [];
+ _ when is_list(Keys) ->
+ Keys
+ end
+ end, ServersACL ++ ClientsACL)),
+ lists:foreach(
+ fun (KeyName) ->
+ Key = sign:read_keyfile_ec(PublickeyDir ++ "/" ++
+ KeyName ++ ".pem"),
+ true = ets:insert(?KEY_TABLE, {KeyName, Key})
+ end, sets:to_list(Keys)),
+ {_OwnKeyName, OwnKeyFile} = application:get_env(plop, own_key, none),
+ OwnKey = sign:read_keyfile_ec(OwnKeyFile),
+ true = ets:insert(?KEY_TABLE, {own_key, OwnKey}).
+
+
+own_key() ->
+ {KeyName, _KeyFile} = application:get_env(plop, own_key, none),
+ [{_, Key}] = ets:lookup(?KEY_TABLE, own_key),
+ {Key, KeyName}.
+
+lookup_publickey(nokey) ->
+ nokey;
+lookup_publickey(KeyName) ->
+ case ets:lookup(?KEY_TABLE, KeyName) of
+ [{_, Key}] ->
+ Key;
+ [] ->
+ failure
+ end.
+
+parse_option(S) ->
+ parse_option(S, []).
+parse_option([], Key) ->
+ {lists:reverse(Key), []};
+parse_option([$= | Rest], Key) ->
+ {lists:reverse(Key), Rest};
+parse_option([C | Rest], Key) ->
+ parse_option(Rest, [C | Key]).
+
+
+sign(PrivKey, Method, Path, Data) ->
+ public_key:sign(iolist_to_binary([Method, 0, Path, 0, Data]), sha256, PrivKey).
+
+verify(Signature, PublicKey, Method, Path, Data) ->
+ public_key:verify(iolist_to_binary([Method, 0, Path, 0, Data]), sha256, Signature, PublicKey).
+
+check_acl(Method, KeyName, Path) ->
+ EnvVarName = case Method of
+ "REPLY" ->
+ allowed_servers;
+ _ ->
+ allowed_clients
+ end,
+ ACL = application:get_env(plop, EnvVarName, []),
+ lager:debug("ACL: ~p", [ACL]),
+ case lists:keyfind(Path, 1, ACL) of
+ {_, noauth} ->
+ lager:debug("Anonymous access allowed"),
+ success;
+ {_, AllowedKeys} when is_list(AllowedKeys) ->
+ lager:debug("Checking key ~p, allowed keys: ~p", [KeyName, AllowedKeys]),
+ case lists:member(KeyName, AllowedKeys) of
+ true ->
+ success;
+ false ->
+ failure
+ end;
+ false ->
+ lager:debug("No allowed keys found for: ~p", [Path]),
+ failure
+ end.
+
+verify_auth(undefined, Method, Path, _Data) ->
+ case check_acl(Method, noauth, Path) of
+ success ->
+ noauth;
+ Error ->
+ lager:info("anonymous access not allowed for path ~p", [Path]),
+ Error
+ end;
+verify_auth(AuthHeader, Method, Path, Data) ->
+ [AuthTokenBase64 | OptionsRaw] = string:tokens(AuthHeader, ";"),
+ AuthToken = base64:decode(AuthTokenBase64),
+ Options = [parse_option(E) || E <- OptionsRaw],
+ KeyName = case lists:keyfind("key", 1, Options) of
+ {_, Value} ->
+ Value;
+ false ->
+ nokey
+ end,
+ AuthSuccess = case lookup_publickey(KeyName) of
+ nokey ->
+ false;
+ failure ->
+ lager:info("key name ~p could not be found", [KeyName]),
+ false;
+ Key ->
+ verify(AuthToken, Key, Method, Path, Data)
+ end,
+ case AuthSuccess of
+ true ->
+ check_acl(Method, KeyName, Path);
+ _ ->
+ lager:info("authentication token ~p was not valid for key name ~p: ~p ~p ~p", [mochihex:to_hex(AuthToken), KeyName, Method, Path, Data]),
+ failure
+ end.
+
+create_auth(Method, Path, Data) ->
+ {Key, KeyName} = own_key(),
+ AuthToken = sign(Key, Method, Path, Data),
+ base64:encode_to_string(AuthToken) ++ ";key=" ++ KeyName.
diff --git a/src/plop.erl b/src/plop.erl
index f90d287..cfca343 100644
--- a/src/plop.erl
+++ b/src/plop.erl
@@ -194,6 +194,16 @@ storage_nodes_quorum() ->
{ok, Value} = application:get_env(plop, storage_nodes_quorum),
Value.
+add_auth(Method, Path, Headers, Data) ->
+ AuthHeader = http_auth:create_auth(Method, Path, Data),
+ lager:debug("sent auth header: ~p", [AuthHeader]),
+ [{"X-Catlfish-Auth", AuthHeader} | Headers].
+
+get_auth_header(Headers) ->
+ Result = binary_to_list(hackney_headers:get_value("X-Catlfish-Auth", Headers)),
+ lager:debug("received auth header: ~p", [Result]),
+ Result.
+
send_http_request(TreeLeafHash, URL, Headers, RequestBody) ->
ParentPid = self(),
RequestId = make_ref(),
@@ -202,17 +212,39 @@ send_http_request(TreeLeafHash, URL, Headers, RequestBody) ->
Starttime = os:timestamp(),
ParsedURL = hackney_url:parse_url(URL),
#hackney_url{path = Path} = ParsedURL,
- lager:debug("leafhash ~s: sending http request to ~p", [mochihex:to_hex(TreeLeafHash), URL]),
+ lager:debug("leafhash ~s: sending http request to ~p",
+ [mochihex:to_hex(TreeLeafHash), URL]),
{ok, ConnRef} = hackney:connect(ParsedURL, [{ssl_options, [{cacertfile, CACertFile}]}]),
- lager:debug("leafhash ~s: connected to ~p", [mochihex:to_hex(TreeLeafHash), URL]),
- {ok, StatusCode, RespHeaders, ClientRef} = hackney:send_request(ConnRef, {post, Path, Headers, RequestBody}),
- lager:debug("leafhash ~s: received headers for ~p", [mochihex:to_hex(TreeLeafHash), URL]),
+ lager:debug("leafhash ~s: connected to ~p",
+ [mochihex:to_hex(TreeLeafHash), URL]),
+ {ok, StatusCode, RespHeaders, ClientRef} =
+ hackney:send_request(ConnRef,
+ {post, Path,
+ add_auth("POST", Path, Headers,
+ RequestBody),
+ RequestBody}),
+ lager:debug("leafhash ~s: received headers for ~p: ~p",
+ [mochihex:to_hex(TreeLeafHash), URL, RespHeaders]),
{ok, Body} = hackney:body(ClientRef),
Stoptime = os:timestamp(),
hackney:close(ClientRef),
- lager:debug("leafhash ~s: received body for ~p: time ~p", [mochihex:to_hex(TreeLeafHash), URL, timer:now_diff(Stoptime, Starttime)]),
+ lager:debug("leafhash ~s: received body for ~p: time ~p",
+ [mochihex:to_hex(TreeLeafHash), URL, timer:now_diff(Stoptime, Starttime)]),
StatusLine = {none, StatusCode, none},
- ParentPid ! {http, {RequestId, {StatusLine, RespHeaders, Body}}}
+ AuthHeader = get_auth_header(hackney_headers:new(RespHeaders)),
+ case http_auth:verify_auth(AuthHeader, "REPLY",
+ binary_to_list(Path), Body) of
+ failure ->
+ lager:debug("auth check failed"),
+ drop;
+ success ->
+ lager:debug("auth check succeeded"),
+ ParentPid ! {http, {RequestId,
+ {StatusLine, RespHeaders, Body}}};
+ noauth ->
+ lager:debug("no auth"),
+ drop
+ end
end),
RequestId.
diff --git a/src/plop_app.erl b/src/plop_app.erl
index 9cb5558..5aae9d9 100644
--- a/src/plop_app.erl
+++ b/src/plop_app.erl
@@ -7,6 +7,7 @@
start(normal, Args) ->
hackney:start(),
+ http_auth:init_key_table(),
plop_sup:start_link(Args).
stop(_State) ->