Skip to content

Commit

Permalink
Merge pull request esl#18 from talko/iamrole
Browse files Browse the repository at this point in the history
IAM Role and temporary security credential support
  • Loading branch information
gleber committed Jan 14, 2013
2 parents 680282e + 97f69c0 commit 64f1db0
Show file tree
Hide file tree
Showing 10 changed files with 65 additions and 27 deletions.
5 changes: 3 additions & 2 deletions include/erlcloud_aws.hrl
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@
mon_host="monitoring.amazonaws.com"::string(),
mon_port=undefined::non_neg_integer()|undefined,
mon_protocol=undefined::string()|undefined,
access_key_id::string(),
secret_access_key::string()
access_key_id::string()|undefined|false,
secret_access_key::string()|undefined|false,
security_token=undefined::string()|undefined
}).
-type(aws_config() :: #aws_config{}).

3 changes: 2 additions & 1 deletion rebar.config
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
{erl_opts, [debug_info]}.

{deps, [{meck, ".*", {git, "https://github.com/eproxus/meck.git", "master"}}]}.
{deps, [{meck, ".*", {git, "https://github.com/eproxus/meck.git", "master"}},
{jsx, ".*", {git, "git://github.com/talentdeficit/jsx.git", "master"}}]}.
3 changes: 2 additions & 1 deletion src/erlcloud.app.src
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
public_key,
ssl,
xmerl,
inets]},
inets,
jsx]},
{modules, []},
{env, []}
]
Expand Down
52 changes: 44 additions & 8 deletions src/erlcloud_aws.erl
Original file line number Diff line number Diff line change
@@ -1,32 +1,43 @@
-module(erlcloud_aws).
-export([aws_request/6,
aws_request/8,
aws_request_xml/6,
aws_request_xml/8,
param_list/2, default_config/0, format_timestamp/1]).
-export([aws_request/5, aws_request/6, aws_request/7, aws_request/8,
aws_request_xml/5, aws_request_xml/6, aws_request_xml/7, aws_request_xml/8,
param_list/2, default_config/0, update_config/1, format_timestamp/1]).

-include_lib("erlcloud/include/erlcloud_aws.hrl").

aws_request_xml(Method, Host, Path, Params, #aws_config{} = Config) ->
Body = aws_request(Method, Host, Path, Params, Config),
element(1, xmerl_scan:string(Body)).
aws_request_xml(Method, Host, Path, Params, AccessKeyID, SecretAccessKey) ->
Body = aws_request(Method, Host, Path, Params, AccessKeyID, SecretAccessKey),
element(1, xmerl_scan:string(Body)).
aws_request_xml(Method, Protocol, Host, Port, Path, Params, #aws_config{} = Config) ->
Body = aws_request(Method, Protocol, Host, Port, Path, Params, Config),
element(1, xmerl_scan:string(Body)).
aws_request_xml(Method, Protocol, Host, Port, Path, Params, AccessKeyID, SecretAccessKey) ->
Body = aws_request(Method, Protocol, Host, Port, Path, Params, AccessKeyID, SecretAccessKey),
element(1, xmerl_scan:string(Body)).

aws_request(Method, Host, Path, Params, #aws_config{} = Config) ->
aws_request(Method, undefined, Host, undefined, Path, Params, Config).
aws_request(Method, Host, Path, Params, AccessKeyID, SecretAccessKey) ->
aws_request(Method, undefined, Host, undefined, Path, Params, AccessKeyID, SecretAccessKey).
aws_request(Method, Protocol, Host, Port, Path, Params, AccessKeyID, SecretAccessKey) ->
aws_request(Method, Protocol, Host, Port, Path, Params, #aws_config{} = Config0) ->
Config = update_config(Config0),
Timestamp = format_timestamp(erlang:universaltime()),
QParams = lists:sort([{"Timestamp", Timestamp},
{"SignatureVersion", "2"},
{"SignatureMethod", "HmacSHA1"},
{"AWSAccessKeyId", AccessKeyID}|Params]),
{"AWSAccessKeyId", Config#aws_config.access_key_id}|Params] ++
case Config#aws_config.security_token of
undefined -> [];
Token -> [{"SecurityToken", Token}]
end),

QueryToSign = erlcloud_http:make_query_string(QParams),
RequestToSign = [string:to_upper(atom_to_list(Method)), $\n,
string:to_lower(Host), $\n, Path, $\n, QueryToSign],
Signature = base64:encode(crypto:sha_mac(SecretAccessKey, RequestToSign)),
Signature = base64:encode(crypto:sha_mac(Config#aws_config.secret_access_key, RequestToSign)),

Query = [QueryToSign, "&Signature=", erlcloud_http:url_encode(Signature)],

Expand Down Expand Up @@ -60,6 +71,9 @@ aws_request(Method, Protocol, Host, Port, Path, Params, AccessKeyID, SecretAcces
{error, Error} ->
erlang:error({aws_error, {socket_error, Error}})
end.
aws_request(Method, Protocol, Host, Port, Path, Params, AccessKeyID, SecretAccessKey) ->
aws_request(Method, Protocol, Host, Port, Path, Params,
#aws_config{access_key_id = AccessKeyID, secret_access_key = SecretAccessKey}).

param_list([], _Key) -> [];
param_list(Values, Key) when is_tuple(Key) ->
Expand Down Expand Up @@ -99,6 +113,28 @@ default_config() ->
Config
end.

-spec update_config(aws_config()) -> aws_config().
update_config(#aws_config{access_key_id = KeyId} = Config)
when is_list(KeyId) ->
%% In order to support caching of the aws_config, we could store the expiration_time
%% and check it here. If it is about to expire (within 5 minutes is what boto uses)
%% then we should get the new config.
Config;
update_config(#aws_config{} = Config) ->
%% AccessKey is not set. Try to read from role metadata.
%% First get the list of roles
{ok, {{_, 200, _}, _, Body}} =
httpc:request("http://169.254.169.254/latest/meta-data/iam/security-credentials/"),
%% Always use the first role
Role = string:sub_word(Body, 1, $\n),
{ok, {{_, 200, _}, _, Json}} =
httpc:request("http://169.254.169.254/latest/meta-data/iam/security-credentials/" ++ Role),
Credentials = jsx:decode(list_to_binary(Json)),
Config#aws_config{
access_key_id = binary_to_list(proplists:get_value(<<"AccessKeyId">>, Credentials)),
secret_access_key = binary_to_list(proplists:get_value(<<"SecretAccessKey">>, Credentials)),
security_token = binary_to_list(proplists:get_value(<<"Token">>, Credentials))}.

port_to_str(Port) when is_integer(Port) ->
integer_to_list(Port);
port_to_str(Port) when is_list(Port) ->
Expand Down
3 changes: 1 addition & 2 deletions src/erlcloud_ec2.erl
Original file line number Diff line number Diff line change
Expand Up @@ -1434,7 +1434,6 @@ ec2_query(Config, Action, Params) ->
ec2_query(Config, Action, Params, ApiVersion) ->
QParams = [{"Action", Action}, {"Version", ApiVersion}|Params],
erlcloud_aws:aws_request_xml(post, Config#aws_config.ec2_host,
"/", QParams, Config#aws_config.access_key_id,
Config#aws_config.secret_access_key).
"/", QParams, Config).

default_config() -> erlcloud_aws:default_config().
3 changes: 1 addition & 2 deletions src/erlcloud_elb.erl
Original file line number Diff line number Diff line change
Expand Up @@ -136,8 +136,7 @@ describe_load_balancers(Names, Config) ->
elb_request(Config, Action, Params) ->
QParams = [{"Action", Action}, {"Version", ?API_VERSION} | Params],
erlcloud_aws:aws_request_xml(get, Config#aws_config.elb_host,
"/", QParams, Config#aws_config.access_key_id,
Config#aws_config.secret_access_key).
"/", QParams, Config).

elb_simple_request(Config, Action, Params) ->
_Doc = elb_request(Config, Action, Params),
Expand Down
3 changes: 1 addition & 2 deletions src/erlcloud_mon.erl
Original file line number Diff line number Diff line change
Expand Up @@ -257,8 +257,7 @@ mon_query(Config, Action, Params, ApiVersion) ->
Config#aws_config.mon_port,
"/",
QParams,
Config#aws_config.access_key_id,
Config#aws_config.secret_access_key).
Config).

default_config() -> erlcloud_aws:default_config().

Expand Down
11 changes: 8 additions & 3 deletions src/erlcloud_s3.erl
Original file line number Diff line number Diff line change
Expand Up @@ -714,17 +714,22 @@ s3_xml_request(Config, Method, Host, Path, Subresource, Params, POSTData, Header
XML
end.

s3_request(Config, Method, Host, Path, Subresource, Params, POSTData, Headers) ->
s3_request(Config0, Method, Host, Path, Subresource, Params, POSTData, Headers0) ->
{ContentMD5, ContentType, Body} =
case POSTData of
{PD, CT} -> {base64:encode(crypto:md5(PD)), CT, PD}; PD -> {"", "", PD}
end,
AmzHeaders = lists:filter(fun ({"x-amz-" ++ _, V}) when V =/= undefined -> true; (_) -> false end, Headers),
Config = erlcloud_aws:update_config(Config0),
Headers = case Config#aws_config.security_token of
undefined -> Headers0;
Token when is_list(Token) -> [{"x-amz-security-token", Token} | Headers0]
end,
FHeaders = [Header || {_, Value} = Header <- Headers, Value =/= undefined],
AmzHeaders = [Header || {"x-amz-" ++ _, _} = Header <- FHeaders],
Date = httpd_util:rfc1123_date(erlang:localtime()),
EscapedPath = erlcloud_http:url_encode_loose(Path),
Authorization = make_authorization(Config, Method, ContentMD5, ContentType,
Date, AmzHeaders, Host, EscapedPath, Subresource),
FHeaders = [Header || {_, Value} = Header <- Headers, Value =/= undefined],
RequestHeaders = [{"date", Date}, {"authorization", Authorization}|FHeaders] ++
case ContentMD5 of
"" -> [];
Expand Down
3 changes: 1 addition & 2 deletions src/erlcloud_sdb.erl
Original file line number Diff line number Diff line change
Expand Up @@ -256,8 +256,7 @@ extract_item(Item) ->
sdb_request(Config, Action, Params) ->
QParams = [{"Action", Action}, {"Version", ?API_VERSION}|Params],
Doc = erlcloud_aws:aws_request_xml(post, Config#aws_config.sdb_host,
"/", QParams, Config#aws_config.access_key_id,
Config#aws_config.secret_access_key),
"/", QParams, Config),
{Doc, [{box_usage, erlcloud_xml:get_float("/*/ResponseMetadata/BoxUsage", Doc)}]}.

sdb_simple_request(Config, Action, Params) ->
Expand Down
6 changes: 2 additions & 4 deletions src/erlcloud_sqs.erl
Original file line number Diff line number Diff line change
Expand Up @@ -309,13 +309,11 @@ sqs_simple_request(Config, QueueName, Action, Params) ->

sqs_xml_request(Config, QueueName, Action, Params) ->
erlcloud_aws:aws_request_xml(post, Config#aws_config.sqs_host,
queue_path(QueueName), [{"Action", Action}, {"Version", ?API_VERSION}|Params], Config#aws_config.access_key_id,
Config#aws_config.secret_access_key).
queue_path(QueueName), [{"Action", Action}, {"Version", ?API_VERSION}|Params], Config).

sqs_request(Config, QueueName, Action, Params) ->
erlcloud_aws:aws_request(post, Config#aws_config.sqs_host,
queue_path(QueueName), [{"Action", Action}, {"Version", ?API_VERSION}|Params], Config#aws_config.access_key_id,
Config#aws_config.secret_access_key).
queue_path(QueueName), [{"Action", Action}, {"Version", ?API_VERSION}|Params], Config).

queue_path([$/|_] = QueueName) -> QueueName;
queue_path(["http"|_] = URL) ->
Expand Down

0 comments on commit 64f1db0

Please sign in to comment.