Skip to content

Commit

Permalink
Support static credentials
Browse files Browse the repository at this point in the history
Allow for configuring a map of static username/password pairs using the
new 'credentials' option.

TODO: Documentation (doc/overview.edoc and CHANGELOG.md).
  • Loading branch information
weiss committed Aug 1, 2023
1 parent 50c5ebc commit 803d863
Show file tree
Hide file tree
Showing 6 changed files with 51 additions and 38 deletions.
44 changes: 33 additions & 11 deletions src/eturnal.erl
Original file line number Diff line number Diff line change
Expand Up @@ -138,12 +138,17 @@ handle_call({set_loglevel, Level}, _From, State) ->
{reply, Err, State}
end;
handle_call({get_password, Username}, _From, State) ->
case get_opt(secret) of
[Secret | _Secrets] ->
case {get_opt(secret), is_dynamic_username(Username)} of
{[Secret | _Secrets], true} ->
Password = derive_password(Username, [Secret]),
{reply, {ok, Password}, State};
undefined ->
{reply, {error, no_secret}, State}
{_, _} ->
case maps:get(Username, get_opt(credentials), undefined) of
Password when is_binary(Password) ->
{reply, {ok, Password}, State};
undefined ->
{reply, {error, no_credentials}, State}
end
end;
handle_call(Request, From, State) ->
?LOG_ERROR("Got unexpected request from ~p: ~p", [From, Request]),
Expand Down Expand Up @@ -235,7 +240,7 @@ get_password(Username, _Realm) ->
ExpireTime ->
case erlang:system_time(second) of
Now when Now < ExpireTime ->
?LOG_DEBUG("Looking up password for: ~ts", [Username]),
?LOG_DEBUG("Deriving password for: ~ts", [Username]),
derive_password(Username, get_opt(secret));
Now when Now >= ExpireTime ->
case get_opt(strict_expiry) of
Expand All @@ -249,8 +254,14 @@ get_password(Username, _Realm) ->
end
end
catch _:badarg ->
?LOG_INFO("Non-numeric expiration field: ~ts", [Username]),
<<>>
?LOG_DEBUG("Looking up password for: ~ts", [Username]),
case maps:get(Username, get_opt(credentials), undefined) of
Password when is_binary(Password) ->
Password;
undefined ->
?LOG_INFO("Have no password for: ~ts", [Username]),
<<>>
end
end.

%% API: retrieve option value.
Expand Down Expand Up @@ -301,6 +312,17 @@ reload_config() ->

%% Internal functions: authentication.

-spec is_dynamic_username(binary()) -> boolean().
is_dynamic_username(Username) ->
case string:to_integer(Username) of
{N, <<":", _Rest/binary>>} when is_integer(N), N > 0 ->
true;
{N, <<>>} when is_integer(N), N > 0 ->
true;
{_, _} ->
false
end.

-spec derive_password(binary(), [binary()]) -> binary() | [binary()].
-ifdef(old_crypto).
derive_password(Username, [Secret]) ->
Expand Down Expand Up @@ -451,7 +473,7 @@ opt_filter(Opt) ->

-spec turn_opts(boolean()) -> proplists:proplist().
turn_opts(EnableTURN) ->
case {EnableTURN, got_secret(), got_relay_addr()} of
case {EnableTURN, got_credentials(), got_relay_addr()} of
{true, true, true} ->
[{use_turn, true},
{auth_type, user}];
Expand Down Expand Up @@ -509,8 +531,8 @@ turn_enabled() ->
EnableTURN =:= true
end, get_opt(listen)).

-spec got_secret() -> boolean().
got_secret() ->
-spec got_credentials() -> boolean().
got_credentials() ->
case get_opt(secret) of
Secrets when is_list(Secrets) ->
lists:all(fun(Secret) ->
Expand All @@ -519,7 +541,7 @@ got_secret() ->
Secret when is_binary(Secret), byte_size(Secret) > 0 ->
true;
undefined ->
false
map_size(get_opt(credentials)) > 0
end.

-spec got_relay_addr() -> boolean().
Expand Down
35 changes: 9 additions & 26 deletions src/eturnal_ctl.erl
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,8 @@ get_credentials(Expiry, Suffix) ->
{ok, Password} ->
Credentials = format_credentials(Username, Password),
{ok, unicode:characters_to_list(Credentials)};
{error, no_secret} ->
{error, "No shared secret"};
{error, no_credentials} ->
{error, "No shared secret and no credentials"};
{error, timeout} ->
{error, "Querying eturnal timed out"}
end
Expand All @@ -78,19 +78,13 @@ get_password(Username0) ->
?LOG_DEBUG("Handling API call: get_password(~p)", [Username0]),
try unicode:characters_to_binary(Username0) of
Username when is_binary(Username) ->
case is_valid_username(Username) of
true ->
case call({get_password, Username}) of
{ok, Password} ->
{ok, unicode:characters_to_list(Password)};
{error, no_secret} ->
{error, "No shared secret"};
{error, timeout} ->
{error, "Querying eturnal timed out"}
end;
false ->
?LOG_DEBUG("Invalid user name: ~s", [Username]),
{error, "Invalid user name: " ++ Username0}
case call({get_password, Username}) of
{ok, Password} ->
{ok, unicode:characters_to_list(Password)};
{error, no_credentials} ->
{error, "No shared secret and no credentials"};
{error, timeout} ->
{error, "Querying eturnal timed out"}
end;
{_, _, _} ->
?LOG_DEBUG("Cannot convert user name to binary: ~p", [Username0]),
Expand Down Expand Up @@ -208,17 +202,6 @@ reload() ->

%% Internal functions.

-spec is_valid_username(binary()) -> boolean().
is_valid_username(Username) ->
case string:to_integer(Username) of
{N, <<":", _Rest/binary>>} when is_integer(N), N > 0 ->
true;
{N, <<>>} when is_integer(N), N > 0 ->
true;
{_, _} ->
false
end.

-spec make_username(string(), string()) -> binary().
make_username(Expiry0, Suffix) ->
Expiry = try string:trim(Expiry0) of
Expand Down
2 changes: 2 additions & 0 deletions src/eturnal_yaml.erl
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ validator() ->
blacklist => blacklist_validator(),
whitelist => list_or_single(ip_mask()),
strict_expiry => bool(),
credentials => map(binary(), binary(), [unique, {return, map}]),
realm => non_empty(binary()),
software_name => non_empty(binary()),
run_dir => directory(write),
Expand Down Expand Up @@ -107,6 +108,7 @@ validator() ->
blacklist => ?DEFAULT_BLACKLIST,
whitelist => [],
strict_expiry => false,
credentials => #{},
realm => <<"eturnal.net">>,
secret => [get_default(secret, make_random_secret())],
software_name => <<"eturnal">>,
Expand Down
4 changes: 3 additions & 1 deletion test/eturnal_SUITE.erl
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,9 @@ check_credentials(_Config) ->
{error, _Reason2} = eturnal_ctl:get_credentials(Invalid, [])
end, ["Invalid", invalid, [invalid]]),
ct:pal("Checking invalid suffix"),
{error, _Reason} = eturnal_ctl:get_credentials(Username, invalid).
{error, _Reason} = eturnal_ctl:get_credentials(Username, invalid),
ct:pal("Checking static credentials"),
{ok, "l0vesBob"} = eturnal_ctl:get_password("alice").

-spec check_loglevel(config()) -> any().
check_loglevel(_Config) ->
Expand Down
2 changes: 2 additions & 0 deletions test/eturnal_SUITE_data/eturnal-new-otp.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ eturnal:
enable_turn: false
relay_ipv4_addr: "127.0.0.1"
secret: "crypt1c"
credentials:
alice: l0vesBob
log_level: debug
tls_options:
- no_tlsv1
Expand Down
2 changes: 2 additions & 0 deletions test/eturnal_SUITE_data/eturnal-old-otp.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ eturnal:
enable_turn: false
relay_ipv4_addr: "127.0.0.1"
secret: "crypt1c"
credentials:
alice: l0vesBob
log_level: debug
tls_options:
- no_tlsv1
Expand Down

0 comments on commit 803d863

Please sign in to comment.