diff --git a/lib/ex_aws/config.ex b/lib/ex_aws/config.ex index 7d6fd36e..b5fc2387 100644 --- a/lib/ex_aws/config.ex +++ b/lib/ex_aws/config.ex @@ -19,6 +19,7 @@ defmodule ExAws.Config do # TODO: Add proper documentation? @common_config [ + :auth_cache_refresh_lead_time, :http_client, :http_opts, :json_codec, diff --git a/lib/ex_aws/config/auth_cache.ex b/lib/ex_aws/config/auth_cache.ex index 31c95d6e..d48ef626 100644 --- a/lib/ex_aws/config/auth_cache.ex +++ b/lib/ex_aws/config/auth_cache.ex @@ -5,7 +5,7 @@ defmodule ExAws.Config.AuthCache do # http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/iam-roles-for-amazon-ec2.html - @refresh_lead_time 300_000 + @default_refresh_lead_time 300_000 @instance_auth_key :aws_instance_auth defmodule AuthConfigAdapter do @@ -34,6 +34,8 @@ defmodule ExAws.Config.AuthCache do end end + def default_refresh_lead_time(), do: @default_refresh_lead_time + ## Callbacks def init(:ok) do @@ -101,7 +103,7 @@ defmodule ExAws.Config.AuthCache do end defp refresh_auth_if_required([{_key, cached_auth}], config) do - if next_refresh_in(cached_auth) > 0 do + if next_refresh_in(cached_auth, config) > 0 do cached_auth else GenServer.call(__MODULE__, {:refresh_auth, config}, 30_000) @@ -118,7 +120,9 @@ defmodule ExAws.Config.AuthCache do end defp refresh_auth_if_stale([{_key, cached_auth}], config, ets) do - if next_refresh_in(cached_auth) > @refresh_lead_time do + IO.inspect config + IO.inspect next_refresh_in(cached_auth, config) + if next_refresh_in(cached_auth, config) > config.auth_cache_refresh_lead_time do # we still have a valid auth token, so simply return that cached_auth else @@ -131,11 +135,11 @@ defmodule ExAws.Config.AuthCache do defp refresh_auth_now(config, ets) do auth = ExAws.InstanceMeta.security_credentials(config) :ets.insert(ets, {@instance_auth_key, auth}) - Process.send_after(__MODULE__, {:refresh_auth, config}, next_refresh_in(auth)) + Process.send_after(__MODULE__, {:refresh_auth, config}, next_refresh_in(auth, config)) auth end - defp next_refresh_in(%{expiration: expiration}) do + defp next_refresh_in(%{expiration: expiration}, config) do try do expires_in_ms = expiration @@ -144,11 +148,11 @@ defmodule ExAws.Config.AuthCache do # refresh lead_time before auth expires, unless the time has passed # otherwise refresh needed now - max(0, expires_in_ms - @refresh_lead_time) + max(0, expires_in_ms - config.auth_cache_refresh_lead_time) rescue _e -> 0 end end - defp next_refresh_in(_), do: 0 + defp next_refresh_in(_, _config), do: 0 end diff --git a/lib/ex_aws/config/defaults.ex b/lib/ex_aws/config/defaults.ex index 87881a66..04ef2098 100644 --- a/lib/ex_aws/config/defaults.ex +++ b/lib/ex_aws/config/defaults.ex @@ -4,6 +4,7 @@ defmodule ExAws.Config.Defaults do """ @common %{ + auth_cache_refresh_lead_time: ExAws.Config.AuthCache.default_refresh_lead_time(), access_key_id: [{:system, "AWS_ACCESS_KEY_ID"}, :instance_role], secret_access_key: [{:system, "AWS_SECRET_ACCESS_KEY"}, :instance_role], http_client: ExAws.Request.Hackney, diff --git a/lib/ex_aws/instance_meta.ex b/lib/ex_aws/instance_meta.ex index 518335a5..721f35e3 100644 --- a/lib/ex_aws/instance_meta.ex +++ b/lib/ex_aws/instance_meta.ex @@ -64,6 +64,7 @@ defmodule ExAws.InstanceMeta do end def instance_role(config) do + IO.inspect "HERE" ExAws.InstanceMeta.request(config, @meta_path_root <> "/iam/security-credentials/") end diff --git a/test/ex_aws/auth/auth_cache_test.exs b/test/ex_aws/auth/auth_cache_test.exs index d0581716..fce8cbe6 100644 --- a/test/ex_aws/auth/auth_cache_test.exs +++ b/test/ex_aws/auth/auth_cache_test.exs @@ -38,26 +38,13 @@ defmodule ExAws.AuthCacheTest do test "using adapter does not leak dirty cache" do parent = self() - op = %ExAws.Operation.S3{ - body: "", - bucket: "", - headers: %{}, - http_method: :get, - params: [], - parser: & &1, - path: "/", - resource: "", - service: :s3, - stream_builder: nil - } - spawn(fn -> ExAws.Request.HttpMock |> expect(:request, fn _method, _url, _body, _headers, _opts -> @response end) - result = ExAws.request(op, @config) + result = ExAws.request(op(), @config) send(parent, result) end) @@ -67,11 +54,56 @@ defmodule ExAws.AuthCacheTest do end) Process.sleep(100) - assert ExAws.request(op, @config) == @response + assert ExAws.request(op(), @config) == @response assert_receive @response, 1000 end + test "overriding refresh lead time" do + defmodule RandomAdapter do + @moduledoc false + + @behaviour ExAws.Config.AuthCache.AuthConfigAdapter + + def adapt_auth_config(_config, _profile, _expiration) do + %{ + access_key_id: Base.encode64(:crypto.strong_rand_bytes(20)), + secret_access_key: Base.encode64(:crypto.strong_rand_bytes(20)) + } + end + end + + config = [ + http_client: ExAws.Request.HttpMock, + access_key_id: :instance_role, + secret_access_key: :instance_role + ] + + Application.put_env(:ex_aws, :awscli_auth_adapter, RandomAdapter) + + parent = self() + + ExAws.Request.HttpMock + |> expect(:request, 5, + fn :get, "http://169.254.169.254/latest/meta-data/iam/security-credentials/", _body, _headers, _opts -> + {:ok, %{status_code: 200, body: "dummy-role"}} + :get, _url, _body, headers, _opts -> + send(parent, headers) + @response + end) + + assert ExAws.request(op(), config) == @response + assert ExAws.request(op(), config) == @response + assert ExAws.request(op(), [{:auth_cache_refresh_lead_time, 1_000_000_000_000} | config]) == @response + assert_receive headers1, 1000 + assert_receive headers2, 1000 + assert_receive headers3, 1000 + + IO.inspect(headers1) + IO.inspect(headers2) + IO.inspect(headers3) + end + test "using adapter retries when there is an error" do # The flaky adapter simulates failures on the adapter side # for a few a tries and then returns a successful response. @@ -108,25 +140,28 @@ defmodule ExAws.AuthCacheTest do FlakyAdapter.init() Application.put_env(:ex_aws, :awscli_auth_adapter, FlakyAdapter) - op = %ExAws.Operation.S3{ + ExAws.Request.HttpMock + |> expect(:request, fn _method, _url, _body, _headers, _opts -> + @response + end) + + Process.sleep(100) + assert ExAws.request(op(), @config) == @response + end + + defp op do + %ExAws.Operation.S3{ body: "", bucket: "", headers: %{}, http_method: :get, params: [], - parser: & &1, + parser: fn x -> x end, path: "/", resource: "", service: :s3, stream_builder: nil } - - ExAws.Request.HttpMock - |> expect(:request, fn _method, _url, _body, _headers, _opts -> - @response - end) - - Process.sleep(100) - assert ExAws.request(op, @config) == @response end + end