Commit 312bbe42 authored by href's avatar href

Initial commit

parents
Pipeline #259 failed with stages
.rebar3
_*
.eunit
*.o
*.beam
*.plt
*.swp
*.swo
.erlang.cookie
ebin
log
erl_crash.dump
.rebar
logs
_build
.idea
*.iml
rebar3.crashdump
apps/escape_from_lan/priv/*.pem
apps/escape_from_lan/priv/*.key
Copyright (c) 2019 Jordan Bracco <href@random.sh>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
Escape from LAN
=====
Escape From LAN (EFL)! WIP.
Similar to localtunnel.me, ngrok.io, and other services -- but just designed for HTTP, runs with HTTP/2 ; speak Cowboy
naturally (by passing/receiving `term_to_binary` cowboy requests / responses).
Design:
* A _server_ connects to `uplink.efldomain` over HTTP/2
* Requests the long-running `/` endpoint (SSE)
* All requests received by EFL at `username.efldomain` are sent to the server (on `/`)
* Server sends the response by POSTing at `/resp/:requestid`
* EFL sends the response to the end client.
Todo:
* Make streaming replies work.
* Always stream when possible.
* Implement security.
* Implement sub-domains & authorization for subdomains
See also [efl_phx](https://git.yt/efl/efl_phx)
Build
-----
$ rebar3 compile
%%% Copyright (c) 2019, Jordan Bracco <href@random.sh>
%%
%% Permission to use, copy, modify, and/or distribute this software for any
%% purpose with or without fee is hereby granted, provided that the above
%% copyright notice and this permission notice appear in all copies.
%%
%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
%%-------------------------------------------------------------------
%% @doc escape_from_lan public API
%% @end
%%%-------------------------------------------------------------------
-module(efl_app).
-behaviour(application).
%% Application callbacks
-export([start/2, stop/1]).
%%====================================================================
%% API
%%====================================================================
start(_StartType, _StartArgs) ->
{ok, RootDomain} = application:get_env(escape_from_lan, root_domain),
{ok, ConnectDomain} = application:get_env(escape_from_lan, connect_domain),
Dispatch = cowboy_router:compile([
{ConnectDomain, [
{"/", efl_connect_h, []},
{"/resp/:id", efl_resp_h, []},
{"/resp/:id[/:part]", efl_resp_h, []},
]},
{":subdomain." ++ RootDomain, [{'_', efl_proxy_h, []}]}
]),
Path = code:priv_dir(escape_from_lan),
{ok, Port} = application:get_env(escape_from_lan, port),
{ok, CertFile} = application:get_env(escape_from_lan, ssl_cert),
{ok, KeyFile} = application:get_env(escape_from_lan, ssl_key),
{ok, _} = cowboy:start_tls(efl_https_listener,
[
{port, Port},
{certfile, Path ++ "/" ++ CertFile},
{keyfile, Path ++ "/" ++ KeyFile}
],
#{
env => #{dispatch => Dispatch}
}
),
efl_sup:start_link().
%%--------------------------------------------------------------------
stop(_State) ->
ok.
%%====================================================================
%% Internal functions
%%====================================================================
%% Copyright (c) 2019, Jordan Bracco <href@random.sh>
%%
%% Permission to use, copy, modify, and/or distribute this software for any
%% purpose with or without fee is hereby granted, provided that the above
%% copyright notice and this permission notice appear in all copies.
%%
%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-module(efl_connect_h).
-export([init/2]).
init(#{version := 'HTTP/2'} = Req0, State) ->
authorize(Req0, State);
init(Req0, State) ->
Req = cowboy_req:reply(400, #{
<<"content-type">> => <<"text/plain">>
}, <<"HTTP/2 Required and/or bad request">>, Req0),
{ok, Req, State}.
authorize(#{host := _Domain} = Req0, State) ->
register_channel(Req0, State).
% Register the process as the uplink for the given domain
register_channel(#{host := Domain} = Req0, State) ->
yes = global:register_name(Domain, self()),
Req = cowboy_req:stream_reply(200, #{
<<"content-type">> => <<"text/html">>
}, Req0),
loop(Req, State).
loop(Req0, State) ->
io:format("efl_connect_handler poll ~p~n", [Req0]),
receive
{reqid, Id, ForeignReq} ->
Data = term_to_binary({Id, ForeignReq}),
ok = cowboy_req:stream_body(Data, nofin, Req0),
loop(Req0, State);
end.
%% Copyright (c) 2019, Jordan Bracco <href@random.sh>
%%
%% Permission to use, copy, modify, and/or distribute this software for any
%% purpose with or without fee is hereby granted, provided that the above
%% copyright notice and this permission notice appear in all copies.
%%
%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-module(efl_proxy_h).
-export([init/2]).
init(Req0, State) ->
PidH = erlang:phash2(self()),
RandP = rand:uniform(9999999),
Id = list_to_binary(integer_to_list(PidH + RandP)),
yes = global:register_name({req,Id}, self()),
global:send(<<"uplink.randomlabs.dev">>, {reqid, Id, Req0}),
receive
{response, Resp} ->
{Status, Headers0, Body} = binary_to_term(Resp),
Headers = maps:put(<<"x-efl-request-id">>, Id, Headers0),
cowboy_req:reply(Status, Headers, Body, Req0)
end.
%% Copyright (c) 2019, Jordan Bracco <href@random.sh>
%%
%% Permission to use, copy, modify, and/or distribute this software for any
%% purpose with or without fee is hereby granted, provided that the above
%% copyright notice and this permission notice appear in all copies.
%%
%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-module(efl_resp_h).
-export([init/2]).
init(#{bindings := #{id := Id}} = Req0 , State) ->
{ok, Body, Req1} = read_body(Req0, <<>>),
global:send({req,Id}, {response, Body}),
Req = cowboy_req:reply(200, #{
<<"content-type">> => <<"text/plain">>
}, <<"">>, Req1),
{ok, Req, State}.
read_body(Req0, Acc) ->
case cowboy_req:read_body(Req0) of
{ok, Data, Req} -> {ok, << Acc/binary, Data/binary >>, Req};
{more, Data, Req} -> read_body(Req, << Acc/binary, Data/binary >>)
end.
%% Copyright (c) 2019, Jordan Bracco <href@random.sh>
%%
%% Permission to use, copy, modify, and/or distribute this software for any
%% purpose with or without fee is hereby granted, provided that the above
%% copyright notice and this permission notice appear in all copies.
%%
%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
%%%-------------------------------------------------------------------
%% @doc escape_from_lan top level supervisor.
%% @end
%%%-------------------------------------------------------------------
-module(efl_sup).
-behaviour(supervisor).
%% API
-export([start_link/0]).
%% Supervisor callbacks
-export([init/1]).
-define(SERVER, ?MODULE).
%%====================================================================
%% API functions
%%====================================================================
start_link() ->
supervisor:start_link({local, ?SERVER}, ?MODULE, []).
%%====================================================================
%% Supervisor callbacks
%%====================================================================
%% Child :: #{id => Id, start => {M, F, A}}
%% Optional keys are restart, shutdown, type, modules.
%% Before OTP 18 tuples must be used to specify a child. e.g.
%% Child :: {Id,StartFunc,Restart,Shutdown,Type,Modules}
init([]) ->
{ok, { {one_for_all, 0, 1}, []} }.
%%====================================================================
%% Internal functions
%%====================================================================
{application, escape_from_lan,
[{description, "Escape From LAN"},
{vsn, "0.1.0"},
{registered, []},
{mod, { efl_app, []}},
{applications,
[kernel,
stdlib,
cowboy
]},
{env,[]},
{modules, []},
{maintainers, ["Jordan Bracco <href@random.sh>"]},
{licenses, ["Apache 2.0"]},
{links, ["https://randomlabs.co/escapefromlan/"]}
]}.
[
{ escape_from_lan, [
{port, 8443},
{root_domain, "randomlabs.dev"},
{connect_domain, "uplink.randomlabs.dev"},
{reserved_sub_domains, ["uplink", "root", "www", "admin", "mgmt", "dev", "git", "load", "dist"]},
{ssl_cert, "randomlabs.dev.pem"},
{ssl_key, "randomlabs.dev.key"}
]}
].
-sname escape_from_lan
-setcookie escape_from_lan_cookie
+K true
+A30
{erl_opts, [debug_info]}.
{deps, [cowboy]}.
{relx, [{release, { escape_from_lan, "0.1.0" },
[escape_from_lan,
sasl]},
{sys_config, "./config/sys.config"},
{vm_args, "./config/vm.args"},
{dev_mode, true},
{include_erts, false},
{extended_start_script, true}]
}.
{profiles, [{prod, [{relx, [{dev_mode, false},
{include_erts, true}]}]
}]
}.
{"1.1.0",
[{<<"cowboy">>,{pkg,<<"cowboy">>,<<"2.6.1">>},0},
{<<"cowlib">>,{pkg,<<"cowlib">>,<<"2.7.0">>},1},
{<<"ranch">>,{pkg,<<"ranch">>,<<"1.7.1">>},1}]}.
[
{pkg_hash,[
{<<"cowboy">>, <<"F2E06F757C337B3B311F9437E6E072B678FCD71545A7B2865BDAA154D078593F">>},
{<<"cowlib">>, <<"3EF16E77562F9855A2605900CEDB15C1462D76FB1BE6A32FC3AE91973EE543D2">>},
{<<"ranch">>, <<"6B1FAB51B49196860B733A49C07604465A47BDB78AA10C1C16A3D199F7F8C881">>}]}
].
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment