Skip to content

Commit

Permalink
In the client, only source creds from the shared file when necessary (#…
Browse files Browse the repository at this point in the history
…259)

* Improve credential sourcing logic
* Fixes local testing when AWS ENV variables are set
  • Loading branch information
davidcpell authored and Seth Thomas committed Feb 10, 2017
1 parent 35889f0 commit 61a1c52
Show file tree
Hide file tree
Showing 2 changed files with 121 additions and 34 deletions.
32 changes: 27 additions & 5 deletions lib/kitchen/driver/aws/client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ def initialize( # rubocop:disable Metrics/ParameterLists
ssl_verify_peer = true
)
creds = self.class.get_credentials(
profile_name, access_key_id, secret_access_key, session_token
profile_name, access_key_id, secret_access_key, session_token, region
)
::Aws.config.update(
:region => region,
Expand All @@ -57,8 +57,10 @@ def initialize( # rubocop:disable Metrics/ParameterLists
# Try and get the credentials from an ordered list of locations
# http://docs.aws.amazon.com/sdkforruby/api/index.html#Configuration
# rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
def self.get_credentials(profile_name, access_key_id, secret_access_key, session_token)
shared_creds = ::Aws::SharedCredentials.new(:profile_name => profile_name)
# rubocop:disable Metrics/ParameterLists, Metrics/MethodLength
def self.get_credentials(profile_name, access_key_id, secret_access_key, session_token,
region, options = {})
source_creds =
if access_key_id && secret_access_key
::Aws::Credentials.new(access_key_id, secret_access_key, session_token)
elsif ENV["AWS_ACCESS_KEY_ID"] && ENV["AWS_SECRET_ACCESS_KEY"]
Expand All @@ -67,14 +69,34 @@ def self.get_credentials(profile_name, access_key_id, secret_access_key, session
ENV["AWS_SECRET_ACCESS_KEY"],
ENV["AWS_SESSION_TOKEN"]
)
elsif shared_creds.loadable?
shared_creds
elsif profile_name
::Aws::SharedCredentials.new(:profile_name => profile_name)
else
::Aws::InstanceProfileCredentials.new(:retries => 1)
end

if options[:assume_role_arn] && options[:assume_role_session_name]
sts = ::Aws::STS::Client.new(:credentials => source_creds, :region => region)

assume_role_options = (options[:assume_role_options] || {}).merge(
:client => sts,
:role_arn => options[:assume_role_arn],
:role_session_name => options[:assume_role_session_name]
)

::Aws::AssumeRoleCredentials.new(assume_role_options)
else
source_creds
end
end
# rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity

def self.get_shared_creds(profile_name)
::Aws::SharedCredentials.new(:profile_name => profile_name)
rescue ::Aws::Errors::NoSuchProfileError
false
end

def create_instance(options)
resource.create_instances(options)[0]
end
Expand Down
123 changes: 94 additions & 29 deletions spec/kitchen/driver/ec2/client_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,45 +21,48 @@

describe Kitchen::Driver::Aws::Client do
describe "::get_credentials" do
let(:shared) { instance_double(Aws::SharedCredentials) }
let(:iam) { instance_double(Aws::InstanceProfileCredentials) }

before do
expect(Aws::SharedCredentials).to \
receive(:new).with(:profile_name => "profile").and_return(shared)
end

# nothing else is set, so we default to this
it "loads IAM credentials last" do
expect(shared).to receive(:loadable?).and_return(false)
expect(Aws::InstanceProfileCredentials).to receive(:new).and_return(iam)
expect(Kitchen::Driver::Aws::Client.get_credentials("profile", nil, nil, nil)).to eq(iam)
iam = instance_double(Aws::InstanceProfileCredentials)

allow(Kitchen::Driver::Aws::Client).to receive(:get_shared_creds).and_return(false)
allow(Aws::InstanceProfileCredentials).to receive(:new).and_return(iam)

env_creds(nil, nil) do
expect(Kitchen::Driver::Aws::Client.get_credentials(nil, nil, nil, nil, nil)).to eq(iam)
end
end

it "loads shared credentials second to last" do
expect(shared).to receive(:loadable?).and_return(true)
expect(Kitchen::Driver::Aws::Client.get_credentials("profile", nil, nil, nil)).to eq(shared)
it "loads the shared credentials file second to last" do
shared = instance_double(Aws::SharedCredentials)

allow(Aws::SharedCredentials).to \
receive(:new).with(:profile_name => "profile").and_return(shared)

env_creds(nil, nil) do
expect(Kitchen::Driver::Aws::Client.get_credentials("profile", nil, nil, nil, nil)).to \
eq(shared)
end
end

it "loads shared credentials third to last" do
expect(shared).to_not receive(:loadable?)
ClimateControl.modify(
"AWS_ACCESS_KEY_ID" => "key1",
"AWS_SECRET_ACCESS_KEY" => "value1",
"AWS_SESSION_TOKEN" => "token1"
) do
expect(Kitchen::Driver::Aws::Client.get_credentials("profile", nil, nil, nil)).to \
it "loads credentials from the environment third to last" do
env_creds("key_id", "secret") do
expect(Kitchen::Driver::Aws::Client.get_credentials(nil, nil, nil, nil, nil)).to \
be_a(Aws::Credentials).and have_attributes(
:access_key_id => "key1",
:secret_access_key => "value1",
:session_token => "token1"
:access_key_id => "key_id",
:secret_access_key => "secret"
)
end
end

it "loads provided credentials first" do
expect(shared).to_not receive(:loadable?)
expect(Kitchen::Driver::Aws::Client.get_credentials("profile", "key3", "value3", nil)).to \
expect(Kitchen::Driver::Aws::Client.get_credentials(
"profile",
"key3",
"value3",
nil,
"us-west-1"
)).to \
be_a(Aws::Credentials).and have_attributes(
:access_key_id => "key3",
:secret_access_key => "value3",
Expand All @@ -68,8 +71,13 @@
end

it "uses a session token if provided" do
expect(shared).to_not receive(:loadable?)
expect(Kitchen::Driver::Aws::Client.get_credentials("profile", "key3", "value3", "t")).to \
expect(Kitchen::Driver::Aws::Client.get_credentials(
"profile",
"key3",
"value3",
"t",
"us-west-1"
)).to \
be_a(Aws::Credentials).and have_attributes(
:access_key_id => "key3",
:secret_access_key => "value3",
Expand All @@ -78,6 +86,55 @@
end
end

describe "::get_credentials + STS AssumeRole" do
let(:shared) { instance_double(Aws::SharedCredentials) }
let(:iam) { instance_double(Aws::InstanceProfileCredentials) }
let(:assume_role) { instance_double(Aws::AssumeRoleCredentials) }
let(:sts_client) { instance_double(Aws::STS::Client) }

before do
expect(Aws::AssumeRoleCredentials).to \
receive(:new).with(
:client => sts_client,
:role_arn => "role_arn",
:role_session_name => "role_session_name"
).and_return(assume_role)
end

# nothing else is set, so we default to this
it "loads an Instance Profile last" do
expect(Aws::InstanceProfileCredentials).to \
receive(:new).and_return(iam)
expect(Aws::STS::Client).to \
receive(:new).with(:credentials => iam, :region => "us-west-1").and_return(sts_client)

expect(Kitchen::Driver::Aws::Client.get_credentials(
nil,
nil,
nil,
nil,
"us-west-1",
:assume_role_arn => "role_arn", :assume_role_session_name => "role_session_name"
)).to eq(assume_role)
end

it "loads shared credentials second to last" do
expect(::Aws::SharedCredentials).to \
receive(:new).with(:profile_name => "profile").and_return(shared)
expect(Aws::STS::Client).to \
receive(:new).with(:credentials => shared, :region => "us-west-1").and_return(sts_client)

expect(Kitchen::Driver::Aws::Client.get_credentials(
"profile",
nil,
nil,
nil,
"us-west-1",
:assume_role_arn => "role_arn", :assume_role_session_name => "role_session_name"
)).to eq(assume_role)
end
end

let(:client) { Kitchen::Driver::Aws::Client.new("us-west-1") }

describe "#initialize" do
Expand Down Expand Up @@ -125,4 +182,12 @@
expect(client.resource).to be_a(Aws::EC2::Resource)
end

def env_creds(key_id, secret, &block)
ClimateControl.modify(
"AWS_ACCESS_KEY_ID" => key_id,
"AWS_SECRET_ACCESS_KEY" => secret
) do
block.call
end
end
end

0 comments on commit 61a1c52

Please sign in to comment.