knife-windows 0.8.6 → 1.0.0.rc.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.travis.yml +17 -3
  4. data/CHANGELOG.md +25 -6
  5. data/DOC_CHANGES.md +323 -0
  6. data/Gemfile +2 -1
  7. data/README.md +160 -29
  8. data/RELEASE_NOTES.md +59 -6
  9. data/appveyor.yml +42 -0
  10. data/ci.gemfile +15 -0
  11. data/knife-windows.gemspec +4 -2
  12. data/lib/chef/knife/bootstrap/windows-chef-client-msi.erb +35 -21
  13. data/lib/chef/knife/bootstrap_windows_base.rb +155 -31
  14. data/lib/chef/knife/bootstrap_windows_ssh.rb +1 -1
  15. data/lib/chef/knife/bootstrap_windows_winrm.rb +17 -10
  16. data/lib/chef/knife/core/windows_bootstrap_context.rb +67 -16
  17. data/lib/chef/knife/windows_cert_generate.rb +155 -0
  18. data/lib/chef/knife/windows_cert_install.rb +62 -0
  19. data/lib/chef/knife/windows_helper.rb +3 -1
  20. data/lib/chef/knife/windows_listener_create.rb +100 -0
  21. data/lib/chef/knife/winrm.rb +84 -208
  22. data/lib/chef/knife/winrm_base.rb +36 -10
  23. data/lib/chef/knife/winrm_knife_base.rb +201 -0
  24. data/lib/chef/knife/winrm_session.rb +72 -0
  25. data/lib/chef/knife/winrm_shared_options.rb +47 -0
  26. data/lib/chef/knife/wsman_endpoint.rb +44 -0
  27. data/lib/chef/knife/wsman_test.rb +96 -0
  28. data/lib/knife-windows/path_helper.rb +77 -0
  29. data/lib/knife-windows/version.rb +1 -1
  30. data/spec/functional/bootstrap_download_spec.rb +41 -23
  31. data/spec/spec_helper.rb +11 -1
  32. data/spec/unit/knife/bootstrap_template_spec.rb +27 -27
  33. data/spec/unit/knife/bootstrap_windows_winrm_spec.rb +67 -23
  34. data/spec/unit/knife/core/windows_bootstrap_context_spec.rb +47 -0
  35. data/spec/unit/knife/windows_cert_generate_spec.rb +90 -0
  36. data/spec/unit/knife/windows_cert_install_spec.rb +35 -0
  37. data/spec/unit/knife/windows_listener_create_spec.rb +61 -0
  38. data/spec/unit/knife/winrm_session_spec.rb +47 -0
  39. data/spec/unit/knife/winrm_spec.rb +222 -56
  40. data/spec/unit/knife/wsman_test_spec.rb +176 -0
  41. metadata +51 -20
@@ -25,6 +25,18 @@ describe Chef::Knife::Core::WindowsBootstrapContext do
25
25
  allow(Chef::Knife::Core::WindowsBootstrapContext).to receive(:new).and_return(mock_bootstrap_context)
26
26
  end
27
27
 
28
+ describe "validation_key", :chef_gte_12_only do
29
+ before do
30
+ mock_bootstrap_context.instance_variable_set(:@config, Mash.new(:validation_key => "C:\\chef\\key.pem"))
31
+ end
32
+
33
+ it "should return false if validation_key does not exist" do
34
+ allow(::File).to receive(:expand_path)
35
+ allow(::File).to receive(:exist?).and_return(false)
36
+ expect(mock_bootstrap_context.validation_key).to eq(false)
37
+ end
38
+ end
39
+
28
40
  describe "latest_current_windows_chef_version_query" do
29
41
  it "returns the major version of the current version of Chef" do
30
42
  stub_const("Chef::VERSION", '11.1.2')
@@ -51,4 +63,39 @@ describe Chef::Knife::Core::WindowsBootstrapContext do
51
63
  end
52
64
  end
53
65
  end
66
+
67
+ describe "msi_url" do
68
+ context "when config option is not set" do
69
+ before do
70
+ expect(mock_bootstrap_context).to receive(:latest_current_windows_chef_version_query).and_return("&v=something")
71
+ end
72
+
73
+ it "returns a chef.io msi url with minimal url parameters" do
74
+ reference_url = "https://www.chef.io/chef/download?p=windows&v=something"
75
+ expect(mock_bootstrap_context.msi_url).to eq(reference_url)
76
+ end
77
+
78
+ it "returns a chef.io msi url with provided url parameters substituted" do
79
+ reference_url = "https://www.chef.io/chef/download?p=windows&pv=machine&m=arch&DownloadContext=ctx&v=something"
80
+ expect(mock_bootstrap_context.msi_url('machine', 'arch', 'ctx')).to eq(reference_url)
81
+ end
82
+ end
83
+
84
+ context "when msi_url config option is set" do
85
+ let(:custom_url) { "file://something" }
86
+
87
+ before do
88
+ mock_bootstrap_context.instance_variable_set(:@config, Mash.new(:msi_url => custom_url))
89
+ end
90
+
91
+ it "returns the overriden url" do
92
+ expect(mock_bootstrap_context.msi_url).to eq(custom_url)
93
+ end
94
+
95
+ it "doesn't introduce any unnecessary query parameters if provided by the template" do
96
+ expect(mock_bootstrap_context.msi_url('machine', 'arch', 'ctx')).to eq(custom_url)
97
+ end
98
+ end
99
+ end
100
+
54
101
  end
@@ -0,0 +1,90 @@
1
+ #
2
+ # Author:: Mukta Aphale <mukta.aphale@clogeny.com>
3
+ # Copyright:: Copyright (c) 2014 Opscode, Inc.
4
+ # License:: Apache License, Version 2.0
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+
19
+ require 'spec_helper'
20
+ require 'chef/knife/windows_cert_generate'
21
+ require 'openssl'
22
+
23
+ describe Chef::Knife::WindowsCertGenerate do
24
+ before(:all) do
25
+ @certgen = Chef::Knife::WindowsCertGenerate.new(["-H","something.mydomain.com"])
26
+ end
27
+
28
+ it "generates RSA key pair" do
29
+ @certgen.config[:key_length] = 2048
30
+ key = @certgen.generate_keypair
31
+ expect(key).to be_instance_of OpenSSL::PKey::RSA
32
+ end
33
+
34
+ it "generates X509 certificate" do
35
+ @certgen.config[:domain] = "test.com"
36
+ @certgen.config[:cert_validity] = "24"
37
+ key = @certgen.generate_keypair
38
+ certificate = @certgen.generate_certificate key
39
+ expect(certificate).to be_instance_of OpenSSL::X509::Certificate
40
+ end
41
+
42
+ it "writes certificate to file" do
43
+ expect(File).to receive(:open).exactly(3).times
44
+ cert = double(OpenSSL::X509::Certificate.new)
45
+ key = double(OpenSSL::PKey::RSA.new)
46
+ @certgen.config[:cert_passphrase] = "password"
47
+ expect(OpenSSL::PKCS12).to receive(:create).with("password", "winrmcert", key, cert)
48
+ @certgen.write_certificate_to_file cert, "test", key
49
+ end
50
+
51
+ context "when creating certificate files" do
52
+ before do
53
+ @certgen.thumbprint = "TEST_THUMBPRINT"
54
+ allow(Dir).to receive(:glob).and_return([])
55
+ allow(@certgen).to receive(:generate_keypair)
56
+ allow(@certgen).to receive(:generate_certificate)
57
+ expect(@certgen.ui).to receive(:info).with("Generated Certificates:")
58
+ expect(@certgen.ui).to receive(:info).with("- winrmcert.pfx - PKCS12 format key pair. Contains public and private keys, can be used with an SSL server.")
59
+ expect(@certgen.ui).to receive(:info).with("- winrmcert.b64 - Base64 encoded PKCS12 key pair. Contains public and private keys, used by some cloud provider API's to configure SSL servers.")
60
+ expect(@certgen.ui).to receive(:info).with("- winrmcert.pem - Base64 encoded public certificate only. Required by the client to connect to the server.")
61
+ expect(@certgen.ui).to receive(:info).with("Certificate Thumbprint: TEST_THUMBPRINT")
62
+ end
63
+
64
+ it "writes out certificates" do
65
+ @certgen.config[:output_file] = 'winrmcert'
66
+
67
+ expect(@certgen).to receive(:certificates_already_exist?).and_return(false)
68
+ expect(@certgen).to receive(:write_certificate_to_file)
69
+ @certgen.run
70
+ end
71
+
72
+ it "prompts when certificates already exist" do
73
+ file_path = 'winrmcert'
74
+ @certgen.config[:output_file] = file_path
75
+
76
+ allow(Dir).to receive(:glob).and_return([file_path])
77
+ expect(@certgen).to receive(:confirm).with("Do you really want to overwrite existing certificates")
78
+ expect(@certgen).to receive(:write_certificate_to_file)
79
+ @certgen.run
80
+ end
81
+
82
+ it "creates certificate on specified file path" do
83
+ file_path = "/tmp/winrmcert"
84
+ @certgen.name_args = [file_path]
85
+
86
+ expect(@certgen).to receive(:write_certificate_to_file) # FIXME: this should be testing that we get /tmp/winrmcert as the filename
87
+ @certgen.run
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,35 @@
1
+ #
2
+ # Author:: Mukta Aphale <mukta.aphale@clogeny.com>
3
+ # Copyright:: Copyright (c) 2014 Opscode, Inc.
4
+ # License:: Apache License, Version 2.0
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+
19
+ require 'spec_helper'
20
+ require 'chef/knife/windows_cert_install'
21
+
22
+ describe Chef::Knife::WindowsCertInstall do
23
+ before(:all) do
24
+ @certinstall = Chef::Knife::WindowsCertInstall.new
25
+ end
26
+
27
+ it "installs certificate" do
28
+ @certinstall.name_args = ["test-path"]
29
+ @certinstall.config[:cert_passphrase] = "your-secret!"
30
+ expect(@certinstall).to receive(:`).with("powershell.exe -Command \" 'your-secret!' | certutil -importPFX 'test-path' AT_KEYEXCHANGE\"")
31
+ expect(@certinstall.ui).to receive(:info).with("Certificate added to Certificate Store")
32
+ expect(@certinstall.ui).to receive(:info).with("Adding certificate to the Windows Certificate Store...")
33
+ @certinstall.run
34
+ end
35
+ end
@@ -0,0 +1,61 @@
1
+ #
2
+ # Author:: Mukta Aphale <mukta.aphale@clogeny.com>
3
+ # Copyright:: Copyright (c) 2014 Opscode, Inc.
4
+ # License:: Apache License, Version 2.0
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+
19
+ require 'spec_helper'
20
+ require 'chef/knife/windows_listener_create'
21
+
22
+ describe Chef::Knife::WindowsListenerCreate, :windows_only do
23
+ before(:all) do
24
+ @listener = Chef::Knife::WindowsListenerCreate.new
25
+ end
26
+
27
+ it "creates winrm listener" do
28
+ @listener.config[:hostname] = "host"
29
+ @listener.config[:cert_thumbprint] = "CERT-THUMBPRINT"
30
+ @listener.config[:port] = "5986"
31
+ expect(@listener).to receive(:`).with("winrm create winrm/config/Listener?Address=*+Transport=HTTPS @{Hostname=\"host\";CertificateThumbprint=\"CERT-THUMBPRINT\";Port=\"5986\"}")
32
+ expect(@listener.ui).to receive(:info).with("WinRM listener created with Port: 5986 and CertificateThumbprint: CERT-THUMBPRINT")
33
+ @listener.run
34
+ end
35
+
36
+ it "raise an error on command failure" do
37
+ @listener.config[:hostname] = "host"
38
+ @listener.config[:cert_thumbprint] = "CERT-THUMBPRINT"
39
+ @listener.config[:port] = "5986"
40
+ @listener.config[:basic_auth] = true
41
+ expect(@listener).to receive(:`).with("winrm create winrm/config/Listener?Address=*+Transport=HTTPS @{Hostname=\"host\";CertificateThumbprint=\"CERT-THUMBPRINT\";Port=\"5986\"}")
42
+ expect($?).to receive(:exitstatus).and_return(100)
43
+ expect(@listener.ui).to receive(:error).with("Error creating WinRM listener. use -VV for more details.")
44
+ expect(@listener.ui).to_not receive(:info).with("WinRM listener created with Port: 5986 and CertificateThumbprint: CERT-THUMBPRINT")
45
+ @listener.run
46
+ end
47
+
48
+ it "creates winrm listener with cert install option" do
49
+ @listener.config[:hostname] = "host"
50
+ @listener.config[:cert_thumbprint] = "CERT-THUMBPRINT"
51
+ @listener.config[:port] = "5986"
52
+ @listener.config[:cert_install] = true
53
+ allow(@listener).to receive(:get_cert_passphrase).and_return("your-secret!")
54
+ expect(@listener).to receive(:`).with("powershell.exe -Command \" 'your-secret!' | certutil -importPFX 'true' AT_KEYEXCHANGE\"")
55
+ expect(@listener).to receive(:`).with("powershell.exe -Command \" echo (Get-PfxCertificate true).thumbprint \"")
56
+ expect(@listener.ui).to receive(:info).with("Certificate installed to Certificate Store")
57
+ expect(@listener.ui).to receive(:info).with("Certificate Thumbprint: ")
58
+ allow(@listener).to receive(:puts)
59
+ @listener.run
60
+ end
61
+ end
@@ -0,0 +1,47 @@
1
+ #
2
+ # Author:: Steven Murawski <smurawski@chef.io>
3
+ # Copyright:: Copyright (c) 2015 Opscode, Inc.
4
+ # License:: Apache License, Version 2.0
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+
19
+ require 'spec_helper'
20
+
21
+ Chef::Knife::Winrm.load_deps
22
+
23
+
24
+ describe Chef::Knife::WinrmSession do
25
+ describe "#relay_command" do
26
+ before do
27
+ @service_mock = Object.new
28
+ @service_mock.define_singleton_method(:open_shell){}
29
+ @service_mock.define_singleton_method(:run_command){}
30
+ @service_mock.define_singleton_method(:cleanup_command){}
31
+ @service_mock.define_singleton_method(:get_command_output){|t,y| {}}
32
+ @service_mock.define_singleton_method(:close_shell){}
33
+ allow(Chef::Knife::WinrmSession).to receive(:new).with(hash_including(:transport => :plaintext)).and_call_original
34
+ allow(WinRM::WinRMWebService).to receive(:new).and_return(@service_mock)
35
+ @session = Chef::Knife::WinrmSession.new({transport: :plaintext})
36
+ end
37
+
38
+ it "run command and display commands output" do
39
+ expect(@service_mock).to receive(:open_shell).ordered
40
+ expect(@service_mock).to receive(:run_command).ordered
41
+ expect(@service_mock).to receive(:get_command_output).ordered.and_return({})
42
+ expect(@service_mock).to receive(:cleanup_command).ordered
43
+ expect(@service_mock).to receive(:close_shell).ordered
44
+ @session.relay_command("cmd.exe echo 'hi'")
45
+ end
46
+ end
47
+ end
@@ -51,6 +51,7 @@ describe Chef::Knife::Winrm do
51
51
  context "when there are some hosts found but they do not have an attribute to connect with" do
52
52
  before do
53
53
  @knife.config[:manual] = false
54
+ @knife.config[:winrm_password] = 'P@ssw0rd!'
54
55
  allow(@query).to receive(:search).and_return([[@node_foo, @node_bar]])
55
56
  @node_foo.automatic_attrs[:fqdn] = nil
56
57
  @node_bar.automatic_attrs[:fqdn] = nil
@@ -60,30 +61,154 @@ describe Chef::Knife::Winrm do
60
61
  it "should raise a specific error (KNIFE-222)" do
61
62
  expect(@knife.ui).to receive(:fatal).with(/does not have the required attribute/)
62
63
  expect(@knife).to receive(:exit).with(10)
63
- @knife.configure_session
64
+ @knife.configure_chef
65
+ @knife.resolve_target_nodes
64
66
  end
65
67
  end
66
68
 
67
69
  context "when there are nested attributes" do
68
70
  before do
69
71
  @knife.config[:manual] = false
72
+ @knife.config[:winrm_password] = 'P@ssw0rd!'
70
73
  allow(@query).to receive(:search).and_return([[@node_foo, @node_bar]])
71
74
  allow(Chef::Search::Query).to receive(:new).and_return(@query)
72
75
  end
73
76
 
74
77
  it "should use nested attributes (KNIFE-276)" do
75
78
  @knife.config[:attribute] = "ec2.public_hostname"
76
- allow(@knife).to receive(:session_from_list)
77
- @knife.configure_session
78
-
79
+ @knife.configure_chef
80
+ @knife.resolve_target_nodes
79
81
  end
80
82
  end
81
83
 
82
84
  describe Chef::Knife::Winrm do
85
+ context "when configuring the WinRM transport" do
86
+ before(:all) do
87
+ @winrm_session = Object.new
88
+ @winrm_session.define_singleton_method(:set_timeout){|timeout| ""}
89
+ end
90
+ after(:each) do
91
+ Chef::Config.configuration = @original_config
92
+ Chef::Config[:knife] = @original_knife_config if @original_knife_config
93
+ end
94
+
95
+ context "on windows workstations" do
96
+ let(:winrm_command_windows_http) { Chef::Knife::Winrm.new(['-m', 'localhost', '-x', 'testuser', '-P', 'testpassword', 'echo helloworld']) }
97
+ it "should default to negotiate when on a Windows host" do
98
+ allow(Chef::Platform).to receive(:windows?).and_return(true)
99
+ expect(winrm_command_windows_http).to receive(:load_windows_specific_gems)
100
+ expect(Chef::Knife::WinrmSession).to receive(:new).with(hash_including(:transport => :sspinegotiate)).and_call_original
101
+ expect(WinRM::WinRMWebService).to receive(:new).with('http://localhost:5985/wsman', anything, anything).and_return(@winrm_session)
102
+ winrm_command_windows_http.configure_chef
103
+ winrm_command_windows_http.configure_session
104
+ end
105
+ end
106
+
107
+ context "on non-windows workstations" do
108
+ before do
109
+ allow(Chef::Platform).to receive(:windows?).and_return(false)
110
+ end
111
+
112
+ let(:winrm_command_http) { Chef::Knife::Winrm.new(['-m', 'localhost', '-x', 'testuser', '-P', 'testpassword', '-t', 'plaintext', '--winrm-authentication-protocol', 'basic', 'echo helloworld']) }
113
+ it "should default to the http uri scheme" do
114
+ expect(Chef::Knife::WinrmSession).to receive(:new).with(hash_including(:transport => :plaintext)).and_call_original
115
+ expect(WinRM::WinRMWebService).to receive(:new).with('http://localhost:5985/wsman', anything, anything).and_return(@winrm_session)
116
+ winrm_command_http.configure_chef
117
+ winrm_command_http.configure_session
118
+ end
119
+
120
+ it "set operation timeout and verify default" do
121
+ expect(Chef::Knife::WinrmSession).to receive(:new).with(hash_including(:transport => :plaintext)).and_call_original
122
+ expect(WinRM::WinRMWebService).to receive(:new).with('http://localhost:5985/wsman', anything, anything).and_return(@winrm_session)
123
+ expect(@winrm_session).to receive(:set_timeout).with(1800)
124
+ winrm_command_http.configure_chef
125
+ winrm_command_http.configure_session
126
+ end
127
+
128
+ it "should set user specified winrm port" do
129
+ Chef::Config[:knife] = {winrm_port: "5988"}
130
+ expect(Chef::Knife::WinrmSession).to receive(:new).with(hash_including(:transport => :plaintext)).and_call_original
131
+ expect(WinRM::WinRMWebService).to receive(:new).with('http://localhost:5988/wsman', anything, anything).and_return(@winrm_session)
132
+ winrm_command_http.configure_chef
133
+ winrm_command_http.configure_session
134
+ end
135
+
136
+ let(:winrm_command_timeout) { Chef::Knife::Winrm.new(['-m', 'localhost', '-x', 'testuser', '-P', 'testpassword', '--winrm-authentication-protocol', 'basic', '--session-timeout', '10', 'echo helloworld']) }
137
+
138
+ it "set operation timeout and verify 10 Minute timeout" do
139
+ expect(Chef::Knife::WinrmSession).to receive(:new).with(hash_including(:transport => :plaintext)).and_call_original
140
+ expect(WinRM::WinRMWebService).to receive(:new).with('http://localhost:5985/wsman', anything, anything).and_return(@winrm_session)
141
+ expect(@winrm_session).to receive(:set_timeout).with(600)
142
+ winrm_command_timeout.configure_chef
143
+ winrm_command_timeout.configure_session
144
+ end
145
+
146
+ let(:winrm_command_https) { Chef::Knife::Winrm.new(['-m', 'localhost', '-x', 'testuser', '-P', 'testpassword', '--winrm-transport', 'ssl', 'echo helloworld']) }
147
+
148
+ it "should use the https uri scheme if the ssl transport is specified" do
149
+ Chef::Config[:knife] = {:winrm_transport => 'ssl'}
150
+ expect(Chef::Knife::WinrmSession).to receive(:new).with(hash_including(:transport => :ssl)).and_call_original
151
+ expect(WinRM::WinRMWebService).to receive(:new).with('https://localhost:5986/wsman', anything, anything).and_return(@winrm_session)
152
+ winrm_command_https.configure_chef
153
+ winrm_command_https.configure_session
154
+ end
155
+
156
+ it "should use the winrm port '5986' by default for ssl transport" do
157
+ Chef::Config[:knife] = {:winrm_transport => 'ssl'}
158
+ expect(Chef::Knife::WinrmSession).to receive(:new).with(hash_including(:transport => :ssl)).and_call_original
159
+ expect(WinRM::WinRMWebService).to receive(:new).with('https://localhost:5986/wsman', anything, anything).and_return(@winrm_session)
160
+ winrm_command_https.configure_chef
161
+ winrm_command_https.configure_session
162
+ end
163
+
164
+ it "should default to validating the server when the ssl transport is used" do
165
+ expect(Chef::Knife::WinrmSession).to receive(:new).with(hash_including(:transport => :ssl)).and_call_original
166
+ expect(WinRM::WinRMWebService).to receive(:new).with(anything, anything, hash_including(:no_ssl_peer_verification => false)).and_return(@winrm_session)
167
+ winrm_command_https.configure_chef
168
+ winrm_command_https.configure_session
169
+ end
170
+
171
+ let(:winrm_command_verify_peer) { Chef::Knife::Winrm.new(['-m', 'localhost', '-x', 'testuser', '-P', 'testpassword', '--winrm-transport', 'ssl', '--winrm-ssl-verify-mode', 'verify_peer', 'echo helloworld'])}
172
+ it "should validate the server when the ssl transport is used and the :winrm_ssl_verify_mode option is not configured to :verify_none" do
173
+ expect(Chef::Knife::WinrmSession).to receive(:new).with(hash_including(:transport => :ssl)).and_call_original
174
+ expect(WinRM::WinRMWebService).to receive(:new).with(anything, anything, hash_including(:no_ssl_peer_verification => false)).and_return(@winrm_session)
175
+ winrm_command_verify_peer.configure_chef
176
+ winrm_command_verify_peer.configure_session
177
+ end
178
+
179
+ let(:winrm_command_no_verify) { Chef::Knife::Winrm.new(['-m', 'localhost', '-x', 'testuser', '-P', 'testpassword', '--winrm-transport', 'ssl', '--winrm-ssl-verify-mode', 'verify_none', 'echo helloworld'])}
180
+
181
+ it "should not validate the server when the ssl transport is used and the :winrm_ssl_verify_mode option is set to :verify_none" do
182
+ expect(Chef::Knife::WinrmSession).to receive(:new).with(hash_including(:transport => :ssl)).and_call_original
183
+ expect(WinRM::WinRMWebService).to receive(:new).with(anything, anything, hash_including(:no_ssl_peer_verification => true)).and_return(@winrm_session)
184
+ winrm_command_no_verify.configure_chef
185
+ winrm_command_no_verify.configure_session
186
+ end
187
+
188
+ it "should provide warning output when the :winrm_ssl_verify_mode set to :verify_none to disable server validation" do
189
+ expect(Chef::Knife::WinrmSession).to receive(:new).with(hash_including(:transport => :ssl)).and_call_original
190
+ expect(WinRM::WinRMWebService).to receive(:new).with(anything, anything, hash_including(:no_ssl_peer_verification => true)).and_return(@winrm_session)
191
+ expect(winrm_command_no_verify).to receive(:warn_no_ssl_peer_verification)
192
+
193
+ winrm_command_no_verify.configure_chef
194
+ winrm_command_no_verify.configure_session
195
+ end
196
+
197
+ let(:winrm_command_ca_trust) { Chef::Knife::Winrm.new(['-m', 'localhost', '-x', 'testuser', '-P', 'testpassword', '--winrm-transport', 'ssl', '--ca-trust-file', '~/catrustroot', '--winrm-ssl-verify-mode', 'verify_none', 'echo helloworld'])}
198
+
199
+ it "should validate the server when the ssl transport is used and the :ca_trust_file option is specified even if the :winrm_ssl_verify_mode option is set to :verify_none" do
200
+ expect(Chef::Knife::WinrmSession).to receive(:new).with(hash_including(:transport => :ssl)).and_call_original
201
+ expect(WinRM::WinRMWebService).to receive(:new).with(anything, anything, hash_including(:no_ssl_peer_verification => false)).and_return(@winrm_session)
202
+ winrm_command_ca_trust.configure_chef
203
+ winrm_command_ca_trust.configure_session
204
+ end
205
+ end
206
+ end
207
+
83
208
  context "when executing the run command which sets the process exit code" do
84
209
  before(:each) do
85
- Chef::Config[:knife] = {:winrm_transport => :http}
86
- @winrm = Chef::Knife::Winrm.new(['-m', 'localhost', '-x', 'testuser', '-P', 'testpassword', 'echo helloworld'])
210
+ Chef::Config[:knife] = {:winrm_transport => 'plaintext'}
211
+ @winrm = Chef::Knife::Winrm.new(['-m', 'localhost', '-x', 'testuser', '-P', 'testpassword', '--winrm-authentication-protocol', 'basic', 'echo helloworld'])
87
212
  end
88
213
 
89
214
  after(:each) do
@@ -92,44 +217,44 @@ describe Chef::Knife::Winrm do
92
217
  end
93
218
 
94
219
  it "should return with 0 if the command succeeds" do
95
- allow(@winrm).to receive(:winrm_command).and_return(0)
220
+ allow(@winrm).to receive(:exit)
221
+ allow(@winrm).to receive(:relay_winrm_command).and_return(0)
96
222
  exit_code = @winrm.run
97
223
  expect(exit_code).to be_zero
98
224
  end
99
225
 
100
226
  it "should exit the process with exact exit status if the command fails and returns config is set to 0" do
101
227
  command_status = 510
228
+ session_mock = Chef::Knife::WinrmSession.new({:transport => :plaintext, :host => 'localhost', :port => '5985'})
229
+
102
230
  @winrm.config[:returns] = "0"
103
231
  Chef::Config[:knife][:returns] = [0]
104
- allow(@winrm).to receive(:winrm_command).and_return(command_status)
105
- session_mock = EventMachine::WinRM::Session.new
106
- allow(EventMachine::WinRM::Session).to receive(:new).and_return(session_mock)
107
- allow(session_mock).to receive(:exit_codes).and_return({"thishost" => command_status})
108
- begin
109
- @winrm.run
110
- expect(0).to eq(510)
111
- rescue Exception => e
112
- expect(e.status).to eq(command_status)
113
- end
232
+
233
+ allow(@winrm).to receive(:relay_winrm_command)
234
+ allow(@winrm.ui).to receive(:error)
235
+ allow(Chef::Knife::WinrmSession).to receive(:new).and_return(session_mock)
236
+ allow(session_mock).to receive(:exit_code).and_return(command_status)
237
+ expect { @winrm.run_with_pretty_exceptions }.to raise_error(SystemExit) { |e| expect(e.status).to eq(command_status) }
114
238
  end
115
239
 
116
240
  it "should exit the process with non-zero status if the command fails and returns config is set to 0" do
117
241
  command_status = 1
118
242
  @winrm.config[:returns] = "0,53"
119
243
  Chef::Config[:knife][:returns] = [0,53]
120
- allow(@winrm).to receive(:winrm_command).and_return(command_status)
121
- session_mock = EventMachine::WinRM::Session.new
122
- allow(EventMachine::WinRM::Session).to receive(:new).and_return(session_mock)
123
- allow(session_mock).to receive(:exit_codes).and_return({"thishost" => command_status})
244
+ allow(@winrm).to receive(:relay_winrm_command).and_return(command_status)
245
+ allow(@winrm.ui).to receive(:error)
246
+ session_mock = Chef::Knife::WinrmSession.new({:transport => :plaintext, :host => 'localhost', :port => '5985'})
247
+ allow(Chef::Knife::WinrmSession).to receive(:new).and_return(session_mock)
248
+ allow(session_mock).to receive(:exit_code).and_return(command_status)
124
249
  expect { @winrm.run_with_pretty_exceptions }.to raise_error(SystemExit) { |e| expect(e.status).to eq(command_status) }
125
250
  end
126
251
 
127
252
  it "should exit the process with a zero status if the command returns an expected non-zero status" do
128
253
  command_status = 53
129
254
  Chef::Config[:knife][:returns] = [0,53]
130
- allow(@winrm).to receive(:winrm_command).and_return(command_status)
131
- session_mock = EventMachine::WinRM::Session.new
132
- allow(EventMachine::WinRM::Session).to receive(:new).and_return(session_mock)
255
+ allow(@winrm).to receive(:relay_winrm_command).and_return(command_status)
256
+ session_mock = Chef::Knife::WinrmSession.new({:transport => :plaintext, :host => 'localhost', :port => '5985'})
257
+ allow(Chef::Knife::WinrmSession).to receive(:new).and_return(session_mock)
133
258
  allow(session_mock).to receive(:exit_codes).and_return({"thishost" => command_status})
134
259
  exit_code = @winrm.run
135
260
  expect(exit_code).to be_zero
@@ -137,82 +262,123 @@ describe Chef::Knife::Winrm do
137
262
 
138
263
  it "should exit the process with a zero status if the command returns an expected non-zero status" do
139
264
  command_status = 53
140
- Chef::Config[:knife][:returns] = [0,53]
141
- allow(@winrm).to receive(:winrm_command).and_return(command_status)
142
- session_mock = EventMachine::WinRM::Session.new
143
- allow(EventMachine::WinRM::Session).to receive(:new).and_return(session_mock)
265
+ @winrm.config[:returns] = '0,53'
266
+ allow(@winrm).to receive(:relay_winrm_command).and_return(command_status)
267
+ session_mock = Chef::Knife::WinrmSession.new({:transport => :plaintext, :host => 'localhost', :port => '5985'})
268
+ allow(Chef::Knife::WinrmSession).to receive(:new).and_return(session_mock)
144
269
  allow(session_mock).to receive(:exit_codes).and_return({"thishost" => command_status})
145
270
  exit_code = @winrm.run
146
271
  expect(exit_code).to be_zero
147
272
  end
148
273
 
149
274
  it "should exit the process with 100 if command execution raises an exception other than 401" do
150
- allow(@winrm).to receive(:winrm_command).and_raise(WinRM::WinRMHTTPTransportError.new('500', '500'))
275
+ allow(@winrm).to receive(:relay_winrm_command).and_raise(WinRM::WinRMHTTPTransportError.new('', '500'))
276
+ allow(@winrm.ui).to receive(:error)
151
277
  expect { @winrm.run_with_pretty_exceptions }.to raise_error(SystemExit) { |e| expect(e.status).to eq(100) }
152
278
  end
153
279
 
154
280
  it "should exit the process with 100 if command execution raises a 401" do
155
- allow(@winrm).to receive(:winrm_command).and_raise(WinRM::WinRMHTTPTransportError.new('401', '401'))
281
+ allow(@winrm).to receive(:relay_winrm_command).and_raise(WinRM::WinRMHTTPTransportError.new('', '401'))
282
+ allow(@winrm.ui).to receive(:info)
283
+ allow(@winrm.ui).to receive(:error)
156
284
  expect { @winrm.run_with_pretty_exceptions }.to raise_error(SystemExit) { |e| expect(e.status).to eq(100) }
157
285
  end
158
286
 
159
287
  it "should exit the process with 0 if command execution raises a 401 and suppress_auth_failure is set to true" do
160
288
  @winrm.config[:suppress_auth_failure] = true
161
- allow(@winrm).to receive(:winrm_command).and_raise(WinRM::WinRMHTTPTransportError.new('401', '401'))
289
+ allow(@winrm).to receive(:relay_winrm_command).and_raise(WinRM::WinRMHTTPTransportError.new('', '401'))
162
290
  exit_code = @winrm.run_with_pretty_exceptions
163
291
  expect(exit_code).to eq(401)
164
292
  end
165
293
 
166
- context "validate sspinegotiate transport option" do
294
+ context "when winrm_authentication_protocol specified" do
167
295
  before do
168
- Chef::Config[:knife] = {:winrm_transport => :plaintext}
169
- allow(@winrm).to receive(:winrm_command).and_return(0)
296
+ Chef::Config[:knife] = {:winrm_transport => 'plaintext'}
297
+ allow(@winrm).to receive(:relay_winrm_command).and_return(0)
170
298
  end
171
299
 
172
- it "should have winrm opts transport set to sspinegotiate for windows" do
300
+ it "set sspinegotiate transport on windows for 'negotiate' authentication" do
301
+ @winrm.config[:winrm_authentication_protocol] = "negotiate"
173
302
  @winrm.config[:winrm_user] = "domain\\testuser"
174
303
  allow(Chef::Platform).to receive(:windows?).and_return(true)
175
304
  allow(@winrm).to receive(:require).with('winrm-s').and_return(true)
176
- expect(@winrm.session).to receive(:use).with("localhost", {:user=>"domain\\testuser", :password=>"testpassword", :port=>nil, :operation_timeout=>1800, :basic_auth_only=>false, :transport=>:sspinegotiate, :disable_sspi=>false})
305
+ expect(@winrm).to receive(:create_winrm_session).with({:user=>"domain\\testuser", :password=>"testpassword", :port=>"5985", :no_ssl_peer_verification => false, :basic_auth_only=>false, :operation_timeout=>1800, :transport=>:sspinegotiate, :disable_sspi=>false, :host=>"localhost"})
306
+ exit_code = @winrm.run
307
+ end
308
+
309
+ it "should not have winrm opts transport set to sspinegotiate for unix" do
310
+ @winrm.config[:winrm_authentication_protocol] = "negotiate"
311
+ allow(Chef::Platform).to receive(:windows?).and_return(false)
312
+ allow(@winrm).to receive(:exit)
313
+ expect(@winrm).to receive(:create_winrm_session).with({:user=>"testuser", :password=>"testpassword", :port=>"5985", :no_ssl_peer_verification=>false, :basic_auth_only=>false, :operation_timeout=>1800, :transport=>:plaintext, :disable_sspi=>true, :host=>"localhost"})
177
314
  exit_code = @winrm.run
178
315
  end
179
316
 
180
- it "should use the winrm monkey patch for windows" do
317
+ it "apply winrm monkey patch on windows if 'negotiate' authentication and 'plaintext' transport is specified", :windows_only => true do
318
+ @winrm.config[:winrm_authentication_protocol] = "negotiate"
181
319
  @winrm.config[:winrm_user] = "domain\\testuser"
182
320
  allow(Chef::Platform).to receive(:windows?).and_return(true)
183
- expect(@winrm).to receive(:require).with('winrm-s')
321
+ allow(@winrm.ui).to receive(:warn)
322
+ expect(@winrm).to receive(:require).with('winrm-s').and_call_original
323
+ exit_code = @winrm.run
324
+ end
184
325
 
326
+ it "raise an error if value is other than [basic, negotiate, kerberos]" do
327
+ @winrm.config[:winrm_authentication_protocol] = "invalid"
328
+ @winrm.config[:winrm_user] = "domain\\testuser"
329
+ allow(Chef::Platform).to receive(:windows?).and_return(true)
330
+ expect(@winrm.ui).to receive(:error)
331
+ expect(@winrm).to receive(:exit)
185
332
  exit_code = @winrm.run
186
333
  end
187
334
 
188
- context "when domain name not given" do
189
- it "should skip winrm monkey patch for windows" do
190
- @winrm.config[:winrm_user] = "testuser"
191
- allow(Chef::Platform).to receive(:windows?).and_return(true)
192
- expect(@winrm).to_not receive(:require).with('winrm-s')
335
+ it "skip winrm monkey patch for 'basic' authentication" do
336
+ @winrm.config[:winrm_authentication_protocol] = "basic"
337
+ @winrm.config[:winrm_user] = "domain\\testuser"
338
+ allow(Chef::Platform).to receive(:windows?).and_return(true)
339
+ expect(@winrm).to_not receive(:require).with('winrm-s')
340
+ exit_code = @winrm.run
341
+ end
193
342
 
194
- exit_code = @winrm.run
195
- end
343
+ it "skip winrm monkey patch for 'kerberos' authentication" do
344
+ @winrm.config[:winrm_authentication_protocol] = "kerberos"
345
+ @winrm.config[:winrm_user] = "domain\\testuser"
346
+ allow(Chef::Platform).to receive(:windows?).and_return(true)
347
+ expect(@winrm).to_not receive(:require).with('winrm-s')
348
+ exit_code = @winrm.run
196
349
  end
197
350
 
198
- context "when local domain name given" do
199
- it "should use the winrm monkey patch for windows" do
200
- @winrm.config[:winrm_user] = ".\\testuser"
201
- allow(Chef::Platform).to receive(:windows?).and_return(true)
202
- expect(@winrm).to receive(:require).with('winrm-s')
351
+ it "skip winrm monkey patch for 'ssl' transport and 'negotiate' authentication" do
352
+ @winrm.config[:winrm_authentication_protocol] = "negotiate"
353
+ @winrm.config[:winrm_transport] = "ssl"
354
+ @winrm.config[:winrm_user] = "domain\\testuser"
355
+ allow(Chef::Platform).to receive(:windows?).and_return(true)
356
+ expect(@winrm).to_not receive(:require).with('winrm-s')
357
+ exit_code = @winrm.run
358
+ end
203
359
 
204
- exit_code = @winrm.run
205
- end
360
+ it "disable sspi and skip winrm monkey patch for 'ssl' transport and 'basic' authentication" do
361
+ @winrm.config[:winrm_authentication_protocol] = "basic"
362
+ @winrm.config[:winrm_transport] = "ssl"
363
+ @winrm.config[:winrm_user] = "domain\\testuser"
364
+ @winrm.config[:winrm_port] = "5986"
365
+ allow(Chef::Platform).to receive(:windows?).and_return(true)
366
+ expect(@winrm).to receive(:create_winrm_session).with({:user=>"domain\\testuser", :password=>"testpassword", :port=>"5986", :no_ssl_peer_verification=>false, :basic_auth_only=>true, :operation_timeout=>1800, :transport=>:ssl, :disable_sspi=>true, :host=>"localhost"})
367
+ expect(@winrm).to_not receive(:require).with('winrm-s')
368
+ exit_code = @winrm.run
206
369
  end
207
370
 
208
- it "should not have winrm opts transport set to sspinegotiate for unix" do
371
+ it "raise error on linux for 'negotiate' authentication" do
372
+ @winrm.config[:winrm_authentication_protocol] = "negotiate"
373
+ @winrm.config[:winrm_transport] = "plaintext"
374
+ @winrm.config[:winrm_user] = "domain\\testuser"
375
+ allow(@winrm).to receive(:exit)
209
376
  allow(Chef::Platform).to receive(:windows?).and_return(false)
210
-
211
- expect(@winrm.session).to receive(:use).with("localhost", {:user=>"testuser", :password=>"testpassword", :port=>nil, :operation_timeout=>1800, :basic_auth_only=>true, :transport=>:plaintext, :disable_sspi=>true})
377
+ expect(@winrm).to_not receive(:require).with('winrm-s')
378
+ expect(@winrm.ui).to receive(:warn)
212
379
  exit_code = @winrm.run
213
380
  end
214
381
  end
215
-
216
382
  end
217
383
  end
218
384
  end