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

In the client, only source creds from the shared file when necessary #259

Merged
merged 12 commits into from
Feb 10, 2017
32 changes: 27 additions & 5 deletions lib/kitchen/driver/aws/client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ def initialize( # rubocop:disable Metrics/ParameterLists
retry_limit = nil
)
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 @@ -55,8 +55,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 @@ -65,14 +67,34 @@ def self.get_credentials(profile_name, access_key_id, secret_access_key, session
ENV["AWS_SECRET_ACCESS_KEY"],
ENV["AWS_SESSION_TOKEN"]
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This method is already called in the Aws::SharedCredentials constructor. Cf. http://docs.aws.amazon.com/sdkforruby/api/Aws/SharedCredentials.html (view source)

)
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 @@ -123,4 +180,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