From 8e879905996eb6e2f9bb75d0d16b892f2f405a12 Mon Sep 17 00:00:00 2001 From: Marc Siegfriedt Date: Fri, 21 Aug 2015 13:37:10 -0700 Subject: [PATCH 1/4] add the option to make elevated interactive scripts --- plugins/communicators/winrm/communicator.rb | 7 ++- .../winrm/scripts/elevated_shell.ps1.erb | 8 ++-- plugins/communicators/winrm/shell.rb | 1 + plugins/provisioners/shell/config.rb | 43 +++++++++++-------- plugins/provisioners/shell/provisioner.rb | 2 +- templates/locales/en.yml | 1 + .../docs/source/v2/provisioning/shell.html.md | 10 ++++- 7 files changed, 45 insertions(+), 27 deletions(-) mode change 100644 => 100755 plugins/communicators/winrm/communicator.rb mode change 100644 => 100755 plugins/communicators/winrm/scripts/elevated_shell.ps1.erb mode change 100644 => 100755 plugins/communicators/winrm/shell.rb mode change 100644 => 100755 plugins/provisioners/shell/config.rb mode change 100644 => 100755 plugins/provisioners/shell/provisioner.rb mode change 100644 => 100755 templates/locales/en.yml mode change 100644 => 100755 website/docs/source/v2/provisioning/shell.html.md diff --git a/plugins/communicators/winrm/communicator.rb b/plugins/communicators/winrm/communicator.rb old mode 100644 new mode 100755 index b14b28efa..d4d14e064 --- a/plugins/communicators/winrm/communicator.rb +++ b/plugins/communicators/winrm/communicator.rb @@ -136,10 +136,11 @@ module VagrantPlugins error_key: nil, # use the error_class message key good_exit: 0, shell: :powershell, + interactive: false, }.merge(opts || {}) opts[:good_exit] = Array(opts[:good_exit]) - command = wrap_in_scheduled_task(command) if opts[:elevated] + command = wrap_in_scheduled_task(command, opts[:interactive]) if opts[:elevated] output = shell.send(opts[:shell], command, &block) execution_output(output, opts) end @@ -195,7 +196,9 @@ module VagrantPlugins # @return The wrapper command to execute def wrap_in_scheduled_task(command) path = File.expand_path("../scripts/elevated_shell.ps1", __FILE__) - script = Vagrant::Util::TemplateRenderer.render(path) + script = Vagrant::Util::TemplateRenderer.render(path, options: { + interactive: interactive, + }) guest_script_path = "c:/tmp/vagrant-elevated-shell.ps1" file = Tempfile.new(["vagrant-elevated-shell", "ps1"]) begin diff --git a/plugins/communicators/winrm/scripts/elevated_shell.ps1.erb b/plugins/communicators/winrm/scripts/elevated_shell.ps1.erb old mode 100644 new mode 100755 index 17767e436..778dcb0cd --- a/plugins/communicators/winrm/scripts/elevated_shell.ps1.erb +++ b/plugins/communicators/winrm/scripts/elevated_shell.ps1.erb @@ -12,8 +12,8 @@ $task_xml = @' - {username} - Password + {user} + <%= options[:interactive] ? 'InteractiveTokenOrPassword' : 'Password' %> HighestAvailable @@ -55,7 +55,7 @@ $schedule.Connect() $task = $schedule.NewTask($null) $task.XmlText = $task_xml $folder = $schedule.GetFolder("\") -$folder.RegisterTaskDefinition($task_name, $task, 6, $username, $password, 1, $null) | Out-Null +$folder.RegisterTaskDefinition($task_name, $task, 6, $user, $password, <%= options[:interactive] ? 3 : 1 %>, $null) | Out-Null $registered_task = $folder.GetTask("\$task_name") $registered_task.Run($null) | Out-Null @@ -71,7 +71,7 @@ function SlurpOutput($out_file, $cur_line) { if (Test-Path $out_file) { get-content $out_file | select -skip $cur_line | ForEach { $cur_line += 1 - Write-Host "$_" + Write-Host "$_" } } return $cur_line diff --git a/plugins/communicators/winrm/shell.rb b/plugins/communicators/winrm/shell.rb old mode 100644 new mode 100755 index 9b0f4e302..1d277829a --- a/plugins/communicators/winrm/shell.rb +++ b/plugins/communicators/winrm/shell.rb @@ -23,6 +23,7 @@ module VagrantPlugins HTTPClient::KeepAliveDisconnected, WinRM::WinRMHTTPTransportError, WinRM::WinRMAuthorizationError, + WinRM::WinRMWSManFault, Errno::EACCES, Errno::EADDRINUSE, Errno::ECONNREFUSED, diff --git a/plugins/provisioners/shell/config.rb b/plugins/provisioners/shell/config.rb old mode 100644 new mode 100755 index 2f4957214..19d5180a9 --- a/plugins/provisioners/shell/config.rb +++ b/plugins/provisioners/shell/config.rb @@ -12,29 +12,32 @@ module VagrantPlugins attr_accessor :keep_color attr_accessor :name attr_accessor :powershell_args + attr_accessor :elevated_interactive def initialize - @args = UNSET_VALUE - @inline = UNSET_VALUE - @path = UNSET_VALUE - @upload_path = UNSET_VALUE - @privileged = UNSET_VALUE - @binary = UNSET_VALUE - @keep_color = UNSET_VALUE - @name = UNSET_VALUE - @powershell_args = UNSET_VALUE + @args = UNSET_VALUE + @inline = UNSET_VALUE + @path = UNSET_VALUE + @upload_path = UNSET_VALUE + @privileged = UNSET_VALUE + @binary = UNSET_VALUE + @keep_color = UNSET_VALUE + @name = UNSET_VALUE + @powershell_args = UNSET_VALUE + @elevated_interactive = UNSET_VALUE end def finalize! - @args = nil if @args == UNSET_VALUE - @inline = nil if @inline == UNSET_VALUE - @path = nil if @path == UNSET_VALUE - @upload_path = "/tmp/vagrant-shell" if @upload_path == UNSET_VALUE - @privileged = true if @privileged == UNSET_VALUE - @binary = false if @binary == UNSET_VALUE - @keep_color = false if @keep_color == UNSET_VALUE - @name = nil if @name == UNSET_VALUE - @powershell_args = "-ExecutionPolicy Bypass" if @powershell_args == UNSET_VALUE + @args = nil if @args == UNSET_VALUE + @inline = nil if @inline == UNSET_VALUE + @path = nil if @path == UNSET_VALUE + @upload_path = "/tmp/vagrant-shell" if @upload_path == UNSET_VALUE + @privileged = true if @privileged == UNSET_VALUE + @binary = false if @binary == UNSET_VALUE + @keep_color = false if @keep_color == UNSET_VALUE + @name = nil if @name == UNSET_VALUE + @powershell_args = "-ExecutionPolicy Bypass" if @powershell_args == UNSET_VALUE + @elevated_interactive = false if @elevated_interactive == UNSET_VALUE if @args && args_valid? @args = @args.is_a?(Array) ? @args.map { |a| a.to_s } : @args.to_s @@ -78,6 +81,10 @@ module VagrantPlugins errors << I18n.t("vagrant.provisioners.shell.args_bad_type") end + if @elevated_interactive == true && @privileged == false + errors << I18n.t("vagrant.provisioners.shell.interactive_not_elevated") + end + { "shell provisioner" => errors } end diff --git a/plugins/provisioners/shell/provisioner.rb b/plugins/provisioners/shell/provisioner.rb old mode 100644 new mode 100755 index 1b89d1f29..1abfc4889 --- a/plugins/provisioners/shell/provisioner.rb +++ b/plugins/provisioners/shell/provisioner.rb @@ -137,7 +137,7 @@ module VagrantPlugins end # Execute it with sudo - comm.sudo(command, elevated: config.privileged) do |type, data| + comm.sudo(command, { elevated: config.privileged, interactive: config.elevated_interactive }) do |type, data| handle_comm(type, data) end end diff --git a/templates/locales/en.yml b/templates/locales/en.yml old mode 100644 new mode 100755 index 9e8c680aa..56846292f --- a/templates/locales/en.yml +++ b/templates/locales/en.yml @@ -1996,6 +1996,7 @@ en: running: "Running: %{script}" runningas: "Running: %{local} as %{remote}" upload_path_not_set: "`upload_path` must be set for the shell provisioner." + interactive_not_elevated: "To be interactive, it must also be privileged." ansible: no_playbook: "`playbook` must be set for the Ansible provisioner." diff --git a/website/docs/source/v2/provisioning/shell.html.md b/website/docs/source/v2/provisioning/shell.html.md old mode 100644 new mode 100755 index ef4119668..783ce948e --- a/website/docs/source/v2/provisioning/shell.html.md +++ b/website/docs/source/v2/provisioning/shell.html.md @@ -45,8 +45,9 @@ The remainder of the available options are optional: defaults to "true". * `privileged` (boolean) - Specifies whether to execute the shell script - as a privileged user or not (`sudo`). By default this is "true". This has - no effect for Windows guests. + as a privileged user or not (`sudo`). By default this is "true". Windows + guests use a scheduled task to run as a true administrator without the + WinRM limitations. * `upload_path` (string) - Is the remote path where the shell script will be uploaded to. The script is uploaded as the SSH user over SCP, so this @@ -65,6 +66,11 @@ The remainder of the available options are optional: * `powershell_args` (string) - Extra arguments to pass to `PowerShell` if you're provisioning with PowerShell on Windows. +* `elevated_interactive` (boolean) - Run an elevated script in interactive mode + on Windows. By default this is "false". Must also be `privileged`. Be sure to + enable auto-login for Windows as the user must be logged in for interactive + mode to work. + ## Inline Scripts From 9d87be51daa69abea4cc82457a96dfdb9b5bfd92 Mon Sep 17 00:00:00 2001 From: Dan Dunckel Date: Mon, 12 Oct 2015 17:55:48 -0700 Subject: [PATCH 2/4] Small refactor on conditional check and add tests --- plugins/provisioners/shell/config.rb | 2 +- .../communicators/winrm/communicator_test.rb | 9 +++++++++ test/unit/plugins/provisioners/shell/config_test.rb | 13 +++++++++++++ 3 files changed, 23 insertions(+), 1 deletion(-) diff --git a/plugins/provisioners/shell/config.rb b/plugins/provisioners/shell/config.rb index 19d5180a9..aaadb8081 100755 --- a/plugins/provisioners/shell/config.rb +++ b/plugins/provisioners/shell/config.rb @@ -81,7 +81,7 @@ module VagrantPlugins errors << I18n.t("vagrant.provisioners.shell.args_bad_type") end - if @elevated_interactive == true && @privileged == false + if elevated_interactive && !privileged errors << I18n.t("vagrant.provisioners.shell.interactive_not_elevated") end diff --git a/test/unit/plugins/communicators/winrm/communicator_test.rb b/test/unit/plugins/communicators/winrm/communicator_test.rb index e98f646b3..829170bd6 100644 --- a/test/unit/plugins/communicators/winrm/communicator_test.rb +++ b/test/unit/plugins/communicators/winrm/communicator_test.rb @@ -93,6 +93,15 @@ describe VagrantPlugins::CommunicatorWinRM::Communicator do expect(subject.execute("dir", { elevated: true })).to eq(0) end + it "wraps command in elevated and interactive shell script when elevated and interactive are true" do + expect(shell).to receive(:upload).with(kind_of(String), "c:/tmp/vagrant-elevated-shell.ps1") + expect(shell).to receive(:powershell) do |cmd| + expect(cmd).to eq("powershell -executionpolicy bypass -file \"c:/tmp/vagrant-elevated-shell.ps1\" " + + "-username \"vagrant\" -password \"password\" -encoded_command \"ZABpAHIAOwAgAGUAeABpAHQAIAAkAEwAQQBTAFQARQBYAEkAVABDAE8ARABFAA==\"") + end.and_return({ exitcode: 0 }) + expect(subject.execute("dir", { elevated: true, interactive: true })).to eq(0) + end + it "can use cmd shell" do expect(shell).to receive(:cmd).with(kind_of(String)).and_return({ exitcode: 0 }) expect(subject.execute("dir", { shell: :cmd })).to eq(0) diff --git a/test/unit/plugins/provisioners/shell/config_test.rb b/test/unit/plugins/provisioners/shell/config_test.rb index 946b4c2a8..582740162 100644 --- a/test/unit/plugins/provisioners/shell/config_test.rb +++ b/test/unit/plugins/provisioners/shell/config_test.rb @@ -85,6 +85,19 @@ describe "VagrantPlugins::Shell::Config" do I18n.t("vagrant.provisioners.shell.args_bad_type") ]) end + + it "returns an error if elevated_interactive is true but privileged is false" do + subject.path = file_that_exists + subject.elevated_interactive = true + subject.privileged = false + subject.finalize! + + result = subject.validate(machine) + + expect(result["shell provisioner"]).to eq([ + I18n.t("vagrant.provisioners.shell.interactive_not_elevated") + ]) + end end describe 'finalize!' do From d859a3b75207f75f31165aacf0ec0dc137fae5b1 Mon Sep 17 00:00:00 2001 From: Dan Dunckel Date: Thu, 15 Oct 2015 12:36:19 -0700 Subject: [PATCH 3/4] Somehow I missed this param while resolving conflicts --- plugins/communicators/winrm/communicator.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/communicators/winrm/communicator.rb b/plugins/communicators/winrm/communicator.rb index d4d14e064..ade021aad 100755 --- a/plugins/communicators/winrm/communicator.rb +++ b/plugins/communicators/winrm/communicator.rb @@ -194,7 +194,7 @@ module VagrantPlugins # in place. # # @return The wrapper command to execute - def wrap_in_scheduled_task(command) + def wrap_in_scheduled_task(command, interactive) path = File.expand_path("../scripts/elevated_shell.ps1", __FILE__) script = Vagrant::Util::TemplateRenderer.render(path, options: { interactive: interactive, From aec65b5d66f48fa0036a5d1abe13557fbb1a0700 Mon Sep 17 00:00:00 2001 From: Dan Dunckel Date: Thu, 15 Oct 2015 12:41:08 -0700 Subject: [PATCH 4/4] Fix user to username that was lost in merge conflict resolution --- plugins/communicators/winrm/scripts/elevated_shell.ps1.erb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/communicators/winrm/scripts/elevated_shell.ps1.erb b/plugins/communicators/winrm/scripts/elevated_shell.ps1.erb index 778dcb0cd..d98bc7554 100755 --- a/plugins/communicators/winrm/scripts/elevated_shell.ps1.erb +++ b/plugins/communicators/winrm/scripts/elevated_shell.ps1.erb @@ -12,7 +12,7 @@ $task_xml = @' - {user} + {username} <%= options[:interactive] ? 'InteractiveTokenOrPassword' : 'Password' %> HighestAvailable @@ -55,7 +55,7 @@ $schedule.Connect() $task = $schedule.NewTask($null) $task.XmlText = $task_xml $folder = $schedule.GetFolder("\") -$folder.RegisterTaskDefinition($task_name, $task, 6, $user, $password, <%= options[:interactive] ? 3 : 1 %>, $null) | Out-Null +$folder.RegisterTaskDefinition($task_name, $task, 6, $username, $password, <%= options[:interactive] ? 3 : 1 %>, $null) | Out-Null $registered_task = $folder.GetTask("\$task_name") $registered_task.Run($null) | Out-Null