Working base loop

This commit is contained in:
Maciej Szlosarczyk 2019-05-27 16:29:02 +03:00
parent da3fa72432
commit c2c91667e7
No known key found for this signature in database
GPG key ID: 41D62D42D3B0D765
12 changed files with 355 additions and 120 deletions

View file

@ -6,12 +6,13 @@
common_name_from_subject/1, certificate_to_pem/1,
der_certificate/1, headers_from_cert/1]).
% Returns a tuple of headers {SSL_CLIENT_S_DN_CN, SLL_CLIENT_CERT}
% Returns a tuple of headers {SSL_CLIENT_S_DN_CN, SSL_CLIENT_CERT}
headers_from_cert(Der) ->
OTPCertificate = der_certificate(Der),
Subject = subject_from_otp_certificate(OTPCertificate),
CommonName = common_name_from_subject(Subject),
PEM = certificate_to_pem(OTPCertificate),
logger:info("~p~n", [CommonName]),
PEM = certificate_to_pem(Der),
{CommonName, PEM}.
%% Read certificate from the wire and return back an otp type record

View file

@ -36,11 +36,15 @@ init([]) ->
type => worker,
modules => [epp_tcp_acceptor],
start => {epp_tcp_acceptor, start_link, [3333]}},
PoolSupervisor = #{id => pool_supervisor,
TLSAcceptor = #{id => epp_tls_acceptor,
type => worker,
modules => [epp_tls_acceptor],
start => {epp_tls_acceptor, start_link, [4444]}},
PoolSupervisor = #{id => epp_pool_supervisor,
type => supervisor,
modules => [pool_supervisor],
start => {pool_supervisor, start_link, []}},
{ok, {SupFlags, [TCPAcceptor, PoolSupervisor]}}.
modules => [epp_pool_supervisor],
start => {epp_pool_supervisor, start_link, []}},
{ok, {SupFlags, [TCPAcceptor, TLSAcceptor, PoolSupervisor]}}.
%%====================================================================
%% Internal functions

View file

@ -1,20 +0,0 @@
-module(epp_tcp).
-behaviour(supervisor).
-define(SERVER, ?MODULE).
-export([start_link/1, init/1]).
start_link(Port) ->
supervisor:start_link({local, ?SERVER}, ?MODULE, [Port]).
init([Port]) ->
process_flag(trap_exit, true),
SupFlags = #{strategy => one_for_one, intensity => 3, period => 60},
ChildSpecs = [#{id => epp_tcp_acceptor,
start => {epp_tcp_acceptor, start_link, [Port]}}],
log_message(Port),
{ok, {SupFlags, ChildSpecs}}.
log_message(Port) ->
logger:info("TCP listening on Port: ~p", [Port]).

View file

@ -2,10 +2,9 @@
-behaviour(gen_server).
-define(SERVER, ?MODULE).
-define(POOL_SUPERVISOR, pool_supervisor).
-define(POOL_SUPERVISOR, epp_pool_supervisor).
-define(WORKER, epp_tcp_worker).
%% gen_server callbacks
-export([init/1, handle_cast/2, handle_call/3, start_link/1]).

View file

@ -5,76 +5,93 @@
%% gen_server callbacks
-export([init/1, handle_cast/2, handle_call/3, start_link/1]).
-export([terminate/2, code_change/3]).
-export([code_change/3]).
-export([request/3]).
-record(state,{socket, length, session_id}).
-record(request,{method, url, body, cookies, headers}).
init(Socket) ->
logger:info("Created a worker process"),
{ok, #state{socket=Socket}}.
SessionId = session_id(),
{ok, #state{socket=Socket, session_id=SessionId}}.
start_link(Socket) ->
gen_server:start_link(?MODULE, Socket, []).
handle_cast(serve, State = #state{socket=Socket}) ->
{noreply, State#state{socket=Socket}};
handle_cast(greeting, State = #state{socket=Socket}) ->
SessionId = session_id(),
Cookie = hackney_cookie:setcookie("session", SessionId, []),
handle_cast(greeting, State = #state{socket=Socket, session_id=SessionId}) ->
Request = request("hello", SessionId, ""),
logger:info("Request: ~p~n", [Request]),
{_Status, _StatusCode, _Headers, ClientRef} =
hackney:request(get, epp_router:route_request("hello"), [], "",
[{cookie, Cookie}, insecure]),
hackney:request(Request#request.method, Request#request.url,
Request#request.headers, Request#request.body,
[{cookie, Request#request.cookies}, insecure]),
{ok, Body} = hackney:body(ClientRef),
Length = byte_size(Body) + 4,
ByteSize = << Length:32/big >>,
io:format("State: ~p~n", [State]),
write_line(Socket, ByteSize),
write_line(Socket, Body),
gen_server:cast(self(), read_length),
frame_to_socket(Body, Socket),
gen_server:cast(self(), process_command),
{noreply, State#state{socket=Socket, session_id=SessionId}};
handle_cast(read_length, State = #state{socket=Socket}) ->
case read_length(Socket) of
handle_cast(process_command, State = #state{socket=Socket, session_id=SessionId}) ->
Length = case read_length(Socket) of
{ok, Data} ->
NewState = State#state{length=Data},
gen_server:cast(self(), read_frame),
{noreply, NewState};
Data;
{error, _Details} ->
{stop, normal, State}
end;
handle_cast(read_frame, State = #state{socket=Socket, length=Length}) ->
case read_frame(Socket, Length) of
{ok, Data} ->
ByteLength = byte_size(Data) + 4,
ByteSize = << ByteLength:32/big >>,
write_line(Socket, ByteSize),
write_line(Socket, Data),
gen_server:cast(self(), read_length),
{noreply, State};
{error, _Details} ->
end,
Frame = case read_frame(Socket, Length) of
{ok, FrameData} ->
io:format("~p~n", [FrameData]),
FrameData;
{error, _FrameDetails} ->
{stop, normal, State}
end;
handle_cast(Message, State) ->
logger:info("Received message ~p.~n", [Message]),
{noreply, State}.
end,
{ok, XMLRecord} = epp_xml:parse(Frame),
Command = epp_xml:get_command(XMLRecord),
Request = request(Command, SessionId, Frame),
logger:info("Request: ~p~n", [Request]),
{_Status, _StatusCode, _Headers, ClientRef} =
hackney:request(Request#request.method, Request#request.url,
Request#request.headers, Request#request.body,
[{cookie, Request#request.cookies}, insecure]),
{ok, Body} = hackney:body(ClientRef),
frame_to_socket(Body, Socket),
%% On logout, close the socket.
%% Else, go back to the beginning of the loop.
if
Command =:= "logout" ->
ok = gen_tcp:shutdown(Socket, read_write),
{stop, normal, State};
true ->
gen_server:cast(self(), process_command),
{noreply, State#state{socket=Socket, session_id=SessionId}}
end.
handle_call(_E, _From, State) -> {noreply, State}.
terminate(_Reason, _State) ->
ok.
code_change(_OldVersion, State, _Extra) -> {ok, State}.
%% Private function
write_line(Socket, Line) ->
gen_tcp:send(Socket, Line).
ok = gen_tcp:send(Socket, Line).
read_length(Socket) ->
case gen_tcp:recv(Socket, 4) of
{ok, Data} ->
Length = binary:decode_unsigned(Data, big),
io:format("Preparing to receive: ~p~n", [Length]),
{ok, Length - 4};
LengthToReceive = epp_util:frame_length_to_receive(Length),
{ok, LengthToReceive};
{error, Reason} ->
io:format("Error: ~p~n", [Reason]),
{error, Reason}
@ -94,3 +111,33 @@ session_id() ->
UniqueMap = epp_util:create_map(self()),
BinaryHash = epp_util:create_session_id(UniqueMap),
BinaryHash.
%% Map request and return values
request(Command, SessionId, RawFrame) ->
URL = epp_router:route_request(Command),
RequestMethod = request_method(Command),
Cookie = hackney_cookie:setcookie("session", SessionId, []),
case Command of
"hello" ->
Body = "";
_ ->
Body = {multipart, [{<<"raw_frame">>, RawFrame}]}
end,
Headers = [],
#request{url=URL, method=RequestMethod, body=Body, cookies=[Cookie],
headers=Headers}.
%% request method: GET for greeting, POST for everything else.
request_method("hello") ->
get;
request_method(<<"hello">>) ->
get;
request_method(_) ->
post.
%% Wrap a message in EPP frame, and then send it to socket.
frame_to_socket(Message, Socket) ->
Length = epp_util:frame_length_to_send(Message),
ByteSize = << Length:32/big >>,
write_line(Socket, ByteSize),
write_line(Socket, Message).

View file

@ -1,51 +0,0 @@
-module(epp_tls).
-behaviour(supervisor).
-define(SERVER, ?MODULE).
-export([start_link/1, init/1, serve/1]).
start_link(Port) ->
supervisor:start_link({local, ?SERVER}, ?MODULE, [Port]).
init([Port]) ->
process_flag(trap_exit, true),
SupFlags = #{strategy => one_for_one, intensity => 3, period => 60},
ChildSpecs = [],
log_message(Port),
accept(Port),
{ok, {SupFlags, ChildSpecs}}.
accept(Port) ->
Options = [binary,
{packet, raw},
{active, false},
{reuseaddr, true},
{verify, verify_peer},
{depth, 1},
{cacertfile, "/Users/maciej/Development/internetee/docker-images/shared/ca/certs/ca.crt.pem"},
{certfile, "/Users/maciej/Development/internetee/docker-images/shared/ca/certs/apache.crt"},
{keyfile, "/Users/maciej/Development/internetee/docker-images/shared/ca/private/apache.key"}],
{ok, ListenSocket} = ssl:listen(Port, Options),
spawn_monitor(fun () -> loop_acceptor(ListenSocket) end).
loop_acceptor(ListenSocket) ->
case ssl:transport_accept(ListenSocket) of
{error, closed} -> exit(shutdown);
{ok, Client} ->
Pid = spawn_link(?MODULE, serve, [Client]),
logger:info("PID: ~p", [Pid]),
ok = ssl:controlling_process(Client, Pid)
end,
loop_acceptor(ListenSocket).
serve(Client) ->
{ok, TSocket} = ssl:handshake(Client),
{ok, ClientCert} = ssl:peercert(TSocket),
logger:info("Client cert ~s", [ClientCert]),
timer:sleep(100000),
exit(shutdown).
log_message(Port) ->
logger:info("SSL Listening on Port: ~p", [Port]).

View file

@ -0,0 +1,67 @@
-module(epp_tls_acceptor).
-behaviour(gen_server).
-define(SERVER, ?MODULE).
-define(POOL_SUPERVISOR, epp_pool_supervisor).
-define(WORKER, epp_tls_worker).
-define(CaCertFile,
case application:get_env(epp_proxy, epp_cacertfile_path) of
undefined -> undefined;
{ok, Value} -> Value
end).
-define(CertFile,
case application:get_env(epp_proxy, epp_certfile_path) of
undefined -> undefined;
{ok, Value} -> Value
end).
-define(KeyFile,
case application:get_env(epp_proxy, epp_keyfile_path) of
undefined -> undefined;
{ok, Value} -> Value
end).
%% gen_server callbacks
-export([init/1, handle_cast/2, handle_call/3, start_link/1]).
-record(state, {socket, port, options}).
start_link(Port) ->
gen_server:start_link({local, ?SERVER}, ?MODULE, Port, []).
init(Port) ->
Options = [binary,
{packet, raw},
{active, false},
{reuseaddr, true},
{verify, verify_peer},
{depth, 1},
{cacertfile, ?CaCertFile},
{certfile, ?CertFile},
{keyfile, ?KeyFile}],
{ok, ListenSocket} = ssl:listen(Port, Options),
gen_server:cast(self(), accept),
{ok, #state{socket=ListenSocket, port=Port, options=Options}}.
handle_cast(accept, State = #state{socket=ListenSocket, port=Port, options=Options}) ->
{ok, AcceptSocket} = ssl:transport_accept(ListenSocket),
{ok, NewOwner} = create_worker(AcceptSocket),
ok = ssl:controlling_process(AcceptSocket, NewOwner),
gen_server:cast(NewOwner, serve),
gen_server:cast(NewOwner, greeting),
gen_server:cast(self(), accept),
{noreply, State#state{socket=ListenSocket, port=Port, options=Options}}.
handle_call(_E, _From, State) -> {noreply, State}.
create_worker(Socket) ->
ChildSpec = #{id => rand:uniform(),
type => worker,
modules => [?WORKER],
restart => temporary,
start => {?WORKER, start_link, [Socket]}},
supervisor:start_child(?POOL_SUPERVISOR, ChildSpec).

View file

@ -0,0 +1,155 @@
-module(epp_tls_worker).
-behaviour(gen_server).
-define(SERVER, ?MODULE).
%% gen_server callbacks
-export([init/1, handle_cast/2, handle_call/3, start_link/1]).
-export([code_change/3]).
-export([request/5]).
-record(state,{socket, length, session_id, common_name, client_cert}).
-record(request,{method, url, body, cookies, headers}).
init(Socket) ->
logger:info("Created a worker process"),
SessionId = session_id(),
{ok, #state{socket=Socket, session_id=SessionId}}.
start_link(Socket) ->
gen_server:start_link(?MODULE, Socket, []).
handle_cast(serve, State = #state{socket=Socket}) ->
{ok, SecureSocket} = ssl:handshake(Socket),
{ok, PeerCert} = ssl:peercert(SecureSocket),
{SSL_CLIENT_S_DN_CN, SSL_CLIENT_CERT} =
epp_certs:headers_from_cert(PeerCert),
{noreply, State#state{socket=SecureSocket, common_name=SSL_CLIENT_S_DN_CN,
client_cert=SSL_CLIENT_CERT}};
handle_cast(greeting, State = #state{socket=Socket, common_name=SSL_CLIENT_S_DN_CN,
client_cert=SSL_CLIENT_CERT,
session_id=SessionId}) ->
Request = request("hello", SessionId, "", SSL_CLIENT_S_DN_CN,
SSL_CLIENT_CERT),
logger:info("Request: ~p~n", [Request]),
{_Status, _StatusCode, _Headers, ClientRef} =
hackney:request(Request#request.method, Request#request.url,
Request#request.headers, Request#request.body,
[{cookie, Request#request.cookies}, insecure]),
{ok, Body} = hackney:body(ClientRef),
frame_to_socket(Body, Socket),
gen_server:cast(self(), process_command),
{noreply, State#state{socket=Socket, session_id=SessionId}};
handle_cast(process_command, State = #state{socket=Socket,
common_name=SSL_CLIENT_S_DN_CN,
client_cert=SSL_CLIENT_CERT,
session_id=SessionId}) ->
Length = case read_length(Socket) of
{ok, Data} ->
Data;
{error, _Details} ->
{stop, normal, State}
end,
Frame = case read_frame(Socket, Length) of
{ok, FrameData} ->
io:format("~p~n", [FrameData]),
FrameData;
{error, _FrameDetails} ->
{stop, normal, State}
end,
{ok, XMLRecord} = epp_xml:parse(Frame),
Command = epp_xml:get_command(XMLRecord),
Request = request(Command, SessionId, Frame, SSL_CLIENT_S_DN_CN,
SSL_CLIENT_CERT),
logger:info("Request: ~p~n", [Request]),
{_Status, _StatusCode, _Headers, ClientRef} =
hackney:request(Request#request.method, Request#request.url,
Request#request.headers, Request#request.body,
[{cookie, Request#request.cookies}, insecure]),
{ok, Body} = hackney:body(ClientRef),
frame_to_socket(Body, Socket),
%% On logout, close the socket.
%% Else, go back to the beginning of the loop.
if
Command =:= "logout" ->
ok = ssl:shutdown(Socket, read_write),
{stop, normal, State};
true ->
gen_server:cast(self(), process_command),
{noreply, State#state{socket=Socket, session_id=SessionId}}
end.
handle_call(_E, _From, State) -> {noreply, State}.
code_change(_OldVersion, State, _Extra) -> {ok, State}.
%% Private function
write_line(Socket, Line) ->
ok = ssl:send(Socket, Line).
read_length(Socket) ->
case ssl:recv(Socket, 4) of
{ok, Data} ->
Length = binary:decode_unsigned(Data, big),
LengthToReceive = epp_util:frame_length_to_receive(Length),
{ok, LengthToReceive};
{error, Reason} ->
io:format("Error: ~p~n", [Reason]),
{error, Reason}
end.
read_frame(Socket, FrameLength) ->
case ssl:recv(Socket, FrameLength) of
{ok, Data} ->
io:format("Frame: ~p~n", [Data]),
{ok, Data};
{error, Reason} ->
io:format("Error: ~p~n", [Reason]),
{error, Reason}
end.
session_id() ->
UniqueMap = epp_util:create_map(self()),
BinaryHash = epp_util:create_session_id(UniqueMap),
BinaryHash.
%% Map request and return values
request(Command, SessionId, RawFrame, CommonName, ClientCert) ->
URL = epp_router:route_request(Command),
RequestMethod = request_method(Command),
Cookie = hackney_cookie:setcookie("session", SessionId, []),
case Command of
"hello" ->
Body = "";
_ ->
Body = {multipart, [{<<"raw_frame">>, RawFrame}]}
end,
Headers = [{"SSL_CLIENT_CERT", ClientCert},
{"SSL_CLIENT_S_DN_CN", CommonName}],
#request{url=URL, method=RequestMethod, body=Body, cookies=[Cookie],
headers=Headers}.
%% request method: GET for greeting, POST for everything else.
request_method("hello") ->
get;
request_method(<<"hello">>) ->
get;
request_method(_) ->
post.
%% Wrap a message in EPP frame, and then send it to socket.
frame_to_socket(Message, Socket) ->
Length = epp_util:frame_length_to_send(Message),
ByteSize = << Length:32/big >>,
write_line(Socket, ByteSize),
write_line(Socket, Message).

View file

@ -1,6 +1,9 @@
-module(epp_util).
-export([create_map/1, create_session_id/1]).
-export([create_map/1, create_session_id/1, frame_length/1,
frame_length_to_receive/1, frame_length_to_send/1]).
-define(OFFSET, 4).
% Give me a process id, I'll create a random map for you.
-spec create_map(pid()) -> #{string() => pid(), string() => float(),
@ -24,3 +27,16 @@ create_session_id(#{"pid" := Pid, "random" := Random, "timestamp" := Timestamp})
BinaryHash = crypto:hash(sha512, ListOfGlyphs),
String = lists:flatten([integer_to_list(X,16) || <<X>> <= BinaryHash]),
String.
frame_length_to_receive(Size) when Size >= 0 ->
Size - ?OFFSET.
frame_length_to_send(Frame) ->
Length = frame_length(Frame),
Length + ?OFFSET.
frame_length(Frame) when is_binary(Frame) ->
byte_size(Frame);
frame_length(Frame) when is_list(Frame) ->
Bin = unicode:characters_to_binary(Frame),
byte_size(Bin).

View file

@ -15,7 +15,7 @@ wder_certificate_test() ->
subject_from_otp_certificate_test() ->
Certificate = test_certificate(),
Subject = epp_certs:subject_from_otp_certificate(Certificate),
{rdnSequence, ListOfAttributes} = Subject.
{rdnSequence, _ListOfAttributes} = Subject.
common_name_from_subject_test() ->
Certificate = test_certificate(),

View file

@ -17,3 +17,17 @@ create_session_id_test() ->
Hash = epp_util:create_session_id(Map),
?assert(is_list(Hash)),
?assertEqual("88F49C2B1BDD6F2355BF4424A67C928DA7C5616B30F7C5B35F17747348AF61EF8723ED6BE6012D879CB3D6A7EC7A187660A56910ED896AE67DE533C212D", Hash).
frame_length_test() ->
?assertEqual(2, epp_util:frame_length("aa")),
?assertEqual(2, epp_util:frame_length(<<"aa">>)),
?assertEqual(2, epp_util:frame_length(<<"">>)).
frame_length_to_receive_test() ->
?assertEqual(2, epp_util:frame_length_to_receive(6)),
?assertEqual(0, epp_util:frame_length_to_receive(4)),
?assertError(function_clause, epp_util:frame_length_to_receive(-22)).
frame_length_to_send_test() ->
?assertEqual(18, epp_util:frame_length_to_send("<epp><command>")),
?assertEqual(4, epp_util:frame_length_to_send("")).

View file

@ -2,5 +2,8 @@
{epp_proxy, [{tcp_port, 3333},
{tls_port, 4444},
{epp_session_url, "https://registry.test/epp/session/"},
{epp_command_url, "https://registry.test/epp/command/"}]}
{epp_command_url, "https://registry.test/epp/command/"},
{epp_cacertfile_path, "/Users/maciej/Development/internetee/docker-images/shared/ca/certs/ca.crt.pem"},
{epp_certfile_path, "/Users/maciej/Development/internetee/docker-images/shared/ca/certs/apache.crt"},
{epp_keyfile_path, "/Users/maciej/Development/internetee/docker-images/shared/ca/private/apache.key"}]}
].