Skip to content

Commit 13d51d4

Browse files
author
Ransom Richardson
committed
Merge pull request erlcloud#177 from ncrothe/master
Update erlcloud to use AWS v4 signature and use for AS and EC2
2 parents 0399078 + eb451d2 commit 13d51d4

File tree

4 files changed

+67
-6
lines changed

4 files changed

+67
-6
lines changed

src/erlcloud_as.erl

+2-2
Original file line numberDiff line numberDiff line change
@@ -333,5 +333,5 @@ get_text(Label, Doc) ->
333333
-spec as_query(aws_config(), string(), list({string(), string()}), string()) -> {ok, term()} | {error, term}.
334334
as_query(Config, Action, Params, ApiVersion) ->
335335
QParams = [{"Action", Action}, {"Version", ApiVersion}|Params],
336-
erlcloud_aws:aws_request_xml2(post, Config#aws_config.as_host,
337-
"/", QParams, Config).
336+
erlcloud_aws:aws_request_xml4(post, Config#aws_config.as_host,
337+
"/", QParams, "autoscaling", Config).

src/erlcloud_aws.erl

+62-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
aws_request_xml/5, aws_request_xml/6, aws_request_xml/7, aws_request_xml/8,
55
aws_request2/7,
66
aws_request_xml2/5, aws_request_xml2/7,
7+
aws_request_xml4/6,aws_request_xml4/8,
78
aws_request_form/8,
89
param_list/2, default_config/0, update_config/1, format_timestamp/1,
910
http_headers_body/1,
@@ -43,6 +44,16 @@ aws_request_xml2(Method, Protocol, Host, Port, Path, Params, #aws_config{} = Con
4344
{error, Reason}
4445
end.
4546

47+
aws_request_xml4(Method, Host, Path, Params, Service, #aws_config{} = Config) ->
48+
aws_request_xml4(Method, undefined, Host, undefined, Path, Params, Service, Config).
49+
aws_request_xml4(Method, Protocol, Host, Port, Path, Params, Service, #aws_config{} = Config) ->
50+
case aws_request4(Method, Protocol, Host, Port, Path, Params, Service, Config) of
51+
{ok, Body} ->
52+
{ok, element(1, xmerl_scan:string(binary_to_list(Body)))};
53+
{error, Reason} ->
54+
{error, Reason}
55+
end.
56+
4657
aws_request(Method, Host, Path, Params, #aws_config{} = Config) ->
4758
aws_request(Method, undefined, Host, undefined, Path, Params, Config).
4859
aws_request(Method, Host, Path, Params, AccessKeyID, SecretAccessKey) ->
@@ -89,6 +100,38 @@ aws_request2_no_update(Method, Protocol, Host, Port, Path, Params, #aws_config{}
89100

90101
aws_request_form(Method, Protocol, Host, Port, Path, Query, [], Config).
91102

103+
aws_request4(Method, Protocol, Host, Port, Path, Params, Service, Config) ->
104+
case update_config(Config) of
105+
{ok, Config1} ->
106+
aws_request4_no_update(Method, Protocol, Host, Port, Path, Params, Service, Config1);
107+
{error, Reason} ->
108+
{error, Reason}
109+
end.
110+
111+
aws_request4_no_update(Method, Protocol, Host, Port, Path, Params, Service, #aws_config{} = Config) ->
112+
QueryToSign = erlcloud_http:make_query_string(Params),
113+
114+
Headers = [{"host", Host}],
115+
116+
Region =
117+
case string:tokens(Host, ".") of
118+
[_, Value, _, _] ->
119+
Value;
120+
_ ->
121+
"us-east-1"
122+
end,
123+
124+
SignedHeaders = case Method of
125+
get -> sign_v4(Method, Config, Headers, Params, "", Region, Service);
126+
post -> sign_v4(Method, Config, Headers, "", QueryToSign, Region, Service)
127+
end,
128+
129+
aws_request_form(Method, Protocol, Host, Port, Path, QueryToSign, SignedHeaders, Config).
130+
131+
132+
-spec aws_request_form(Method :: atom(), Protocol :: undefined | string(), Host :: string(),
133+
Port :: undefined | integer() | string(), Path :: string(), Form :: string(),
134+
Headers :: list(), Config :: aws_config()) -> {ok, binary()} | {error, tuple()}.
92135
aws_request_form(Method, Protocol, Host, Port, Path, Form, Headers, Config) ->
93136
UProtocol = case Protocol of
94137
undefined -> "https://";
@@ -276,13 +319,19 @@ request_to_return(#aws_request{response_type = error,
276319
%% TODO additional parameters - currently only supports what is needed for DynamoDB
277320
-spec sign_v4(aws_config(), headers(), binary(), string(), string()) -> headers().
278321
sign_v4(Config, Headers, Payload, Region, Service) ->
322+
sign_v4(post, Config, Headers, [], Payload, Region, Service).
323+
324+
-spec sign_v4(atom(), aws_config(), headers(), binary(), binary(), string(), string()) -> headers().
325+
sign_v4(Method, Config, Headers, QueryParams, Payload, Region, Service) ->
279326
Date = iso_8601_basic_time(),
280327
Headers1 = [{"x-amz-date", Date} | Headers],
281328
Headers2 = case Config#aws_config.security_token of
282329
undefined -> Headers1;
283330
Token -> [{"x-amz-security-token", Token} | Headers1]
284331
end,
285-
{Request, SignedHeaders} = canonical_request("POST", "/", "", Headers2, Payload),
332+
CanonicalQueryString = canonical_query_string(QueryParams),
333+
MethodString = string:to_upper(atom_to_list(Method)),
334+
{Request, SignedHeaders} = canonical_request(MethodString, "/", CanonicalQueryString, Headers2, Payload),
286335
CredentialScope = credential_scope(Date, Region, Service),
287336
ToSign = to_sign(Date, CredentialScope, Request),
288337
SigningKey = signing_key(Config, Date, Region, Service),
@@ -313,6 +362,18 @@ canonical_headers(Headers) ->
313362
Signed = string:join([Name || {Name, _} <- Sorted], ";"),
314363
{Canonical, Signed}.
315364

365+
%% @doc calculate canonical query string out of query params and according to v4 documentation
366+
canonical_query_string([]) ->
367+
"";
368+
canonical_query_string(Params) ->
369+
Normalized = [{erlcloud_http:url_encode(Name), erlcloud_http:url_encode(erlcloud_http:value_to_string(Value))} || {Name, Value} <- Params],
370+
Sorted = lists:keysort(1, Normalized),
371+
string:join([case Value of
372+
[] -> [Key, "="];
373+
_ -> [Key, "=", Value]
374+
end
375+
|| {Key, Value} <- Sorted, Value =/= none, Value =/= undefined], "&").
376+
316377
trimall(Value) ->
317378
%% TODO - remove excess internal whitespace in header values
318379
re:replace(Value, "(^\\s+)|(\\s+$)", "", [global]).

src/erlcloud_ec2.erl

+2-2
Original file line numberDiff line numberDiff line change
@@ -2765,8 +2765,8 @@ ec2_query2(Config, Action, Params) ->
27652765

27662766
ec2_query2(Config, Action, Params, ApiVersion) ->
27672767
QParams = [{"Action", Action}, {"Version", ApiVersion}|Params],
2768-
erlcloud_aws:aws_request_xml2(post, Config#aws_config.ec2_host,
2769-
"/", QParams, Config).
2768+
erlcloud_aws:aws_request_xml4(post, Config#aws_config.ec2_host,
2769+
"/", QParams, "ec2", Config).
27702770

27712771
default_config() -> erlcloud_aws:default_config().
27722772

src/erlcloud_http.erl

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
-module(erlcloud_http).
2-
-export([make_query_string/1, make_query_string/2, url_encode/1, url_encode_loose/1]).
2+
-export([make_query_string/1, make_query_string/2, value_to_string/1, url_encode/1, url_encode_loose/1]).
33

44
encode_query_term(Key, [], no_assignment) ->
55
[Key];

0 commit comments

Comments
 (0)