diff --git a/lib/chef/knife/ec2_ami_list.rb b/lib/chef/knife/ec2_ami_list.rb index d60999c2..57651dcb 100644 --- a/lib/chef/knife/ec2_ami_list.rb +++ b/lib/chef/knife/ec2_ami_list.rb @@ -62,7 +62,7 @@ def run validate_aws_config! custom_warnings! - + mfa_creds servers_list = [ ui.color("AMI ID", :bold), ui.color("Platform", :bold), diff --git a/lib/chef/knife/ec2_base.rb b/lib/chef/knife/ec2_base.rb index e4f0ed40..5361010c 100644 --- a/lib/chef/knife/ec2_base.rb +++ b/lib/chef/knife/ec2_base.rb @@ -76,6 +76,21 @@ def self.included(includer) boolean: true, default: false, proc: Proc.new { |key| Chef::Config[:knife][:use_iam_profile] = key } + + option :duration_seconds, + long: "--duration-seconds DURATION_SECONDS", + description: "The duration, in seconds, that the credentials should remain valid. Acceptable durations for IAM user sessions range from 900 seconds (15 minutes) to 129,600 seconds (36 hours), with 43,200 seconds (12 hours) as the default. Sessions for AWS account owners are restricted to a maximum of 3,600 seconds (one hour). If the duration is longer than one hour, the session for AWS account owners defaults to one hour.", + default: 43200 + + option :mfa_enabled_user, + long: "--mfa-enabled-user", + description: "Use MFA enabled IAM user.", + boolean: true, + default: false + + option :serial_number, + long: "--serial-number SERIAL_NUMBER", + description: "Serial number of user." end end @@ -99,6 +114,14 @@ def ec2_connection @ec2_connection ||= Aws::EC2::Client.new(connection_string) end + # @return [Aws::STS::Client] + def sts_connection + @sts_connection ||= begin + require "aws-sdk-core" # lazy load the aws sdk to speed up the knife run + Aws::STS::Client.new(connection_string) + end + end + def fetch_ami(image_id) return nil unless image_id @@ -307,6 +330,7 @@ def validate_aws_config_file! unless aws_config.values.empty? if aws_config[profile_key] Chef::Config[:knife][:region] = aws_config[profile_key]["region"] + Chef::Config[:knife][:serial_number] = aws_config[profile_key]["mfa_serial"] else raise ArgumentError, "The provided --aws-profile '#{profile_key}' is invalid." end @@ -346,5 +370,29 @@ def validate_aws_credential_file! raise ArgumentError, "The provided --aws-profile '#{profile}' is invalid. Does the credential file at '#{aws_cred_file_location}' contain this profile?" end end + + def mfa_creds + if config[:mfa_enabled_user] + puts("Enter MFA code for #{config[:serial_number]}") + token_code = STDIN.gets.chomp + generate_credentials(config[:duration_seconds], config[:serial_number], token_code) + end + end + + def generate_credentials(duration_seconds, mfa_serial, token_code) + creds = sts_connection.get_session_token({ + duration_seconds: duration_seconds, + serial_number: mfa_serial, + token_code: token_code, + }) + + save_credentials(creds) + end + + def save_credentials(creds) + Chef::Config[:knife][:aws_access_key_id] = creds.credentials.access_key_id + Chef::Config[:knife][:aws_secret_access_key] = creds.credentials.secret_access_key + Chef::Config[:knife][:aws_session_token] = creds.credentials.session_token + end end end diff --git a/lib/chef/knife/ec2_eip_list.rb b/lib/chef/knife/ec2_eip_list.rb index 17acb19c..fcaed182 100644 --- a/lib/chef/knife/ec2_eip_list.rb +++ b/lib/chef/knife/ec2_eip_list.rb @@ -29,7 +29,7 @@ class Ec2EniList < Knife def run validate_aws_config! custom_warnings! - + mfa_creds eni_list = [ ui.color("ID", :bold), ui.color("Status", :bold), diff --git a/lib/chef/knife/ec2_securitygroup_list.rb b/lib/chef/knife/ec2_securitygroup_list.rb index c47f9cc6..cf0c630a 100644 --- a/lib/chef/knife/ec2_securitygroup_list.rb +++ b/lib/chef/knife/ec2_securitygroup_list.rb @@ -29,7 +29,7 @@ class Ec2SecuritygroupList < Knife def run validate_aws_config! custom_warnings! - + mfa_creds sg_list = [ ui.color("ID", :bold), ui.color("Name", :bold), diff --git a/lib/chef/knife/ec2_server_create.rb b/lib/chef/knife/ec2_server_create.rb index 25c07b12..70d8859c 100644 --- a/lib/chef/knife/ec2_server_create.rb +++ b/lib/chef/knife/ec2_server_create.rb @@ -586,6 +586,10 @@ def plugin_validate_options! Chef::Config[:knife].delete(:aws_ssh_key_id) ui.warn("Use of aws_ssh_key_id option in knife.rb/config.rb config is deprecated, use ssh_key_name option instead.") end + # require 'byebug' + # byebug + mfa_creds + create_key_pair unless config_value(:ssh_key_name) validate_aws_config!(%i{image ssh_key_name aws_access_key_id aws_secret_access_key}) diff --git a/lib/chef/knife/ec2_server_delete.rb b/lib/chef/knife/ec2_server_delete.rb index 24ac5b6e..cb721131 100644 --- a/lib/chef/knife/ec2_server_delete.rb +++ b/lib/chef/knife/ec2_server_delete.rb @@ -69,6 +69,7 @@ def destroy_item(klass, name, type_name) def run validate_aws_config! + mfa_creds validate_instances! server_hashes.each do |h| diff --git a/lib/chef/knife/ec2_server_list.rb b/lib/chef/knife/ec2_server_list.rb index 3debece4..f02ebbc8 100644 --- a/lib/chef/knife/ec2_server_list.rb +++ b/lib/chef/knife/ec2_server_list.rb @@ -87,6 +87,7 @@ def run $stdout.sync = true validate_aws_config! + mfa_creds servers_list = [ ui.color("Instance ID", :bold), diff --git a/lib/chef/knife/ec2_subnet_list.rb b/lib/chef/knife/ec2_subnet_list.rb index a7bf3eb7..e1b892b6 100644 --- a/lib/chef/knife/ec2_subnet_list.rb +++ b/lib/chef/knife/ec2_subnet_list.rb @@ -29,7 +29,7 @@ class Ec2SubnetList < Knife def run validate_aws_config! custom_warnings! - + mfa_creds subnet_list = [ ui.color("ID", :bold), ui.color("State", :bold), diff --git a/lib/chef/knife/ec2_vpc_list.rb b/lib/chef/knife/ec2_vpc_list.rb index fdedfea3..017819cc 100644 --- a/lib/chef/knife/ec2_vpc_list.rb +++ b/lib/chef/knife/ec2_vpc_list.rb @@ -29,7 +29,7 @@ class Ec2VpcList < Knife def run validate_aws_config! custom_warnings! - + mfa_creds vpcs_list = [ ui.color("ID", :bold), ui.color("State", :bold),