Current section
14 Versions
Jump to
Current section
14 Versions
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…