Skip to content
This repository was archived by the owner on Mar 22, 2024. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 5 additions & 12 deletions apps/cvmfs_gateway/src/cvmfs_fe.erl
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,7 @@

-module(cvmfs_fe).

-export([start_link/1, api_version/0]).

-define(API_VERSION, 1).
-define(API_ROOT, "/api/v" ++ integer_to_list(?API_VERSION)).
-export([start_link/1]).


%%--------------------------------------------------------------------
Expand All @@ -25,10 +22,10 @@ start_link([TcpPort]) ->
%% to a set of handler modules. The handler modules implement the init/2 callback
%% required by Cowboy
Dispatch = cowboy_router:compile([{'_', [
{?API_ROOT, cvmfs_root_handler, []},
{?API_ROOT ++ "/repos/[:id]", cvmfs_repos_handler, []},
{?API_ROOT ++ "/leases/[:token]", cvmfs_leases_handler, []},
{?API_ROOT ++ "/payloads/[:id]", cvmfs_payloads_handler, []}
{cvmfs_version:api_root(), cvmfs_root_handler, []},
{cvmfs_version:api_root() ++ "/repos/[:id]", cvmfs_repos_handler, []},
{cvmfs_version:api_root() ++ "/leases/[:token]", cvmfs_leases_handler, []},
{cvmfs_version:api_root() ++ "/payloads/[:id]", cvmfs_payloads_handler, []}
]}]),
%% Start the HTTP listener process configured with the routing table
cowboy:start_clear(cvmfs_fe,
Expand All @@ -37,7 +34,3 @@ start_link([TcpPort]) ->
idle_timeout => cvmfs_app_util:get_max_lease_time(),
inactivity_timeout => cvmfs_app_util:get_max_lease_time()}).


-spec api_version() -> integer().
api_version() ->
?API_VERSION.
3 changes: 2 additions & 1 deletion apps/cvmfs_gateway/src/cvmfs_gateway.app.src
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@
cvmfs_repos_handler,
cvmfs_root_handler,
cvmfs_gateway_app,
cvmfs_gateway_sup
cvmfs_gateway_sup,
cvmfs_version
]},

{maintainers, []}
Expand Down
36 changes: 25 additions & 11 deletions apps/cvmfs_gateway/src/cvmfs_leases_handler.erl
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ p_handle_new_lease(Req0, State, Uid) ->
#{<<"path">> := Path, <<"api_version">> := ClientApiVersion} ->
Rep = case p_check_hmac(Uid, Data, KeyId, ClientHMAC) of
true ->
p_new_lease(Uid, KeyId, Path, binary_to_integer(ClientApiVersion));
p_new_lease(Uid, KeyId, Path, p_to_integer(ClientApiVersion));
false ->
#{<<"status">> => <<"error">>,
<<"reason">> => <<"invalid_hmac">>}
Expand Down Expand Up @@ -245,14 +245,28 @@ p_check_hmac(Uid, JSONMessage, KeyId, ClientHMAC) ->


p_new_lease(Uid, KeyId, Path, ClientApiVersion) ->
case cvmfs_be:new_lease(Uid, KeyId, Path) of
{ok, Token} ->
#{<<"status">> => <<"ok">>,
<<"session_token">> => Token,
<<"max_api_version">> => integer_to_binary(erlang:min(cvmfs_fe:api_version(),
ClientApiVersion))};
{path_busy, Time} ->
#{<<"status">> => <<"path_busy">>, <<"time_remaining">> => Time};
{error, Reason} ->
#{<<"status">> => <<"error">>, <<"reason">> => atom_to_binary(Reason, latin1)}
case ClientApiVersion >= cvmfs_version:min_api_protocol_version() of
true ->
case cvmfs_be:new_lease(Uid, KeyId, Path) of
{ok, Token} ->
#{<<"status">> => <<"ok">>,
<<"session_token">> => Token,
<<"max_api_version">> => integer_to_binary(erlang:min(cvmfs_version:api_protocol_version(),
ClientApiVersion))};
{path_busy, Time} ->
#{<<"status">> => <<"path_busy">>, <<"time_remaining">> => Time};
{error, Reason} ->
#{<<"status">> => <<"error">>, <<"reason">> => atom_to_binary(Reason, latin1)}
end;
false ->
Msg = "incompatible version: " ++ integer_to_list(ClientApiVersion)
++ ", min version: " ++ integer_to_list(cvmfs_version:min_api_protocol_version()),
#{<<"status">> => <<"error">>, <<"reason">> => list_to_binary(Msg)}
end.


p_to_integer(V) when is_binary(V) ->
binary_to_integer(V);
p_to_integer(V) when is_integer(V) ->
V.

31 changes: 31 additions & 0 deletions apps/cvmfs_gateway/src/cvmfs_version.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
%%%-------------------------------------------------------------------
%%% This file is part of the CernVM File System.
%%%
%%% @doc cvmfs_version
%%%
%%% @end
%%%-------------------------------------------------------------------

-module(cvmfs_version).

-export([api_protocol_version/0, min_api_protocol_version/0, api_root/0]).


-define(API_PROTOCOL_VERSION, 2).
-define(API_ROOT, "/api/v1").


-spec api_protocol_version() -> integer().
api_protocol_version() ->
?API_PROTOCOL_VERSION.


-spec min_api_protocol_version() -> integer().
min_api_protocol_version() ->
?API_PROTOCOL_VERSION.


-spec api_root() -> list().
api_root() ->
?API_ROOT.

70 changes: 34 additions & 36 deletions apps/cvmfs_gateway/test/cvmfs_fe_SUITE.erl
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,6 @@
normal_payload_submission/1,
payload_submission_with_invalid_hmac/1]).

-define(API_ROOT, "/api/v1").

%% Test description

all() ->
Expand Down Expand Up @@ -102,32 +100,32 @@ end_per_testcase(_TestCase, Config) ->
%% Test specifications

check_root(Config) ->
{ok, Body} = p_get(conn_pid(Config), ?API_ROOT),
{ok, Body} = p_get(conn_pid(Config), cvmfs_version:api_root()),
#{<<"resources">> := Resources} = jsx:decode(Body, [return_maps]),
Resources =:= [<<"users">>, <<"repos">>, <<"leases">>, <<"payloads">>].


check_repos(Config) ->
{ok, Body} = p_get(conn_pid(Config), ?API_ROOT ++ "/repos"),
{ok, Body} = p_get(conn_pid(Config), cvmfs_version:api_root() ++ "/repos"),
#{<<"repos">> := Repos} = jsx:decode(Body, [return_maps]),
Repos =:= cvmfs_auth:get_repos().


check_leases(Config) ->
{error, reply_without_body} = p_get(conn_pid(Config), ?API_ROOT ++ "/leases").
{error, reply_without_body} = p_get(conn_pid(Config), cvmfs_version:api_root() ++ "/leases").


create_and_delete_session(Config) ->
RequestBody = jsx:encode(#{<<"path">> => <<"repo1.domain1.org">>,
<<"api_version">> => integer_to_binary(cvmfs_fe:api_version())}),
<<"api_version">> => integer_to_binary(cvmfs_version:api_protocol_version())}),
HMAC = p_make_hmac(RequestBody),
RequestHeaders = p_make_headers(RequestBody, <<"key1">>, HMAC),
{ok, ReplyBody1} = p_post(conn_pid(Config), ?API_ROOT ++ "/leases", RequestHeaders, RequestBody),
{ok, ReplyBody1} = p_post(conn_pid(Config), cvmfs_version:api_root() ++ "/leases", RequestHeaders, RequestBody),
#{<<"session_token">> := Token} = jsx:decode(ReplyBody1, [return_maps]),

HMAC2 = p_make_hmac(Token),
RequestHeaders2 = p_make_headers(<<"key1">>, HMAC2),
{ok, ReplyBody2} = p_delete(conn_pid(Config), ?API_ROOT ++ "/leases/" ++ binary_to_list(Token),
{ok, ReplyBody2} = p_delete(conn_pid(Config), cvmfs_version:api_root() ++ "/leases/" ++ binary_to_list(Token),
RequestHeaders2),
#{<<"status">> := <<"ok">>} = jsx:decode(ReplyBody2, [return_maps]).

Expand All @@ -139,10 +137,10 @@ create_invalid_leases(Config) ->
],
Check = fun({KeyId, Path, Reason}) ->
RequestBody = jsx:encode(#{<<"path">> => Path,
<<"api_version">> => integer_to_binary(cvmfs_fe:api_version())}),
<<"api_version">> => integer_to_binary(cvmfs_version:api_protocol_version())}),
HMAC = p_make_hmac(RequestBody),
RequestHeaders = p_make_headers(RequestBody, KeyId, HMAC),
{ok, ReplyBody} = p_post(conn_pid(Config), ?API_ROOT ++ "/leases", RequestHeaders, RequestBody),
{ok, ReplyBody} = p_post(conn_pid(Config), cvmfs_version:api_root() ++ "/leases", RequestHeaders, RequestBody),
#{<<"status">> := <<"error">>,
<<"reason">> := Reason} = jsx:decode(ReplyBody, [return_maps])
end,
Expand All @@ -151,71 +149,71 @@ create_invalid_leases(Config) ->

busy_path(Config) ->
RequestBody1 = jsx:encode(#{<<"path">> => <<"repo1.domain1.org/dir">>,
<<"api_version">> => integer_to_binary(cvmfs_fe:api_version())}),
<<"api_version">> => integer_to_binary(cvmfs_version:api_protocol_version())}),
HMAC1 = p_make_hmac(RequestBody1),
RequestHeaders1 = p_make_headers(RequestBody1, <<"key1">>, HMAC1),
{ok, ReplyBody1} = p_post(conn_pid(Config), ?API_ROOT ++ "/leases", RequestHeaders1, RequestBody1),
{ok, ReplyBody1} = p_post(conn_pid(Config), cvmfs_version:api_root() ++ "/leases", RequestHeaders1, RequestBody1),
#{<<"session_token">> := Token} = jsx:decode(ReplyBody1, [return_maps]),

RequestBody2 = jsx:encode(#{<<"path">> => <<"repo1.domain1.org/dir/subdir">>,
<<"api_version">> => integer_to_binary(cvmfs_fe:api_version())}),
<<"api_version">> => integer_to_binary(cvmfs_version:api_protocol_version())}),
HMAC2 = p_make_hmac(RequestBody2),
RequestHeaders2 = p_make_headers(RequestBody2, <<"key1">>, HMAC2),
{ok, ReplyBody2} = p_post(conn_pid(Config), ?API_ROOT ++ "/leases", RequestHeaders2, RequestBody2),
{ok, ReplyBody2} = p_post(conn_pid(Config), cvmfs_version:api_root() ++ "/leases", RequestHeaders2, RequestBody2),
#{<<"status">> := <<"path_busy">>} = jsx:decode(ReplyBody2, [return_maps]),

HMAC3 = p_make_hmac(Token),
RequestHeaders3 = p_make_headers(<<"key1">>, HMAC3),
{ok, ReplyBody3} = p_delete(conn_pid(Config), ?API_ROOT ++ "/leases/" ++ binary_to_list(Token),
{ok, ReplyBody3} = p_delete(conn_pid(Config), cvmfs_version:api_root() ++ "/leases/" ++ binary_to_list(Token),
RequestHeaders3),
#{<<"status">> := <<"ok">>} = jsx:decode(ReplyBody3, [return_maps]).


independent_leases(Config) ->
RequestBody1 = jsx:encode(#{<<"path">> => <<"repo1.domain1.org/dir1">>,
<<"api_version">> => integer_to_binary(cvmfs_fe:api_version())}),
<<"api_version">> => integer_to_binary(cvmfs_version:api_protocol_version())}),
HMAC1 = p_make_hmac(RequestBody1),
RequestHeaders1 = p_make_headers(RequestBody1, <<"key1">>, HMAC1),
{ok, ReplyBody1} = p_post(conn_pid(Config), ?API_ROOT ++ "/leases", RequestHeaders1, RequestBody1),
{ok, ReplyBody1} = p_post(conn_pid(Config), cvmfs_version:api_root() ++ "/leases", RequestHeaders1, RequestBody1),
#{<<"session_token">> := Token1} = jsx:decode(ReplyBody1, [return_maps]),

RequestBody2 = jsx:encode(#{<<"path">> => <<"repo1.domain1.org/dir2">>,
<<"api_version">> => integer_to_binary(cvmfs_fe:api_version())}),
<<"api_version">> => integer_to_binary(cvmfs_version:api_protocol_version())}),
HMAC2 = p_make_hmac(RequestBody2),
RequestHeaders2 = p_make_headers(RequestBody2, <<"key1">>, HMAC2),
{ok, ReplyBody2} = p_post(conn_pid(Config), ?API_ROOT ++ "/leases", RequestHeaders2, RequestBody2),
{ok, ReplyBody2} = p_post(conn_pid(Config), cvmfs_version:api_root() ++ "/leases", RequestHeaders2, RequestBody2),
#{<<"session_token">> := Token2} = jsx:decode(ReplyBody2, [return_maps]),

HMAC3 = p_make_hmac(Token1),
RequestHeaders3 = p_make_headers(<<"key1">>, HMAC3),
{ok, ReplyBody3} = p_delete(conn_pid(Config), ?API_ROOT ++ "/leases/" ++ binary_to_list(Token1),
{ok, ReplyBody3} = p_delete(conn_pid(Config), cvmfs_version:api_root() ++ "/leases/" ++ binary_to_list(Token1),
RequestHeaders3),
#{<<"status">> := <<"ok">>} = jsx:decode(ReplyBody3, [return_maps]),

HMAC4 = p_make_hmac(Token2),
RequestHeaders4 = p_make_headers(<<"key1">>, HMAC4),
{ok, ReplyBody4} = p_delete(conn_pid(Config), ?API_ROOT ++ "/leases/" ++ binary_to_list(Token2),
{ok, ReplyBody4} = p_delete(conn_pid(Config), cvmfs_version:api_root() ++ "/leases/" ++ binary_to_list(Token2),
RequestHeaders4),
#{<<"status">> := <<"ok">>} = jsx:decode(ReplyBody4, [return_maps]).


create_session_when_already_created(Config) ->
% Create new lease
RequestBody = jsx:encode(#{<<"path">> => <<"repo1.domain1.org">>,
<<"api_version">> => integer_to_binary(cvmfs_fe:api_version())}),
<<"api_version">> => integer_to_binary(cvmfs_version:api_protocol_version())}),
HMAC = p_make_hmac(RequestBody),
RequestHeaders = p_make_headers(RequestBody, <<"key1">>, HMAC),
{ok, ReplyBody1} = p_post(conn_pid(Config), ?API_ROOT ++ "/leases", RequestHeaders, RequestBody),
{ok, ReplyBody1} = p_post(conn_pid(Config), cvmfs_version:api_root() ++ "/leases", RequestHeaders, RequestBody),
#{<<"session_token">> := Token} = jsx:decode(ReplyBody1, [return_maps]),

% Try to acquire a lease for the same path a second time
{ok, ReplyBody2} = p_post(conn_pid(Config), ?API_ROOT ++ "/leases", RequestHeaders, RequestBody),
{ok, ReplyBody2} = p_post(conn_pid(Config), cvmfs_version:api_root() ++ "/leases", RequestHeaders, RequestBody),
#{<<"status">> := <<"path_busy">>} = jsx:decode(ReplyBody2, [return_maps]),

% End lease
HMAC3 = p_make_hmac(Token),
RequestHeaders3 = p_make_headers(<<"key1">>, HMAC3),
{ok, ReplyBody3} = p_delete(conn_pid(Config), ?API_ROOT ++ "/leases/" ++ binary_to_list(Token),
{ok, ReplyBody3} = p_delete(conn_pid(Config), cvmfs_version:api_root() ++ "/leases/" ++ binary_to_list(Token),
RequestHeaders3),
#{<<"status">> := <<"ok">>} = jsx:decode(ReplyBody3, [return_maps]).

Expand All @@ -224,18 +222,18 @@ end_invalid_session(Config) ->
Token = <<"NOT_A_PROPER_SESSION_TOKEN">>,
HMAC = p_make_hmac(Token),
RequestHeaders = p_make_headers(<<"key1">>, HMAC),
{ok, Body} = p_delete(conn_pid(Config), ?API_ROOT ++ "/leases/" ++ binary_to_list(Token), RequestHeaders),
{ok, Body} = p_delete(conn_pid(Config), cvmfs_version:api_root() ++ "/leases/" ++ binary_to_list(Token), RequestHeaders),
#{<<"status">> := <<"error">>,
<<"reason">> := <<"invalid_token">>} = jsx:decode(Body, [return_maps]).


normal_payload_submission(Config) ->
% Create new lease
RequestBody1 = jsx:encode(#{<<"path">> => <<"repo1.domain1.org">>,
<<"api_version">> => integer_to_binary(cvmfs_fe:api_version())}),
<<"api_version">> => integer_to_binary(cvmfs_version:api_protocol_version())}),
HMAC1 = p_make_hmac(RequestBody1),
RequestHeaders1 = p_make_headers(RequestBody1, <<"key1">>, HMAC1),
{ok, ReplyBody1} = p_post(conn_pid(Config), ?API_ROOT ++ "/leases", RequestHeaders1, RequestBody1),
{ok, ReplyBody1} = p_post(conn_pid(Config), cvmfs_version:api_root() ++ "/leases", RequestHeaders1, RequestBody1),
#{<<"session_token">> := Token} = jsx:decode(ReplyBody1, [return_maps]),

% Submit payload
Expand All @@ -244,29 +242,29 @@ normal_payload_submission(Config) ->
JSONMessage = jsx:encode(#{<<"session_token">> => Token,
<<"payload_digest">> => Digest,
<<"header_size">> => <<"1">>,
<<"api_version">> => integer_to_binary(cvmfs_fe:api_version())}),
<<"api_version">> => integer_to_binary(cvmfs_version:api_protocol_version())}),
RequestBody2 = <<JSONMessage/binary,Payload/binary>>,
MessageSize = size(JSONMessage),
MessageHMAC = p_make_hmac(JSONMessage),
RequestHeaders2 = p_make_headers(RequestBody2, <<"key1">>, MessageHMAC, MessageSize),
{ok, ReplyBody2} = p_post(conn_pid(Config), ?API_ROOT ++ "/payloads", RequestHeaders2, RequestBody2),
{ok, ReplyBody2} = p_post(conn_pid(Config), cvmfs_version:api_root() ++ "/payloads", RequestHeaders2, RequestBody2),
#{<<"status">> := <<"ok">>} = jsx:decode(ReplyBody2, [return_maps]),

% End lease
HMAC3 = p_make_hmac(Token),
RequestHeaders3 = p_make_headers(<<"key1">>, HMAC3),
{ok, ReplyBody3} = p_delete(conn_pid(Config), ?API_ROOT ++ "/leases/" ++ binary_to_list(Token),
{ok, ReplyBody3} = p_delete(conn_pid(Config), cvmfs_version:api_root() ++ "/leases/" ++ binary_to_list(Token),
RequestHeaders3),
#{<<"status">> := <<"ok">>} = jsx:decode(ReplyBody3, [return_maps]).


payload_submission_with_invalid_hmac(Config) ->
% Create new lease
RequestBody1 = jsx:encode(#{<<"path">> => <<"repo1.domain1.org">>,
<<"api_version">> => integer_to_binary(cvmfs_fe:api_version())}),
<<"api_version">> => integer_to_binary(cvmfs_version:api_protocol_version())}),
HMAC1 = p_make_hmac(RequestBody1),
RequestHeaders1 = p_make_headers(RequestBody1, <<"key1">>, HMAC1),
{ok, ReplyBody1} = p_post(conn_pid(Config), ?API_ROOT ++ "/leases", RequestHeaders1, RequestBody1),
{ok, ReplyBody1} = p_post(conn_pid(Config), cvmfs_version:api_root() ++ "/leases", RequestHeaders1, RequestBody1),
#{<<"session_token">> := Token} = jsx:decode(ReplyBody1, [return_maps]),

% Submit payload
Expand All @@ -275,19 +273,19 @@ payload_submission_with_invalid_hmac(Config) ->
JSONMessage = jsx:encode(#{<<"session_token">> => Token
,<<"payload_digest">> => Digest
,<<"header_size">> => <<"1">>
,<<"api_version">> => integer_to_binary(cvmfs_fe:api_version())}),
,<<"api_version">> => integer_to_binary(cvmfs_version:api_protocol_version())}),
RequestBody2 = <<JSONMessage/binary,Payload/binary>>,
MessageSize = size(JSONMessage),
MessageHMAC = p_make_hmac(<<"SOME_RUBBISH">>),
RequestHeaders2 = p_make_headers(RequestBody2, <<"key1">>, MessageHMAC, MessageSize),
{ok, ReplyBody2} = p_post(conn_pid(Config), ?API_ROOT ++ "/payloads", RequestHeaders2, RequestBody2),
{ok, ReplyBody2} = p_post(conn_pid(Config), cvmfs_version:api_root() ++ "/payloads", RequestHeaders2, RequestBody2),
#{<<"status">> := <<"error">>,
<<"reason">> := <<"invalid_hmac">>} = jsx:decode(ReplyBody2, [return_maps]),

% End lease
HMAC3 = p_make_hmac(Token),
RequestHeaders3 = p_make_headers(<<"key1">>, HMAC3),
{ok, ReplyBody3} = p_delete(conn_pid(Config), ?API_ROOT ++ "/leases/" ++ binary_to_list(Token),
{ok, ReplyBody3} = p_delete(conn_pid(Config), cvmfs_version:api_root() ++ "/leases/" ++ binary_to_list(Token),
RequestHeaders3),
#{<<"status">> := <<"ok">>} = jsx:decode(ReplyBody3, [return_maps]).

Expand Down
4 changes: 2 additions & 2 deletions ci/make_deb.sh
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ fpm -s dir -t deb \
--description "CernVM-FS Repository Gateway" \
--url "http://cernvm.cern.ch" \
--license "BSD-3-Clause" \
--depends "cvmfs-server > 2.5.0" \
--depends "cvmfs-server > 2.5.1" \
--directories usr/libexec/cvmfs-gateway \
--config-files etc/rsyslog.d/90-cvmfs-gateway.conf \
--config-files etc/logrotate.d/90-cvmfs-gateway-rotate-systemd \
Expand All @@ -73,4 +73,4 @@ PKGMAP_FILE=${BUILD_LOCATION}/pkgmap/pkgmap.${PLATFORM}_x86_64
echo "[${PLATFORM}_x86_64]" >> $PKGMAP_FILE
echo "gateway=$PACKAGE_NAME" >> $PKGMAP_FILE

rm -rf $WORKSPACE
rm -rf $WORKSPACE
2 changes: 1 addition & 1 deletion ci/spec/header
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ Source0: %{name}.tar

# If this package has prerequisites, uncomment this line and
# list them here - examples are already listed
Requires: cvmfs-server >= 2.5.1
Requires: cvmfs-server >= 2.5.2

# A more verbose description of your package
%description
Expand Down
Loading