From 1c96cf358158b572af3290113a4a18539c59f91e Mon Sep 17 00:00:00 2001 From: Rafael Goodman Date: Sat, 19 Dec 2015 12:36:00 -0500 Subject: [PATCH] ISSUE #423: Support using EC2-generated password as the WinRM password Adds a winrm_info provider capability to support using the EC2 GetPasswordData API as a means of getting the WinRM password. If the winrm.password is set to :aws, go fetch the AWS password data for the machine, decrypt the user-specified private key, and set it as the winrm.password --- README.md | 35 +++++++++++++ lib/vagrant-aws/action.rb | 9 ++++ lib/vagrant-aws/action/get_winrm_password.rb | 54 ++++++++++++++++++++ lib/vagrant-aws/capability.rb | 14 +++++ lib/vagrant-aws/plugin.rb | 7 +++ locales/en.yml | 2 + 6 files changed, 121 insertions(+) create mode 100644 lib/vagrant-aws/action/get_winrm_password.rb create mode 100644 lib/vagrant-aws/capability.rb diff --git a/README.md b/README.md index 62a5e936..a10c4b2d 100644 --- a/README.md +++ b/README.md @@ -279,6 +279,41 @@ Vagrant.configure("2") do |config| end ``` +### Windows WinRM passwords + +Want to use the EC2-generated Administrator password as the WinRM password for your Windows images? Use `:aws` as the WinRM password, and it will be fetched and decrypted using your private key. + +```ruby +Vagrant.configure("2") do |config| + # ... other stuff + + config.vm.communicator = "winrm" + config.winrm.username = "Administrator" + + config.vm.provider "aws" do |aws, override| + # Indicate that the password should be fetched and decrypted from AWS + override.winrm.password = :aws + + # private_key_path needed to decrypt the password + override.ssh.private_key_path = '~/mykey.pem' + + # keypair name corresponding to private_key_path + aws.keypair_name = "mykey" + + # Use a security group that allows WinRM port inbound (port 5985) + aws.security_groups = ['some_security_group_that_allows_winrm_inbound'] + + # Enable WinRM on the instance + aws.user_data = <<-USERDATA + + Enable-PSRemoting -Force + netsh advfirewall firewall add rule name="WinRM HTTP" dir=in localport=5985 protocol=TCP action=allow + + USERDATA + end +end +``` + ## Development To work on the `vagrant-aws` plugin, clone this repository out, and use diff --git a/lib/vagrant-aws/action.rb b/lib/vagrant-aws/action.rb index c140ac44..b975e64b 100644 --- a/lib/vagrant-aws/action.rb +++ b/lib/vagrant-aws/action.rb @@ -184,6 +184,14 @@ def self.action_reload end end + def self.action_get_winrm_password + Vagrant::Action::Builder.new.tap do |b| + b.use ConfigValidate + b.use ConnectAWS + b.use GetWinRMPassword + end + end + # The autoload farm action_root = Pathname.new(File.expand_path("../action", __FILE__)) autoload :ConnectAWS, action_root.join("connect_aws") @@ -204,6 +212,7 @@ def self.action_reload autoload :WarnNetworks, action_root.join("warn_networks") autoload :ElbRegisterInstance, action_root.join("elb_register_instance") autoload :ElbDeregisterInstance, action_root.join("elb_deregister_instance") + autoload :GetWinRMPassword, action_root.join("get_winrm_password") end end end diff --git a/lib/vagrant-aws/action/get_winrm_password.rb b/lib/vagrant-aws/action/get_winrm_password.rb new file mode 100644 index 00000000..dedb3d74 --- /dev/null +++ b/lib/vagrant-aws/action/get_winrm_password.rb @@ -0,0 +1,54 @@ +require "fog" +require "log4r" + +module VagrantPlugins + module AWS + module Action + # This action connects to AWS, verifies credentials work, and + # puts the AWS connection object into the `:aws_compute` key + # in the environment. + class GetWinRMPassword + def initialize(app, env) + @app = app + @logger = Log4r::Logger.new("vagrant_aws::action::get_winrm_password") + end + + def call(env) + machine = env[:machine] + + if machine.config.winrm.password == :aws && machine.config.ssh.private_key_path + machine.ui.info(I18n.t("vagrant_aws.getting_winrm_password")) + + aws = env[:aws_compute] + response = aws.get_password_data(machine.id) + password_data = response.body['passwordData'] + + if password_data + password_data_bytes = Base64.decode64(password_data) + + # Try to decrypt the password data using each one of the private key files + # set by the user until we hit one that decrypts successfully + machine.config.ssh.private_key_path.each do |private_key_path| + private_key_path = File.expand_path private_key_path + + @logger.info("Decrypting password data using #{private_key_path}") + rsa = OpenSSL::PKey::RSA.new File.read private_key_path + begin + machine.config.winrm.password = rsa.private_decrypt password_data_bytes + @logger.info("Successfully decrypted password data using #{private_key_path}") + rescue OpenSSL::PKey::RSAError + @logger.warn("Failed to decrypt password data using #{private_key_path}") + next + end + + break + end + end + end + + @app.call(env) + end + end + end + end +end diff --git a/lib/vagrant-aws/capability.rb b/lib/vagrant-aws/capability.rb new file mode 100644 index 00000000..d17ab4b0 --- /dev/null +++ b/lib/vagrant-aws/capability.rb @@ -0,0 +1,14 @@ +require "vagrant/action/builder" + +module VagrantPlugins + module AWS + module Capability + class WinRMInfo + def self.winrm_info(machine) + machine.action("get_winrm_password") + return {} + end + end + end + end +end diff --git a/lib/vagrant-aws/plugin.rb b/lib/vagrant-aws/plugin.rb index 2a93656a..99016863 100644 --- a/lib/vagrant-aws/plugin.rb +++ b/lib/vagrant-aws/plugin.rb @@ -34,6 +34,13 @@ class Plugin < Vagrant.plugin("2") Provider end + provider_capability(:aws, :winrm_info) do + setup_logging + + require_relative 'capability' + Capability::WinRMInfo + end + # This initializes the internationalization strings. def self.setup_i18n I18n.load_path << File.expand_path("locales/en.yml", AWS.source_root) diff --git a/locales/en.yml b/locales/en.yml index 6536e801..fd08e85c 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -62,6 +62,8 @@ en: will_not_destroy: |- The instance '%{name}' will not be destroyed, since the confirmation was declined. + getting_winrm_password: |- + Getting WinRM password from AWS... config: access_key_id_required: |-