Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add explicit option for using iam profile for authentication #107

Merged
merged 14 commits into from
Apr 10, 2015

Conversation

JamesAwesome
Copy link

PR #104 broke the ability to use kitchen-ec2 with explicit aws credentials set via .kitchen.yml while on an ec2 host.

When on an ec2 host the #fetch_credentials method which is included from Fog::AWS::CredentialFetcher::ServiceMethods will always return a hash with a value set for the :aws_session_token key. Because of this config[:aws_session_token] will always default to iam_creds[:aws_session_token]. Since config[:aws_session_token] is passed to #connection, Fog::Compute fails to authenticate using any credentials set in .kitchen.yml since the value of :aws_session_token is incorrect.

By adding an option to explicitly use a server's iam profile we can allow hosts which live in ec2 to use .kitchen.yml as well as their iam profile for driver configuration.

* Add 'use_iam_profile' config option, default to false
* Have #iam_creds return an empty hash unless use_iam_profile is true
* Call iam_creds as driver.iam_creds in `default_config` blocks
* Rescue fron NoMethodError in iam_creds
  `Fog::AWS::CredentialFetcher::ServiceMethods::fetch_credentials`
  will call super when it fails. Because there is no superclass method
  this will throw a NoMethodError
* Write unit tests for #iam_creds
@JamesAwesome
Copy link
Author

@tyler-ball Finally got these tests to pass. Sorry for all the chatter.

@Igorshp
Copy link
Contributor

Igorshp commented Apr 7, 2015

@JamesAwesome there is already a 'iam_profile_name' param that assigns the named role to the new node.

I think 'use_iam_profile' may be little misleading next to it.

Would be nice to do the detection automaticaly.

@JamesAwesome
Copy link
Author

@Igorshp we could change the name to something like :use_iam_role however if we try to autodetect while on an ec2 node we will always receive some form of value for iam_creds[:aws_session_token], whether or not that value is valid.

@Igorshp
Copy link
Contributor

Igorshp commented Apr 7, 2015

I mean the autodetection should check if key_id and/or secret are manualy set and return session_token accordingly.

@JamesAwesome
Copy link
Author

Might be able to whip that up, I'll futz with this a bit more and update

fetch_credentials(use_iam_profile:true)
rescue RuntimeError => e
config[:use_iam_profile] ? fetch_credentials(use_iam_profile: true) : {}
rescue RuntimeError, NoMethodError => e
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where do you expect the NoMethodError to come from?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fetch_credentials calls super when it fails, so since we don't have a super for it to fail over to we need to catch NoMethodError, I think I made a note in the commit message.

@tyler-ball
Copy link
Contributor

Thanks for adding a test and turning fetch_credentials into an instance method rather than class method. I think it makes more sense for our use case and I didn't think of that.

I'm not familiar enough with AWS - is there a scenario where you would ever set access_key_id/secret_access_key AND session_token? Or are they mutually exclusive forms of authentication?

If you can't use both of them in 1 auth call I agree with @Igorshp. We should detect the presence of access_key_id/secret_access_key and only provide those if available. If those are not available, use the session_token. In this sense, the access_key (if available) overrides the session_token. I think this makes sense because you can simply not provide those and rely on fetch_credentials if you want to use the session_token.

I would love to get @fnichol's opinion on this.

@Igorshp
Copy link
Contributor

Igorshp commented Apr 7, 2015

@tyler-ball the core of the problem is the fact that you can only use one set of credentials that comes from one source.

i.e. either all are from iam_creds or all are user defined (session token can be user provided, but i've never seen it used like that. default is nil), because they are not compatible with each other.

so if even one piece of credentials is manually set, the rest should not be pulled from iam_role.

James Awesome added 2 commits April 7, 2015 12:16
* Remove :use_iam_profile config setting
* Update tests to reflect autodetection vs use of :use_iam_profile
* Add `#default_aws_session_token` method to handle logic for setting
  default value of config[:aws_session_token]

Since the :use_iam_profile parameter is confusing instead autodetect
if `iam_creds` should be used for setting config[:aws_session_token]
by checking if other config settings match the values returned from
`iam_creds`. If the config settings match then it's a safe bet that
iam_creds should be used for setting config[:aws_session_token].
@JamesAwesome
Copy link
Author

@tyler-ball whoops I regressed the include to extend at some point. Corrected it. There are in fact instances where session token makes sense however they seem very edge. See docs here.

Auto detection should work fine now.

@Igorshp
Copy link
Contributor

Igorshp commented Apr 7, 2015

👍 sweet, thanks!

@JamesAwesome
Copy link
Author

Ready for review. No more commits for now.

@moimikey
Copy link

moimikey commented Apr 7, 2015

:shipit:

end
default_config :aws_secret_access_key do |driver|
ENV['AWS_SECRET_KEY'] || ENV['AWS_SECRET_ACCESS_KEY'] || iam_creds[:aws_secret_access_key]
ENV['AWS_SECRET_KEY'] || ENV['AWS_SECRET_ACCESS_KEY'] \
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you want to break this across multiple lines you don't need the \ - just leave the || at the end of the line

if config[:aws_secret_access_key] == iam_creds[:aws_secret_access_key] \
&& config[:aws_access_key_id] == iam_creds[:aws_access_key_id]
iam_creds[:aws_session_token]
else
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is the user required to specify the same access key / secret as returned by the iam_creds query? If they have to do that, why not just use the provided access key / secret for authentication?

I thought the primary point of the iam_creds was they allowed the EC2 node acting as a provisioner to provision nodes using the local credentials.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually I'm just checking that config[:aws_secret_access_key] et all had fallen back to the iam_creds statement so that we know it would be logical to return the iam_creds[:aws_session_token] since our other config values are from iam.

@tyler-ball
Copy link
Contributor

When running on a non-ec2 node it takes a really long time to figure out the metadata service isn't available. With that in mind I created the following patch:

diff --git a/lib/kitchen/driver/ec2.rb b/lib/kitchen/driver/ec2.rb
index e8df7af..de5eaf9 100644
--- a/lib/kitchen/driver/ec2.rb
+++ b/lib/kitchen/driver/ec2.rb
@@ -41,14 +41,16 @@ module Kitchen
       default_config :iam_profile_name,   nil
       default_config :price,   nil
       default_config :aws_access_key_id do |driver|
-        ENV['AWS_ACCESS_KEY'] || ENV['AWS_ACCESS_KEY_ID'] || driver.iam_creds[:aws_access_key_id]
+        ENV['AWS_ACCESS_KEY'] || ENV['AWS_ACCESS_KEY_ID'] ||
+          driver.iam_creds[:aws_access_key_id]
       end
       default_config :aws_secret_access_key do |driver|
         ENV['AWS_SECRET_KEY'] || ENV['AWS_SECRET_ACCESS_KEY'] ||
           driver.iam_creds[:aws_secret_access_key]
       end
       default_config :aws_session_token do |driver|
-        driver.default_aws_session_token
+        ENV['AWS_SESSION_TOKEN'] || ENV['AWS_TOKEN'] ||
+          driver.iam_creds[:aws_session_token]
       end
       default_config :aws_ssh_key_id do |driver|
         ENV['AWS_SSH_KEY_ID']
@@ -98,10 +100,18 @@ module Kitchen
         end
       end

+      # First we check the existence of the metadata host.  Only fetch_credentials
+      # if we can find the host.  Ping logic taken from
+      # http://stackoverflow.com/questions/7519159/ruby-ping-pingecho-missing
       def iam_creds
+        require 'net/http'
+        require 'timeout'
         @iam_creds ||= begin
+          timeout(5) do
+            Net::HTTP.get(URI.parse('http://169.254.169.254'))
+          end
           fetch_credentials(use_iam_profile: true)
-        rescue RuntimeError, NoMethodError => e
+        rescue Errno::EHOSTUNREACH, Timeout::Error, StandardError => e
           debug("fetch_credentials failed with exception #{e.message}:#{e.backtrace.join("\n")}")
           {}
         end
@@ -167,15 +177,6 @@ module Kitchen
         !!config[:subnet_id]
       end

-      def default_aws_session_token
-        if config[:aws_secret_access_key] == iam_creds[:aws_secret_access_key] \
-          && config[:aws_access_key_id] == iam_creds[:aws_access_key_id]
-          iam_creds[:aws_session_token]
-        else
-          ENV['AWS_SESSION_TOKEN'] || ENV['AWS_TOKEN']
-        end
-      end
-
       private

       def connection

I think thats a better solution for this case. If we can access the metadata service, we do.

@JamesAwesome
Copy link
Author

My only concern is that http://169.254.169.254 is a generic link-local address and also used to serve metadata for clouds like openstack.

We could work around this by letting fetch_credentials fail and rescue NoMethodError but there may be a better way to check. Will take a look.

@tyler-ball
Copy link
Contributor

@JamesAwesome Ah, I see your concern - drat that people are starting to use Test Kitchen in their CI workflow from multiple cloud providers! 😄

But even if we can connect to http://169.254.169.254, fetch_credentials should fail on non-ec2 nodes because it is requesting the path /latest/meta-data/iam/security-credentials/. In this case we will fall through to the NoMethodError rescue.

My concern is that I don't want to wait a full minute for all ec2 creates like the person in #104 is experiencing.

@JamesAwesome
Copy link
Author

Yeah I'm perfectly happy with that then if you are. 👍 I'll merge in the patch and get the tests adjusted.

@JamesAwesome
Copy link
Author

So problem:

Because this patch will call driver.iam_creds[:aws_session_token] if config[:aws_session_token] is unset in .kitchen.yml or not present in the environment we are back in the position where if you have explicitly set :aws_access_key_id or :aws_secret_access_key and are running on an ec2 host you will have an unwanted and incorrect :aws_session_token and authentication will fail.

We can deal with this by comparing the configured :aws_access_key_id against the value returned by iam_creds like I do in the current commit, however I'd like to hear @tyler-ball 's opinion on that before I continue so that we can solve this a bit quicker. I'll think on the topic a bit and comment if I come up with anything a bit more clever.

@JamesAwesome
Copy link
Author

Updated (failing) tests illustrate the problem

James Awesome added 6 commits April 9, 2015 16:57
* only set :aws_session_token via iam_creds in the event that
  :aws_access_key_id and :aws_secret_access_key are BOTH
  already set via iam_creds
* prefer environment over iam_creds when setting :aws_session_token

I think this is the best I can do. All unprevented combinations of
autodetectable credentials via iam_creds and user defined settings
should simply fail authentication.
@JamesAwesome
Copy link
Author

I am now 100% happy with this.

I had to remove the top level before block and move it into appropriate context's since it was calling instance too soon which was causing my tests to fail.

@tyler-ball I promise no more commits unless you comment. 😄

tyler-ball added a commit that referenced this pull request Apr 10, 2015
@tyler-ball tyler-ball merged commit ac3d1c4 into test-kitchen:master Apr 10, 2015
tyler-ball added a commit that referenced this pull request Apr 10, 2015
@tyler-ball
Copy link
Contributor

@JamesAwesome I did a little bit of test refactoring / cleanup when I merged, but thank you for tackling this issue and doing all the work!

@JamesAwesome
Copy link
Author

👍 😄

@Igorshp
Copy link
Contributor

Igorshp commented Apr 10, 2015

👍 Thanks

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants