Skip to content
This repository has been archived by the owner on Feb 11, 2022. It is now read-only.

Add support for credentials file (WIP) #337

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ source "https://rubygems.org"

gemspec

gem 'aws-sdk', '~> 1.60.2'

group :development do
# We depend on Vagrant for development, but we don't add it as a
# gem dependency because we expect to be installed within the
Expand Down
65 changes: 62 additions & 3 deletions lib/vagrant-aws/config.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
require "vagrant"
require "log4r"
require "aws/core"

module VagrantPlugins
module AWS
Expand Down Expand Up @@ -75,6 +77,11 @@ class Config < Vagrant.plugin("2", :config)
# @return [String]
attr_accessor :session_token

# Path to file containing AWS credentials
#
# @return [String]
attr_accessor :credential_file

# The security groups to set on the instance. For VPC this must
# be a list of IDs. For EC2, it can be either.
#
Expand Down Expand Up @@ -156,6 +163,8 @@ class Config < Vagrant.plugin("2", :config)
attr_accessor :elb

def initialize(region_specific=false)
@logger = Log4r::Logger.new("vagrant_aws::config")

@access_key_id = UNSET_VALUE
@ami = UNSET_VALUE
@availability_zone = UNSET_VALUE
Expand All @@ -169,6 +178,7 @@ def initialize(region_specific=false)
@version = UNSET_VALUE
@secret_access_key = UNSET_VALUE
@session_token = UNSET_VALUE
@credential_file = UNSET_VALUE
@security_groups = UNSET_VALUE
@subnet_id = UNSET_VALUE
@tags = {}
Expand Down Expand Up @@ -266,9 +276,43 @@ def merge(other)
def finalize!
# Try to get access keys from standard AWS environment variables; they
# will default to nil if the environment variables are not present.
@access_key_id = ENV['AWS_ACCESS_KEY'] if @access_key_id == UNSET_VALUE
@secret_access_key = ENV['AWS_SECRET_KEY'] if @secret_access_key == UNSET_VALUE
@session_token = ENV['AWS_SESSION_TOKEN'] if @session_token == UNSET_VALUE

@credential_file = ENV['AWS_CREDENTIAL_FILE'] if @credential_file == UNSET_VALUE

unless @credential_file.nil?
path = File.expand_path(@credential_file)

@logger.info("Loading AWS credentials from #{path}")
if !File.exist?(path)
@logger.warn("Provided credential file #{path} doesn't exist")

@access_key_id = nil
@secret_access_key = nil
@session_token = nil
else
provider = ::AWS::Core::CredentialProviders::SharedCredentialFileProvider.new(
:path => path
)
credentials = provider.get_credentials()

@logger.debug("Loaded credentials via AWS SDK: #{credentials}")

@access_key_id = credentials[:access_key_id]
@secret_access_key = credentials[:secret_access_key]
@session_token = credentials[:session_token]
end
else
@logger.debug("Using AWS credentials provided directly")

@access_key_id = ENV['AWS_ACCESS_KEY'] if @access_key_id == UNSET_VALUE
@secret_access_key = ENV['AWS_SECRET_KEY'] if @secret_access_key == UNSET_VALUE
@session_token = ENV['AWS_SESSION_TOKEN'] if @session_token == UNSET_VALUE
end

_ = '<empty>'
@logger.debug("Using AWS access key: #{@access_key_id||_}")
@logger.debug("Using AWS secret key: #{@secret_access_key||_}")
@logger.debug("Using AWS session token: #{@session_token||_}")

# AMI must be nil, since we can't default that
@ami = nil if @ami == UNSET_VALUE
Expand Down Expand Up @@ -367,6 +411,21 @@ def validate(machine)
# that region.
config = get_region_config(@region)

# TODO - lifecycle misunderstanding, it's always defined (even when loaded from file as above)
if !config.credential_file.nil?
if !config.access_key_id.nil?
errors << I18n.t("vagrant_aws.config.access_key_id_cred_path_conflict")
end

if !config.secret_access_key.nil?
errors << I18n.t("vagrant_aws.config.secret_access_key_cred_path_conflict")
end

if !config.session_token.nil?
errors << I18n.t("vagrant_aws.config.session_token_cred_path_conflict")
end
end

if !config.use_iam_profile
errors << I18n.t("vagrant_aws.config.access_key_id_required") if \
config.access_key_id.nil?
Expand Down
6 changes: 6 additions & 0 deletions locales/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,12 @@ en:
A secret access key is required via "secret_access_key"
subnet_id_required_with_public_ip: |-
If you assign a public IP address to an instance in a VPC, a subnet must be specifed via "subnet_id"
access_key_id_cred_path_conflict: |-
Either "access_key_id" or "credential_file" must be specifed, not both
secret_access_key_cred_path_conflict: |-
Either "secret_access_key" or "credential_file" must be specifed, not both
session_token_cred_path_conflict: |-
Either "session_token" or "credential_file" must be specifed, not both

errors:
fog_error: |-
Expand Down
40 changes: 35 additions & 5 deletions spec/vagrant-aws/config_spec.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
require 'vagrant-aws'
require 'vagrant-aws/plugin'
require "vagrant-aws/config"
require 'rspec/its'

Expand Down Expand Up @@ -27,6 +29,7 @@
its("region") { should == "us-east-1" }
its("secret_access_key") { should be_nil }
its("session_token") { should be_nil }
its("credential_file") { should be_nil }
its("security_groups") { should == [] }
its("subnet_id") { should be_nil }
its("iam_instance_profile_arn") { should be_nil }
Expand All @@ -49,11 +52,12 @@
# each of these attributes to "foo" in isolation, and reads the value
# and asserts the proper result comes back out.
[:access_key_id, :ami, :availability_zone, :instance_ready_timeout,
:instance_package_timeout, :instance_type, :keypair_name, :ssh_host_attribute,
:ebs_optimized, :region, :secret_access_key, :session_token, :monitoring,
:associate_public_ip, :subnet_id, :tags, :elastic_ip, :terminate_on_shutdown,
:iam_instance_profile_arn, :iam_instance_profile_name,
:use_iam_profile, :user_data, :block_device_mapping].each do |attribute|
:instance_package_timeout, :instance_type, :keypair_name,
:ssh_host_attribute, :ebs_optimized, :region, :secret_access_key,
:session_token, :credential_file, :monitoring, :associate_public_ip,
:subnet_id, :tags, :elastic_ip, :terminate_on_shutdown,
:iam_instance_profile_arn, :iam_instance_profile_name, :use_iam_profile,
:user_data, :block_device_mapping].each do |attribute|

it "should not default #{attribute} if overridden" do
instance.send("#{attribute}=".to_sym, "foo")
Expand All @@ -76,6 +80,7 @@
end
end

its("credential_file") { should be_nil }
its("access_key_id") { should be_nil }
its("secret_access_key") { should be_nil }
its("session_token") { should be_nil }
Expand All @@ -86,6 +91,7 @@
ENV.stub(:[]).with("AWS_ACCESS_KEY").and_return("access_key")
ENV.stub(:[]).with("AWS_SECRET_KEY").and_return("secret_key")
ENV.stub(:[]).with("AWS_SESSION_TOKEN").and_return("session_token")
ENV.stub(:[]).with("AWS_CREDENTIAL_FILE").and_return(nil)
end

subject do
Expand All @@ -97,6 +103,7 @@
its("access_key_id") { should == "access_key" }
its("secret_access_key") { should == "secret_key" }
its("session_token") { should == "session_token" }
its("credential_file") { should be_nil }
end
end

Expand All @@ -108,6 +115,7 @@
let(:config_region) { "foo" }
let(:config_secret_access_key) { "foo" }
let(:config_session_token) { "foo" }
let(:config_credential_file) { nil }

def set_test_values(instance)
instance.access_key_id = config_access_key_id
Expand All @@ -117,6 +125,7 @@ def set_test_values(instance)
instance.region = config_region
instance.secret_access_key = config_secret_access_key
instance.session_token = config_session_token
instance.credential_file = config_credential_file
end

it "should raise an exception if not finalized" do
Expand All @@ -143,6 +152,7 @@ def set_test_values(instance)
its("region") { should == config_region }
its("secret_access_key") { should == config_secret_access_key }
its("session_token") { should == config_session_token }
its("credential_file") { should == config_credential_file }
end

context "with a specific config set" do
Expand All @@ -168,6 +178,26 @@ def set_test_values(instance)
its("region") { should == region_name }
its("secret_access_key") { should == config_secret_access_key }
its("session_token") { should == config_session_token }
its("credential_file") { should == config_credential_file }
end

context "with conflicting config options" do
before(:each) do
VagrantPlugins::AWS::Plugin.setup_i18n
set_test_values(instance)
instance.finalize!
@config = instance.get_region_config("us-east-1")
end

it "should throw error if keys & credential_file is defined" do
@config.access_key_id = "access_key"
@config.secret_access_key = "secret key"
@config.session_token = "token"
@config.credential_file = "credential file"

errors = instance.validate(nil)
errors['AWS Provider'].length.should eq(3)
end
end

describe "inheritance of parent config" do
Expand Down