feat(config): read secrets from NAME_FILE variables (#1421)

Co-authored-by: Hampus <[email protected]>
This commit is contained in:
Kai Compton
2026-10-06 21:43:08 +02:00
committed by GitHub
co-authored by Hampus
parent cc5545c333
commit 0c4f016ba2
17 changed files with 534 additions and 31 deletions
@@ -70,6 +70,21 @@ else
rm -f "$node_name_file"
fi
case "${FLUXER_ERLANG_COOKIE_FILE:-}" in
*[![:space:]]*)
case "${FLUXER_ERLANG_COOKIE:-}" in
*[![:space:]]*)
echo 'FLUXER_ERLANG_COOKIE and FLUXER_ERLANG_COOKIE_FILE are both set, set only one.' >&2
exit 1
;;
esac
if ! FLUXER_ERLANG_COOKIE="$(cat -- "$FLUXER_ERLANG_COOKIE_FILE")"; then
echo "FLUXER_ERLANG_COOKIE_FILE could not read $FLUXER_ERLANG_COOKIE_FILE." >&2
exit 1
fi
;;
esac
if [ -z "${FLUXER_ERLANG_COOKIE:-}" ]; then
echo 'FLUXER_ERLANG_COOKIE is required.' >&2
exit 1
@@ -18,4 +18,8 @@ if [ -r "$node_name_file" ]; then
FLUXER_ERLANG_NODE_NAME="$(cat "$node_name_file")"
export FLUXER_ERLANG_NODE_NAME
fi
if [ -r "${FLUXER_ERLANG_COOKIE_FILE:-}" ] && [ -z "$(printf '%s' "${FLUXER_ERLANG_COOKIE:-}" | tr -d '[:space:]')" ]; then
FLUXER_ERLANG_COOKIE="$(cat "$FLUXER_ERLANG_COOKIE_FILE")"
export FLUXER_ERLANG_COOKIE
fi
exec "$script_dir/fluxer_gateway.real" "$@"
@@ -305,11 +305,44 @@ env_string(Name, Default) ->
-spec env_value(string()) -> string() | undefined.
env_value(Name) ->
Value = non_blank_env(Name),
FileName = Name ++ "_FILE",
case non_blank_env(FileName) of
undefined -> Value;
Path when Value =:= undefined -> read_env_file(FileName, Path);
_ -> erlang:error({ambiguous_env, Name, FileName})
end.
-spec non_blank_env(string()) -> string() | undefined.
non_blank_env(Name) ->
case os:getenv(Name) of
false -> undefined;
Value -> non_blank(Value)
end.
-spec read_env_file(string(), string()) -> string() | undefined.
read_env_file(FileName, Path) ->
case file:read_file(Path) of
{ok, Contents} -> env_file_value(FileName, Path, strip_newline(Contents));
{error, Reason} -> erlang:error({unreadable_env_file, FileName, Path, Reason})
end.
-spec env_file_value(string(), string(), binary()) -> string() | undefined.
env_file_value(FileName, Path, Contents) ->
case unicode:characters_to_list(Contents) of
Value when is_list(Value) -> non_blank(Value);
_ -> erlang:error({invalid_env_file, FileName, Path})
end.
-spec strip_newline(binary()) -> binary().
strip_newline(Contents) ->
Size = byte_size(Contents),
case Contents of
<<Rest:(Size - 2)/binary, "\r\n">> -> Rest;
<<Rest:(Size - 1)/binary, "\n">> -> Rest;
_ -> Contents
end.
-spec non_blank(string()) -> string() | undefined.
non_blank(Value) ->
case string:trim(Value) of
@@ -272,6 +272,64 @@ public_endpoints_defaults_test() ->
?assertEqual(undefined, maps:get(media_proxy_endpoint, Config)),
?assertEqual(<<"http://localhost:8088">>, maps:get(static_cdn_endpoint, Config)).
env_value_reads_name_file_test() ->
with_env_file(<<"from-file\r\n">>, fun(Path) ->
with_envs(
[{"FLUXER_GATEWAY_TEST_SECRET", ""}, {"FLUXER_GATEWAY_TEST_SECRET_FILE", Path}],
fun() ->
?assertEqual(
"from-file", fluxer_gateway_config:env_value("FLUXER_GATEWAY_TEST_SECRET")
)
end
)
end).
env_value_trims_only_one_newline_test() ->
with_env_file(<<"line1\nline2\n\n">>, fun(Path) ->
with_env("FLUXER_GATEWAY_TEST_SECRET_FILE", Path, fun() ->
?assertEqual(
"line1\nline2\n", fluxer_gateway_config:env_value("FLUXER_GATEWAY_TEST_SECRET")
)
end)
end).
env_value_rejects_name_and_name_file_test() ->
with_envs(
[
{"FLUXER_GATEWAY_TEST_SECRET", "direct"},
{"FLUXER_GATEWAY_TEST_SECRET_FILE", "/run/secrets/x"}
],
fun() ->
?assertError(
{ambiguous_env, "FLUXER_GATEWAY_TEST_SECRET",
"FLUXER_GATEWAY_TEST_SECRET_FILE"},
fluxer_gateway_config:env_value("FLUXER_GATEWAY_TEST_SECRET")
)
end
).
env_value_rejects_missing_name_file_test() ->
Path = "/nonexistent/fluxer-gateway-test-secret",
with_env("FLUXER_GATEWAY_TEST_SECRET_FILE", Path, fun() ->
?assertError(
{unreadable_env_file, "FLUXER_GATEWAY_TEST_SECRET_FILE", Path, enoent},
fluxer_gateway_config:env_value("FLUXER_GATEWAY_TEST_SECRET")
)
end).
with_env_file(Contents, Fun) ->
Path = filename:join(
filename:basedir(user_cache, "fluxer_gateway_tests"),
integer_to_list(erlang:unique_integer([positive]))
),
ok = filelib:ensure_dir(Path),
ok = file:write_file(Path, Contents),
try
Fun(Path)
after
file:delete(Path)
end.
with_envs([], Fun) ->
Fun();
with_envs([{Name, Value} | Rest], Fun) ->