knife-windows 0.8.6 → 1.0.0.rc.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.travis.yml +17 -3
- data/CHANGELOG.md +25 -6
- data/DOC_CHANGES.md +323 -0
- data/Gemfile +2 -1
- data/README.md +160 -29
- data/RELEASE_NOTES.md +59 -6
- data/appveyor.yml +42 -0
- data/ci.gemfile +15 -0
- data/knife-windows.gemspec +4 -2
- data/lib/chef/knife/bootstrap/windows-chef-client-msi.erb +35 -21
- data/lib/chef/knife/bootstrap_windows_base.rb +155 -31
- data/lib/chef/knife/bootstrap_windows_ssh.rb +1 -1
- data/lib/chef/knife/bootstrap_windows_winrm.rb +17 -10
- data/lib/chef/knife/core/windows_bootstrap_context.rb +67 -16
- data/lib/chef/knife/windows_cert_generate.rb +155 -0
- data/lib/chef/knife/windows_cert_install.rb +62 -0
- data/lib/chef/knife/windows_helper.rb +3 -1
- data/lib/chef/knife/windows_listener_create.rb +100 -0
- data/lib/chef/knife/winrm.rb +84 -208
- data/lib/chef/knife/winrm_base.rb +36 -10
- data/lib/chef/knife/winrm_knife_base.rb +201 -0
- data/lib/chef/knife/winrm_session.rb +72 -0
- data/lib/chef/knife/winrm_shared_options.rb +47 -0
- data/lib/chef/knife/wsman_endpoint.rb +44 -0
- data/lib/chef/knife/wsman_test.rb +96 -0
- data/lib/knife-windows/path_helper.rb +77 -0
- data/lib/knife-windows/version.rb +1 -1
- data/spec/functional/bootstrap_download_spec.rb +41 -23
- data/spec/spec_helper.rb +11 -1
- data/spec/unit/knife/bootstrap_template_spec.rb +27 -27
- data/spec/unit/knife/bootstrap_windows_winrm_spec.rb +67 -23
- data/spec/unit/knife/core/windows_bootstrap_context_spec.rb +47 -0
- data/spec/unit/knife/windows_cert_generate_spec.rb +90 -0
- data/spec/unit/knife/windows_cert_install_spec.rb +35 -0
- data/spec/unit/knife/windows_listener_create_spec.rb +61 -0
- data/spec/unit/knife/winrm_session_spec.rb +47 -0
- data/spec/unit/knife/winrm_spec.rb +222 -56
- data/spec/unit/knife/wsman_test_spec.rb +176 -0
- 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.
|
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
|
-
|
77
|
-
@knife.
|
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 =>
|
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(:
|
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
|
-
|
105
|
-
|
106
|
-
allow(
|
107
|
-
allow(
|
108
|
-
|
109
|
-
|
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(:
|
121
|
-
|
122
|
-
|
123
|
-
allow(
|
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(:
|
131
|
-
session_mock =
|
132
|
-
allow(
|
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
|
-
|
141
|
-
allow(@winrm).to receive(:
|
142
|
-
session_mock =
|
143
|
-
allow(
|
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(:
|
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(:
|
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(:
|
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 "
|
294
|
+
context "when winrm_authentication_protocol specified" do
|
167
295
|
before do
|
168
|
-
Chef::Config[:knife] = {:winrm_transport =>
|
169
|
-
allow(@winrm).to receive(:
|
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 "
|
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
|
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 "
|
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
|
-
|
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
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
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
|
-
|
195
|
-
|
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
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
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
|
-
|
205
|
-
|
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 "
|
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.
|
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
|