Current section

14 Versions

Jump to

Compare versions

6 files changed
+72 additions
-11 deletions
  @@ -16,3 +16,6 @@ CHANGELOG
16 16
17 17 ---- 0.1.0 / 2014-09-13 / ------------------------------------------------------
18 18 * initial release to hex.pm
19 +
20 + ---- 0.1.2 / 2014-09-17 / metadata-credentials ---------------------------------
21 + * retrieve access keys from IAM roles when running in EC2
  @@ -16,7 +16,7 @@ Install the [Hex.pm](http://hex.pm) package
16 16 ```elixir
17 17 def deps do
18 18 [
19 - {:simplex, "0.1.0"}
19 + {:simplex, "0.1.2"}
20 20 ]
21 21 end
22 22 ```
  @@ -46,6 +46,8 @@ There are two ways to provide your keys to the Simplex library:
46 46 SIMPLEX_AWS_ACCESS_KEY=your-access-key SIMPLEX_AWS_SECRET_ACCESS_KEY=your-secret-access-key iex -S mix
47 47 ```
48 48
49 + 3. If not provided by the above two methods Simplex will attempt to retrieve keys from [instance metadata](http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/iam-roles-for-amazon-ec2.html) if it's running in EC2 and you launched your instance with an IAM role with permission to access SimpleDB.
50 +
49 51 #### SimpleDB URL
50 52
51 53 By default Simplex will send all requests to the us-east-1 SimpleDB url: `https://sdb.amazonaws.com`. If you want to use a [different region](http://docs.aws.amazon.com/general/latest/gr/rande.html#sdb_region) you can change the url by:
  @@ -121,13 +123,13 @@ You can pattern match to determine how to handle the response:
121 123 [Put](http://docs.aws.amazon.com/AmazonSimpleDB/latest/DeveloperGuide/SDB_API_PutAttributes.html) attributes on an item (or create a new item).
122 124
123 125 ````elixir
124 - # Attribute values can be strings, lists of strings, or a
126 + # Attribute values can be strings, lists of strings, or a
125 127 # two-element tuple of :replace and a string or list of strings
126 128 # The replace tuple indicates that the value should replace the existing
127 129 # value for that attribute, rather than be added to its values
128 -
129 - Simplex.Attributes.put("your_domain",
130 - "your_item_name",
130 +
131 + Simplex.Attributes.put("your_domain",
132 + "your_item_name",
131 133 %{"some_key" => "some_value",
132 134 "another_key" => ["a", "list", "of", "values"],
133 135 "yet_another_key" => {:replace, "a value to replace the existing value(s) of yet_another_key"},
  @@ -136,7 +138,7 @@ You can pattern match to determine how to handle the response:
136 138 # put "some_value" in the "some_key" attribute only if
137 139 # "other_key" has the "other_value" value
138 140 Simplex.Attributes.put("your_domain",
139 - "your_item_name",
141 + "your_item_name",
140 142 %{"some_key" => "some_value"},
141 143 %{"Name" => "other_key", "Value" => "other_value"})
142 144 ````
  @@ -1,5 +1,5 @@
1 1 ---
2 2 major: 0
3 3 minor: 1
4 - patch: 1
4 + patch: 2
5 5 pre:
  @@ -15,6 +15,7 @@
15 15 {<<"links">>,#{<<"github">> => <<"https://github.com/adamkittelson/simplex">>}}.
16 16 {<<"requirements">>,
17 17 #{<<"httpoison">> => #{<<"optional">> => nil,<<"requirement">> => <<"~> 0.4.2">>},
18 + <<"poison">> => #{<<"optional">> => nil,<<"requirement">> => <<"~> 1.2.0">>},
18 19 <<"sweet_xml">> => #{<<"optional">> => nil,<<"requirement">> => <<"~> 0.1.1">>},
19 20 <<"timex">> => #{<<"optional">> => nil,<<"requirement">> => <<"~> 0.12.5">>}}}.
20 - {<<"version">>,<<"0.1.1">>}.
21 + {<<"version">>,<<"0.1.2">>}.
  @@ -1,5 +1,6 @@
1 1 defmodule Simplex.Config do
2 2 use GenServer
3 + use Timex
3 4
4 5 #############
5 6 # Client API
  @@ -28,6 +29,43 @@ defmodule Simplex.Config do
28 29 GenServer.cast(__MODULE__, {:set_simpledb_url, url})
29 30 end
30 31
32 + def needs_refresh?(:aws_access_key, config) do
33 + expiring?(config) or (!config[:aws_access_key] and !System.get_env("SIMPLEX_AWS_ACCESS_KEY"))
34 + end
35 +
36 + def needs_refresh?(:aws_secret_access_key, config) do
37 + expiring?(config) or (!config[:aws_secret_access_key] and !System.get_env("SIMPLEX_AWS_SECRET_ACCESS_KEY"))
38 + end
39 +
40 + # keys expired or expiring within the next 60 seconds
41 + def expiring?(%{:expires_at => nil}), do: false
42 + def expiring?(%{:expires_at => expires_at}) do
43 + expires_at = DateFormat.parse!(expires_at, "{ISOz}")
44 + Date.shift(Date.now, secs: 60) > expires_at
45 + end
46 + def expiring?(_config), do: false
47 +
48 + def load_credentials_from_metadata do
49 + try do
50 + %HTTPoison.Response{:body => role_name} = HTTPoison.get("http://169.254.169.254/latest/meta-data/iam/security-credentials/", [], [timeout: 500])
51 + %HTTPoison.Response{:body => body} = HTTPoison.get("http://169.254.169.254/latest/meta-data/iam/security-credentials/#{role_name}", [], [timeout: 500])
52 + Poison.decode!(body)
53 + rescue
54 + _ ->
55 + %{}
56 + end
57 + end
58 +
59 + def refresh(config) do
60 + credentials_from_metadata = load_credentials_from_metadata
61 + update = %{
62 + :aws_access_key => credentials_from_metadata["AccessKeyId"],
63 + :aws_secret_access_key => credentials_from_metadata["SecretAccessKey"],
64 + :expires_at => credentials_from_metadata["Expiration"]
65 + }
66 + Map.merge(config, update)
67 + end
68 +
31 69 ###################
32 70 # Server Callbacks
33 71
  @@ -36,11 +74,21 @@ defmodule Simplex.Config do
36 74 end
37 75
38 76 def handle_call(:get_aws_access_key, _from, config) do
39 - {:reply, config[:aws_access_key], config}
77 + if needs_refresh?(:aws_access_key, config) do
78 + config = refresh(config)
79 + {:reply, config[:aws_access_key], config}
80 + else
81 + {:reply, config[:aws_access_key], config}
82 + end
40 83 end
41 84
42 85 def handle_call(:get_aws_secret_access_key, _from, config) do
43 - {:reply, config[:aws_secret_access_key], config}
86 + if needs_refresh?(:aws_secret_access_key, config) do
87 + config = refresh(config)
88 + {:reply, config[:aws_secret_access_key], config}
89 + else
90 + {:reply, config[:aws_secret_access_key], config}
91 + end
44 92 end
45 93
46 94 def handle_call(:get_simpledb_url, _from, config) do
  @@ -48,10 +96,16 @@ defmodule Simplex.Config do
48 96 end
49 97
50 98 def handle_cast({:set_aws_access_key, api_key}, config) do
51 - {:noreply, Map.put(config, :aws_access_key, api_key)}
99 + config = config
100 + |> Map.put(:aws_access_key, api_key)
101 + |> Map.delete(:expires_at)
102 + {:noreply, config}
52 103 end
53 104
54 105 def handle_cast({:set_aws_secret_access_key, api_key}, config) do
106 + config = config
107 + |> Map.put(:aws_secret_access_key, api_key)
108 + |> Map.delete(:expires_at)
55 109 {:noreply, Map.put(config, :aws_secret_access_key, api_key)}
56 110 end
Loading more files…