555 lines
21 KiB
Erlang
555 lines
21 KiB
Erlang
-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"
|
||
).
|