rabbitmq-server/deps/rabbitmq_aws/test/rabbitmq_aws_config_tests.erl

555 lines
21 KiB
Erlang
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

-module(rabbitmq_aws_config_tests).
-include_lib("eunit/include/eunit.hrl").
-include("rabbitmq_aws.hrl").
config_file_test_() ->
[
{"from environment variable", fun() ->
os:putenv("AWS_CONFIG_FILE", "/etc/aws/config"),
?assertEqual("/etc/aws/config", rabbitmq_aws_config:config_file())
end},
{"default without environment variable", fun() ->
os:unsetenv("AWS_CONFIG_FILE"),
os:putenv("HOME", "/home/rrabbit"),
?assertEqual(
"/home/rrabbit/.aws/config",
rabbitmq_aws_config:config_file()
)
end}
].
config_file_data_test_() ->
[
{"successfully parses ini", fun() ->
setup_test_config_env_var(),
Expectation = [
{"default", [
{aws_access_key_id, "default-key"},
{aws_secret_access_key, "default-access-key"},
{region, "us-east-4"}
]},
{"profile testing", [
{aws_access_key_id, "foo1"},
{aws_secret_access_key, "bar2"},
{s3, [
{max_concurrent_requests, 10},
{max_queue_size, 1000}
]},
{region, "us-west-5"}
]},
{"profile no-region", [
{aws_access_key_id, "foo2"},
{aws_secret_access_key, "bar3"}
]},
{"profile only-key", [{aws_access_key_id, "foo3"}]},
{"profile only-secret", [{aws_secret_access_key, "foo4"}]},
{"profile bad-entry", [{aws_secret_access, "foo5"}]}
],
?assertEqual(
Expectation,
rabbitmq_aws_config:config_file_data()
)
end},
{"file does not exist", fun() ->
?assertEqual(
{error, enoent},
rabbitmq_aws_config:ini_file_data(
filename:join([filename:absname("."), "bad_path"]), false
)
)
end},
{"file exists but path is invalid", fun() ->
?assertEqual(
{error, enoent},
rabbitmq_aws_config:ini_file_data(
filename:join([filename:absname("."), "bad_path"]), true
)
)
end}
].
instance_metadata_test_() ->
[
{"instance role URL", fun() ->
?assertEqual(
"http://169.254.169.254/latest/meta-data/iam/security-credentials",
rabbitmq_aws_config:instance_role_url()
)
end},
{"availability zone URL", fun() ->
?assertEqual(
"http://169.254.169.254/latest/meta-data/placement/availability-zone",
rabbitmq_aws_config:instance_availability_zone_url()
)
end},
{"instance id URL", fun() ->
?assertEqual(
"http://169.254.169.254/latest/meta-data/instance-id",
rabbitmq_aws_config:instance_id_url()
)
end},
{"arbitrary paths", fun() ->
?assertEqual(
"http://169.254.169.254/a/b/c", rabbitmq_aws_config:instance_metadata_url("a/b/c")
),
?assertEqual(
"http://169.254.169.254/a/b/c", rabbitmq_aws_config:instance_metadata_url("/a/b/c")
)
end}
].
credentials_file_test_() ->
[
{"from environment variable", fun() ->
os:putenv("AWS_SHARED_CREDENTIALS_FILE", "/etc/aws/credentials"),
?assertEqual("/etc/aws/credentials", rabbitmq_aws_config:credentials_file())
end},
{"default without environment variable", fun() ->
os:unsetenv("AWS_SHARED_CREDENTIALS_FILE"),
os:putenv("HOME", "/home/rrabbit"),
?assertEqual(
"/home/rrabbit/.aws/credentials",
rabbitmq_aws_config:credentials_file()
)
end}
].
credentials_test_() ->
{
foreach,
fun() ->
meck:new(httpc),
meck:new(rabbitmq_aws),
reset_environment(),
[httpc, rabbitmq_aws]
end,
fun meck:unload/1,
[
{"from environment variables", fun() ->
os:putenv("AWS_ACCESS_KEY_ID", "Sésame"),
os:putenv("AWS_SECRET_ACCESS_KEY", "ouvre-toi"),
?assertEqual(
{ok, "Sésame", "ouvre-toi", undefined, undefined},
rabbitmq_aws_config:credentials()
)
end},
{"from environment variables with session token", fun() ->
os:putenv("AWS_ACCESS_KEY_ID", "Sésame"),
os:putenv("AWS_SECRET_ACCESS_KEY", "ouvre-toi"),
os:putenv("AWS_SESSION_TOKEN", "session42"),
?assertEqual(
{ok, "Sésame", "ouvre-toi", undefined, "session42"},
rabbitmq_aws_config:credentials()
)
end},
{"from config file with default profile", fun() ->
setup_test_config_env_var(),
?assertEqual(
{ok, "default-key", "default-access-key", undefined, undefined},
rabbitmq_aws_config:credentials()
)
end},
{"with missing environment variable", fun() ->
os:putenv("AWS_ACCESS_KEY_ID", "Sésame"),
meck:sequence(rabbitmq_aws, ensure_imdsv2_token_valid, 0, "secret_imdsv2_token"),
?assertEqual(
{error, undefined},
rabbitmq_aws_config:credentials()
)
end},
{"from config file with default profile", fun() ->
setup_test_config_env_var(),
?assertEqual(
{ok, "default-key", "default-access-key", undefined, undefined},
rabbitmq_aws_config:credentials()
)
end},
{"from config file with profile", fun() ->
setup_test_config_env_var(),
?assertEqual(
{ok, "foo1", "bar2", undefined, undefined},
rabbitmq_aws_config:credentials("testing")
)
end},
{"from config file with bad profile", fun() ->
setup_test_config_env_var(),
meck:expect(rabbitmq_aws, ensure_imdsv2_token_valid, 0, undefined),
?assertEqual(
{error, undefined},
rabbitmq_aws_config:credentials("bad-profile-name")
)
end},
{"from credentials file with default profile", fun() ->
setup_test_credentials_env_var(),
?assertEqual(
{ok, "foo1", "bar1", undefined, undefined},
rabbitmq_aws_config:credentials()
)
end},
{"from credentials file with profile", fun() ->
setup_test_credentials_env_var(),
?assertEqual(
{ok, "foo2", "bar2", undefined, undefined},
rabbitmq_aws_config:credentials("development")
)
end},
{"from credentials file with session token", fun() ->
setup_test_credentials_env_var(),
?assertEqual(
{ok, "foo3", "bar3", undefined, "session42"},
rabbitmq_aws_config:credentials("with-session-token")
)
end},
{"from credentials file with bad profile", fun() ->
setup_test_credentials_env_var(),
meck:expect(rabbitmq_aws, ensure_imdsv2_token_valid, 0, undefined),
?assertEqual(
{error, undefined},
rabbitmq_aws_config:credentials("bad-profile-name")
)
end},
{"from credentials file with only the key in profile", fun() ->
setup_test_credentials_env_var(),
meck:expect(rabbitmq_aws, ensure_imdsv2_token_valid, 0, undefined),
?assertEqual(
{error, undefined},
rabbitmq_aws_config:credentials("only-key")
)
end},
{"from credentials file with only the value in profile", fun() ->
setup_test_credentials_env_var(),
meck:expect(rabbitmq_aws, ensure_imdsv2_token_valid, 0, undefined),
?assertEqual(
{error, undefined},
rabbitmq_aws_config:credentials("only-value")
)
end},
{"from credentials file with missing keys in profile", fun() ->
setup_test_credentials_env_var(),
meck:expect(rabbitmq_aws, ensure_imdsv2_token_valid, 0, undefined),
?assertEqual(
{error, undefined},
rabbitmq_aws_config:credentials("bad-entry")
)
end},
{"from instance metadata service", fun() ->
CredsBody =
"{\n \"Code\" : \"Success\",\n \"LastUpdated\" : \"2016-03-31T21:51:49Z\",\n \"Type\" : \"AWS-HMAC\",\n \"AccessKeyId\" : \"ASIAIMAFAKEACCESSKEY\",\n \"SecretAccessKey\" : \"2+t64tZZVaz0yp0x1G23ZRYn+FAKEyVALUEs/4qh\",\n \"Token\" : \"FAKE//////////wEAK/TOKEN/VALUE=\",\n \"Expiration\" : \"2016-04-01T04:13:28Z\"\n}",
meck:sequence(
httpc,
request,
4,
[
{ok, {{protocol, 200, message}, headers, "Bob"}},
{ok, {{protocol, 200, message}, headers, CredsBody}}
]
),
meck:expect(rabbitmq_aws, ensure_imdsv2_token_valid, 0, undefined),
Expectation =
{ok, "ASIAIMAFAKEACCESSKEY", "2+t64tZZVaz0yp0x1G23ZRYn+FAKEyVALUEs/4qh",
{{2016, 4, 1}, {4, 13, 28}}, "FAKE//////////wEAK/TOKEN/VALUE="},
?assertEqual(Expectation, rabbitmq_aws_config:credentials())
end},
{"with instance metadata service role error", fun() ->
meck:expect(rabbitmq_aws, ensure_imdsv2_token_valid, 0, undefined),
meck:expect(httpc, request, 4, {error, timeout}),
?assertEqual({error, undefined}, rabbitmq_aws_config:credentials())
end},
{"with instance metadata service role http error", fun() ->
meck:expect(rabbitmq_aws, ensure_imdsv2_token_valid, 0, undefined),
meck:expect(
httpc,
request,
4,
{ok, {{protocol, 500, message}, headers, "Internal Server Error"}}
),
?assertEqual({error, undefined}, rabbitmq_aws_config:credentials())
end},
{"with instance metadata service credentials error", fun() ->
meck:expect(rabbitmq_aws, ensure_imdsv2_token_valid, 0, undefined),
meck:sequence(
httpc,
request,
4,
[
{ok, {{protocol, 200, message}, headers, "Bob"}},
{error, timeout}
]
),
?assertEqual({error, undefined}, rabbitmq_aws_config:credentials())
end},
{"with instance metadata service credentials not found", fun() ->
meck:expect(rabbitmq_aws, ensure_imdsv2_token_valid, 0, undefined),
meck:sequence(
httpc,
request,
4,
[
{ok, {{protocol, 200, message}, headers, "Bob"}},
{ok, {{protocol, 404, message}, headers, "File Not Found"}}
]
),
?assertEqual({error, undefined}, rabbitmq_aws_config:credentials())
end}
]
}.
home_path_test_() ->
[
{"with HOME", fun() ->
os:putenv("HOME", "/home/rrabbit"),
?assertEqual(
"/home/rrabbit",
rabbitmq_aws_config:home_path()
)
end},
{"without HOME", fun() ->
os:unsetenv("HOME"),
?assertEqual(
filename:absname("."),
rabbitmq_aws_config:home_path()
)
end}
].
ini_format_key_test_() ->
[
{"when value is list", fun() ->
?assertEqual(test_key, rabbitmq_aws_config:ini_format_key("test_key"))
end},
{"when value is binary", fun() ->
?assertEqual({error, type}, rabbitmq_aws_config:ini_format_key(<<"test_key">>))
end}
].
maybe_convert_number_test_() ->
[
{"when string contains an integer", fun() ->
?assertEqual(123, rabbitmq_aws_config:maybe_convert_number("123"))
end},
{"when string contains a float", fun() ->
?assertEqual(123.456, rabbitmq_aws_config:maybe_convert_number("123.456"))
end},
{"when string does not contain a number", fun() ->
?assertEqual("hello, world", rabbitmq_aws_config:maybe_convert_number("hello, world"))
end}
].
parse_iso8601_test_() ->
[
{"parse test", fun() ->
Value = "2016-05-19T18:25:23Z",
Expectation = {{2016, 5, 19}, {18, 25, 23}},
?assertEqual(Expectation, rabbitmq_aws_config:parse_iso8601_timestamp(Value))
end}
].
profile_test_() ->
[
{"from environment variable", fun() ->
os:putenv("AWS_DEFAULT_PROFILE", "httpc-aws test"),
?assertEqual("httpc-aws test", rabbitmq_aws_config:profile())
end},
{"default without environment variable", fun() ->
os:unsetenv("AWS_DEFAULT_PROFILE"),
?assertEqual("default", rabbitmq_aws_config:profile())
end}
].
read_file_test_() ->
[
{"file does not exist", fun() ->
?assertEqual(
{error, enoent},
rabbitmq_aws_config:read_file(filename:join([filename:absname("."), "bad_path"]))
)
end}
].
region_test_() ->
{
foreach,
fun() ->
meck:new(httpc),
meck:new(rabbitmq_aws),
reset_environment(),
[httpc, rabbitmq_aws]
end,
fun meck:unload/1,
[
{"with environment variable", fun() ->
os:putenv("AWS_DEFAULT_REGION", "us-west-1"),
?assertEqual({ok, "us-west-1"}, rabbitmq_aws_config:region())
end},
{"with config file and specified profile", fun() ->
setup_test_config_env_var(),
?assertEqual({ok, "us-west-5"}, rabbitmq_aws_config:region("testing"))
end},
{"with config file using default profile", fun() ->
setup_test_config_env_var(),
?assertEqual({ok, "us-east-4"}, rabbitmq_aws_config:region())
end},
{"missing profile in config", fun() ->
setup_test_config_env_var(),
meck:expect(rabbitmq_aws, ensure_imdsv2_token_valid, 0, undefined),
?assertEqual({ok, ?DEFAULT_REGION}, rabbitmq_aws_config:region("no-region"))
end},
{"from instance metadata service", fun() ->
meck:expect(rabbitmq_aws, ensure_imdsv2_token_valid, 0, undefined),
meck:expect(
httpc,
request,
4,
{ok, {{protocol, 200, message}, headers, "us-west-1a"}}
),
?assertEqual({ok, "us-west-1"}, rabbitmq_aws_config:region())
end},
{"full lookup failure", fun() ->
meck:expect(rabbitmq_aws, ensure_imdsv2_token_valid, 0, undefined),
?assertEqual({ok, ?DEFAULT_REGION}, rabbitmq_aws_config:region())
end},
{"http error failure", fun() ->
meck:expect(rabbitmq_aws, ensure_imdsv2_token_valid, 0, undefined),
meck:expect(
httpc,
request,
4,
{ok, {{protocol, 500, message}, headers, "Internal Server Error"}}
),
?assertEqual({ok, ?DEFAULT_REGION}, rabbitmq_aws_config:region())
end}
]
}.
instance_id_test_() ->
{
foreach,
fun() ->
meck:new(httpc),
meck:new(rabbitmq_aws),
reset_environment(),
[httpc, rabbitmq_aws]
end,
fun meck:unload/1,
[
{"get instance id successfully", fun() ->
meck:expect(rabbitmq_aws, ensure_imdsv2_token_valid, 0, undefined),
meck:expect(
httpc, request, 4, {ok, {{protocol, 200, message}, headers, "instance-id"}}
),
?assertEqual({ok, "instance-id"}, rabbitmq_aws_config:instance_id())
end},
{"getting instance id is rejected with invalid token error", fun() ->
meck:expect(rabbitmq_aws, ensure_imdsv2_token_valid, 0, "invalid"),
meck:expect(
httpc, request, 4, {error, {{protocol, 401, message}, headers, "Invalid token"}}
),
?assertEqual({error, undefined}, rabbitmq_aws_config:instance_id())
end},
{"getting instance id is rejected with access denied error", fun() ->
meck:expect(rabbitmq_aws, ensure_imdsv2_token_valid, 0, "expired token"),
meck:expect(
httpc, request, 4, {error, {{protocol, 403, message}, headers, "access denied"}}
),
?assertEqual({error, undefined}, rabbitmq_aws_config:instance_id())
end}
]
}.
load_imdsv2_token_test_() ->
{
foreach,
fun() ->
meck:new(httpc),
[httpc]
end,
fun meck:unload/1,
[
{"fail to get imdsv2 token - timeout", fun() ->
meck:expect(httpc, request, 4, {error, timeout}),
?assertEqual(undefined, rabbitmq_aws_config:load_imdsv2_token())
end},
{"fail to get imdsv2 token - PUT request is not valid", fun() ->
meck:expect(
httpc,
request,
4,
{error, {
{protocol, 400, messge},
headers,
"Missing or Invalid Parameters The PUT request is not valid."
}}
),
?assertEqual(undefined, rabbitmq_aws_config:load_imdsv2_token())
end},
{"successfully get imdsv2 token from instance metadata service", fun() ->
IMDSv2Token = "super_secret_token_value",
meck:sequence(
httpc,
request,
4,
[{ok, {{protocol, 200, message}, headers, IMDSv2Token}}]
),
?assertEqual(IMDSv2Token, rabbitmq_aws_config:load_imdsv2_token())
end}
]
}.
maybe_imdsv2_token_headers_test_() ->
{
foreach,
fun() ->
meck:new(rabbitmq_aws),
[rabbitmq_aws]
end,
fun meck:unload/1,
[
{"imdsv2 token is not available", fun() ->
meck:expect(rabbitmq_aws, ensure_imdsv2_token_valid, 0, undefined),
?assertEqual([], rabbitmq_aws_config:maybe_imdsv2_token_headers())
end},
{"imdsv2 is available", fun() ->
IMDSv2Token = "super_secret_token_value ;)",
meck:expect(rabbitmq_aws, ensure_imdsv2_token_valid, 0, IMDSv2Token),
?assertEqual(
[{"X-aws-ec2-metadata-token", IMDSv2Token}],
rabbitmq_aws_config:maybe_imdsv2_token_headers()
)
end}
]
}.
reset_environment() ->
os:unsetenv("AWS_ACCESS_KEY_ID"),
os:unsetenv("AWS_DEFAULT_REGION"),
os:unsetenv("AWS_SECRET_ACCESS_KEY"),
setup_test_file_with_env_var("AWS_CONFIG_FILE", "bad_config.ini"),
setup_test_file_with_env_var(
"AWS_SHARED_CREDENTIALS_FILE",
"bad_credentials.ini"
),
meck:expect(httpc, request, 4, {error, timeout}).
setup_test_config_env_var() ->
setup_test_file_with_env_var("AWS_CONFIG_FILE", "test_aws_config.ini").
setup_test_file_with_env_var(EnvVar, Filename) ->
os:putenv(
EnvVar,
filename:join([
filename:absname("."),
"test",
Filename
])
).
setup_test_credentials_env_var() ->
setup_test_file_with_env_var(
"AWS_SHARED_CREDENTIALS_FILE",
"test_aws_credentials.ini"
).