Packages
erldns
10.6.0
11.0.2
11.0.1
11.0.0
10.6.0
10.5.6
10.5.5
10.5.4
10.5.3
10.5.2
10.5.1
10.5.0
10.4.4
10.4.3
10.4.2
10.4.1
10.4.0
10.3.0
10.2.1
10.2.0
10.1.0
10.0.0
10.0.0-rc4
10.0.0-rc3
10.0.0-rc2
10.0.0-rc1
9.1.0
9.0.0
9.0.0-rc3
9.0.0-rc2
9.0.0-rc1
8.1.0
8.0.0
8.0.0-rc6
8.0.0-rc5
8.0.0-rc4
8.0.0-rc3
8.0.0-rc2
8.0.0-rc1
7.0.0
7.0.0-rc9
7.0.0-rc8
7.0.0-rc7
7.0.0-rc6
7.0.0-rc5
7.0.0-rc4
7.0.0-rc3
7.0.0-rc2
7.0.0-rc12
7.0.0-rc11
7.0.0-rc10
7.0.0-rc1
6.0.2
6.0.1
6.0.0
5.0.0
4.3.1
4.3.0
4.2.4
4.2.3
4.2.2
4.2.1
4.2.0
4.1.2
4.1.1
4.1.0
4.0.0
3.0.0
1.0.0
Erlang Authoritative DNS Server
Current section
Files
Jump to
Current section
Files
src/admin/erldns_admin.erl
-module(erldns_admin).
-moduledoc """
Erldns admin API.
### Configuration:
This application will read from your `sys.config` the following example:
```erlang
{erldns, [
{admin, [
{port, 8083},
{tls, false},
{credentials, {~"username", ~"password"}},
{middleware, [my_custom_middleware, another_middleware]},
{routes, [{~"/custom/:action", my_custom_route_handler, #{}}]}
]}
]}
```
The accepted values are:
- `port`: an integer between `1` and `65535` indicating the port to listen on
(default: `8083` without TLS and `8483` when TLS is enabled).
- `tls`: `false`, or `{true, SslOpts}` where `SslOpts` is a list of `t:ssl:tls_server_option/0`,
indicating whether to use TLS (default: `false`).
- `credentials`: `false` if no authentication is required, or a tuple of `{Username, Password}`
binary strings. If configured, all routes will require authentication (default: `false`).
- `middleware`: an optional list of cowboy compliant middleware modules to apply
to all admin API requests (default: `[]`).
- `routes`: an optional list of additional routes to add to the admin API.
This is a list of `cowboy_router:route_path()` elements that will be prepended
to the default routes (default: `[]`).
""".
-define(DEFAULT_CLEAR_PORT, 8083).
-define(DEFAULT_TLS_PORT, 8483).
-define(LOG_METADATA, #{domain => [erldns, admin]}).
-include_lib("kernel/include/logger.hrl").
-export([maybe_start/0]).
-ifdef(TEST).
-export_type([env/0]).
-endif.
-doc """
Configuration parameters, see the module documentation for details.
""".
-type config() :: #{
port := 0..65535,
tls := false | [ssl:tls_server_option()],
credentials := false | {binary(), binary()},
middleware => [module()],
routes => cowboy_router:routes()
}.
-type env() :: [{atom(), term()}].
-spec maybe_start() -> ok | {ok, pid()} | {error, term()}.
maybe_start() ->
case ensure_valid_config() of
disabled ->
ok;
error ->
error(bad_configuration);
Config ->
start(Config)
end.
-spec start(config()) -> {ok, pid()} | {error, term()}.
start(
#{
tls := Tls,
port := Port,
credentials := Credentials,
middleware := Middleware,
routes := AdditionalRoutes
}
) ->
Dispatch = cowboy_router:compile(default_routes(AdditionalRoutes)),
persistent_term:put(?MODULE, Dispatch),
ProtocolOpts = #{
env => #{dispatch => {persistent_term, ?MODULE}, credentials => Credentials},
middlewares => middlewares(Middleware, Credentials)
},
case Tls of
false ->
TransportOpts = socket_opts(Port),
cowboy:start_clear(?MODULE, #{socket_opts => TransportOpts}, ProtocolOpts);
SslOpts ->
TransportOpts = socket_opts(Port) ++ SslOpts,
cowboy:start_tls(?MODULE, #{socket_opts => TransportOpts}, ProtocolOpts)
end.
-spec middlewares([module()], false | dynamic()) -> [module()].
middlewares(Middleware, false) ->
[cowboy_router] ++ Middleware ++ [cowboy_handler];
middlewares(Middleware, _) ->
[erldns_admin_auth_middleware, cowboy_router] ++ Middleware ++ [cowboy_handler].
-spec default_routes([dynamic()]) -> cowboy_router:routes().
default_routes(AdditionalRoutes) ->
DefaultRoutes = [
{~"/", erldns_admin_root_handler, #{}},
{~"/zones/:zonename", erldns_admin_zone_handler, #{}},
{~"/zones/:zonename/records[/:record_name]", erldns_admin_zone_records_handler, #{}}
],
[{'_', AdditionalRoutes ++ DefaultRoutes}].
-spec ensure_valid_config() -> error | disabled | config().
ensure_valid_config() ->
maybe
{ok, Env} ?= env(),
{ok, Tls} ?= config_tls(Env),
{ok, Port} ?= config_port(Env, Tls),
{ok, Credentials} ?= config_credentials(Env),
{ok, Middleware} ?= config_middleware(Env),
{ok, Routes} ?= config_routes(Env),
#{
port => Port,
tls => Tls,
credentials => Credentials,
middleware => Middleware,
routes => Routes
}
end.
-spec socket_opts(inet:port_number()) -> [gen_tcp:option()].
socket_opts(Port) ->
[
inet6,
{ipv6_v6only, false},
{ip, any},
{port, Port},
{nodelay, true},
{keepalive, true},
{reuseport, true},
{reuseport_lb, true}
].
-spec config_port(env(), false | {true, dynamic()}) -> {ok, 1..65535} | error.
config_port(Env, MaybeTls) ->
DefaultPort =
case MaybeTls of
false -> ?DEFAULT_CLEAR_PORT;
_ -> ?DEFAULT_TLS_PORT
end,
case proplists:get_value(port, Env, DefaultPort) of
Port when is_integer(Port), 0 < Port, Port =< 65535 ->
{ok, Port};
OtherPort ->
?LOG_ERROR(
#{what => erldns_admin_bad_config, port => OtherPort},
?LOG_METADATA
),
error
end.
-spec config_tls(env()) -> {ok, false | [ssl:tls_server_option()]} | error.
config_tls(Env) ->
case proplists:get_value(tls, Env, false) of
false ->
{ok, false};
{true, Opts} when is_list(Opts) ->
{ok, Opts};
Other ->
?LOG_ERROR(
#{what => erldns_admin_bad_config, tls => Other},
?LOG_METADATA
),
error
end.
-spec config_credentials(env()) -> {ok, false | {binary(), binary()}} | error.
config_credentials(Env) ->
case lists:keyfind(credentials, 1, Env) of
false ->
{ok, false};
{credentials, {Username, Password}} when is_binary(Username), is_binary(Password) ->
{ok, {Username, Password}};
OtherValue ->
?LOG_ERROR(
#{what => erldns_admin_bad_config, credentials => OtherValue},
?LOG_METADATA
),
error
end.
-spec config_middleware(env()) -> {ok, [module()]} | error.
config_middleware(Env) ->
maybe
{middleware, Modules} ?= lists:keyfind(middleware, 1, Env),
[] ?= lists:filter(fun is_not_middleware/1, Modules),
{ok, Modules}
else
false ->
{ok, []};
OtherValue ->
?LOG_ERROR(
#{what => erldns_admin_bad_config, middleware => OtherValue},
?LOG_METADATA
),
error
end.
-spec is_not_middleware(module()) -> boolean().
is_not_middleware(Module) when is_atom(Module) ->
not (is_atom(Module) andalso {module, Module} =:= code:ensure_loaded(Module) andalso
erlang:function_exported(Module, execute, 2)).
-spec config_routes(env()) -> {ok, cowboy_router:routes()} | error.
config_routes(Env) ->
maybe
{routes, Routes} ?= lists:keyfind(routes, 1, Env),
true ?= is_list(Routes),
{ok, Routes}
else
false ->
{ok, []};
OtherValue ->
?LOG_ERROR(
#{what => erldns_admin_bad_config, routes => OtherValue},
?LOG_METADATA
),
error
end.
-spec env() -> {ok, env()} | error | disabled.
env() ->
case application:get_env(erldns, admin) of
{ok, Env} when is_list(Env) -> {ok, Env};
{ok, _} -> error;
_ -> disabled
end.