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

Add support for AWS config and credential files #441

Merged
merged 14 commits into from Mar 21, 2016
Merged
1 change: 1 addition & 0 deletions Gemfile
Expand Up @@ -7,6 +7,7 @@ group :development do
# gem dependency because we expect to be installed within the
# Vagrant environment itself using `vagrant plugin`.
gem "vagrant", :git => "https://github.com/mitchellh/vagrant.git"
gem 'iniparse', '~> 1.4', '>= 1.4.2'
Copy link
Collaborator

Choose a reason for hiding this comment

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

This gem is referred to in vagrant-aws/config.rb so I'm not clear on why it is in the :development group of this Gemfile instead of as a dependency listed in the gemspec proper.

end

group :plugins do
Expand Down
15 changes: 15 additions & 0 deletions README.md
Expand Up @@ -87,6 +87,19 @@ no preconfigured defaults.
If you have issues with SSH connecting, make sure that the instances
are being launched with a security group that allows SSH access.

Note: if you don't configure `aws.access_key_id` or `aws_secret_access_key`
it will attempt to read credentials from environment variables first and then
from `$HOME/.aws/`. You can choose your AWS profile and files location by using
`aws.aws_profile` and `aws.aws_dir`, however environment variables will always
have precedence as defined by the [AWS documentation](http://docs.aws.amazon.com/cli/latest/userguide/cli-chap-getting-started.html).
To use profile `vagrantDev` from your AWS files:
```ruby
# this first line can actually be omitted
aws.aws_dir = ENV['HOME'] + "/.aws/"
aws.aws_profile = "vagrantDev"
```


## Box Format

Every provider in Vagrant must introduce a custom box format. This
Expand All @@ -106,6 +119,8 @@ This provider exposes quite a few provider-specific configuration options:
* `ami` - The AMI id to boot, such as "ami-12345678"
* `availability_zone` - The availability zone within the region to launch
the instance. If nil, it will use the default set by Amazon.
* `aws_profile` - AWS profile in your config files. Defaults to *default*.
* `aws_dir` - AWS config and credentials location. Defaults to *$HOME/.aws/*.
* `instance_ready_timeout` - The number of seconds to wait for the instance
to become "ready" in AWS. Defaults to 120 seconds.
* `instance_check_interval` - The number of seconds to wait to check the instance's
Expand Down
7 changes: 4 additions & 3 deletions Rakefile
Expand Up @@ -15,7 +15,8 @@ Dir.chdir(File.expand_path("../", __FILE__))
Bundler::GemHelper.install_tasks

# Install the `spec` task so that we can run tests.
RSpec::Core::RakeTask.new

RSpec::Core::RakeTask.new(:spec) do |t|
t.rspec_opts = "--order defined"
end
# Default task is to run the unit tests
task :default => "spec"
task :default => :spec
121 changes: 116 additions & 5 deletions lib/vagrant-aws/config.rb
@@ -1,4 +1,5 @@
require "vagrant"
require "iniparse"

module VagrantPlugins
module AWS
Expand Down Expand Up @@ -185,6 +186,16 @@ class Config < Vagrant.plugin("2", :config)
# @return [String]
attr_accessor :tenancy

# The directory where AWS files are stored (usually $HOME/.aws)
#
# @return [String]
attr_accessor :aws_dir

# The selected AWS named profile (as defined in $HOME/.aws/* files)
#
# @return [String]
attr_accessor :aws_profile

def initialize(region_specific=false)
@access_key_id = UNSET_VALUE
@ami = UNSET_VALUE
Expand Down Expand Up @@ -220,6 +231,8 @@ def initialize(region_specific=false)
@unregister_elb_from_az = UNSET_VALUE
@kernel_id = UNSET_VALUE
@tenancy = UNSET_VALUE
@aws_dir = UNSET_VALUE
@aws_profile = UNSET_VALUE

# Internal state (prefix with __ so they aren't automatically
# merged)
Expand Down Expand Up @@ -304,11 +317,21 @@ def merge(other)
end

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
# If access_key_id or secret_access_key were not specified in Vagrantfile
# then try to read from environment variables first, and if it fails from
# the AWS folder.
if @access_key_id == UNSET_VALUE or @secret_access_key == UNSET_VALUE
@aws_profile = 'default' if @aws_profile == UNSET_VALUE
@aws_dir = ENV['HOME'].to_s + '/.aws/' if @aws_dir == UNSET_VALUE
@region, @access_key_id, @secret_access_key, @session_token = Credentials.new.get_aws_info(@aws_profile, @aws_dir)
@region = UNSET_VALUE if @region.nil?
else
@aws_profile = nil
@aws_dir = nil
end

# session token must be set to nil, empty string isn't enough!
@session_token = nil if @session_token == UNSET_VALUE

# AMI must be nil, since we can't default that
@ami = nil if @ami == UNSET_VALUE
Expand Down Expand Up @@ -414,6 +437,10 @@ def finalize!
def validate(machine)
errors = _detected_errors

errors << I18n.t("vagrant_aws.config.aws_info_required",
:profile => @aws_profile, :location => @aws_dir) if \
@aws_profile and (@access_key_id.nil? or @secret_access_key.nil? or @region.nil?)

errors << I18n.t("vagrant_aws.config.region_required") if @region.nil?

if @region
Expand Down Expand Up @@ -449,5 +476,89 @@ def get_region_config(name)
@__compiled_region_configs[name] || self
end
end


class Credentials < Vagrant.plugin("2", :config)
# This module reads AWS config and credentials.
# Behaviour aims to mimic what is described in AWS documentation:
# http://docs.aws.amazon.com/cli/latest/userguide/cli-chap-getting-started.html
# http://docs.aws.amazon.com/cli/latest/topic/config-vars.html
# Which is the following (stopping at the first successful case):
# 1) read config and credentials from environment variables
# 2) read config and credentials from files at location defined by environment variables
# 3) read config and credentials from files at default location
#
# The mandatory fields for a successful "get credentials" are the id and the secret keys.
# Region is not required since Config#finalize falls back to sensible defaults.
# The behaviour is all-or-nothing (ie: no mixing between vars and files).
#
# It also allows choosing a profile (by default it's [default]) and an "info"
# directory (by default $HOME/.aws), which can be specified in the Vagrantfile.
# Supported information: region, aws_access_key_id, aws_secret_access_key, and aws_session_token.

def get_aws_info(profile, location)
# read credentials from environment variables
aws_region, aws_id, aws_secret, aws_token = read_aws_environment()
# if nothing there, then read from files
# (the _if_ doesn't check aws_region since Config#finalize sets one by default)
if aws_id.to_s == '' or aws_secret.to_s == ''
# check if there are env variables for credential location, if so use then
aws_config = ENV['AWS_CONFIG_FILE'].to_s
aws_creds = ENV['AWS_SHARED_CREDENTIALS_FILE'].to_s
if aws_config == '' or aws_creds == ''
aws_config = location + 'config'
aws_creds = location + 'credentials'
end
if File.exist?(aws_config) and File.exist?(aws_creds)
aws_region, aws_id, aws_secret, aws_token = read_aws_files(profile, aws_config, aws_creds)
end
end
aws_region = nil if aws_region == ''
aws_id = nil if aws_id == ''
aws_secret = nil if aws_secret == ''
aws_token = nil if aws_token == ''

return aws_region, aws_id, aws_secret, aws_token
end


private

def read_aws_files(profile, aws_config, aws_creds)
# determine section in config ini file
if profile == 'default'
ini_profile = profile
else
ini_profile = 'profile ' + profile
end
# get info from config ini file for selected profile
data = File.read(aws_config)
doc_cfg = IniParse.parse(data)
aws_region = doc_cfg[ini_profile]['region']

# determine section in credentials ini file
ini_profile = profile
# get info from credentials ini file for selected profile
data = File.read(aws_creds)
doc_cfg = IniParse.parse(data)
aws_id = doc_cfg[ini_profile]['aws_access_key_id']
aws_secret = doc_cfg[ini_profile]['aws_secret_access_key']
aws_token = doc_cfg[ini_profile]['aws_session_token']

return aws_region, aws_id, aws_secret, aws_token
end

def read_aws_environment()
aws_region = ENV['AWS_DEFAULT_REGION']
aws_id = ENV['AWS_ACCESS_KEY_ID']
aws_secret = ENV['AWS_SECRET_ACCESS_KEY']
aws_token = ENV['AWS_SESSION_TOKEN']

return aws_region, aws_id, aws_secret, aws_token
end

end


end
end
2 changes: 1 addition & 1 deletion lib/vagrant-aws/version.rb
@@ -1,5 +1,5 @@
module VagrantPlugins
module AWS
VERSION = '0.7.0'
VERSION = '0.7.1'
end
end
3 changes: 3 additions & 0 deletions locales/en.yml
Expand Up @@ -76,6 +76,9 @@ 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"
aws_info_required: |-
One or more of the needed AWS credentials are missing. No environment variables
are set nor profile '%{profile}' exists at '%{location}'

errors:
fog_error: |-
Expand Down