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.
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
@@ -24,6 +24,9 @@ class Chef
24
24
  class Knife
25
25
  module WinrmBase
26
26
 
27
+ # It includes supported WinRM authentication protocol.
28
+ WINRM_AUTH_PROTOCOL_LIST ||= %w{basic negotiate kerberos}
29
+
27
30
  # :nodoc:
28
31
  # Would prefer to do this in a rational way, but can't be done b/c of
29
32
  # Mixlib::CLI's design :(
@@ -48,11 +51,19 @@ class Chef
48
51
  :description => "The WinRM password",
49
52
  :proc => Proc.new { |key| Chef::Config[:knife][:winrm_password] = key }
50
53
 
54
+ option :winrm_transport,
55
+ :short => "-t TRANSPORT",
56
+ :long => "--winrm-transport TRANSPORT",
57
+ :description => "The WinRM transport type. valid choices are [ssl, plaintext]",
58
+ :default => 'plaintext',
59
+ :proc => Proc.new { |transport| Chef::Config[:knife][:winrm_port] = '5986' if transport == 'ssl'
60
+ Chef::Config[:knife][:winrm_transport] = transport }
61
+
51
62
  option :winrm_port,
52
63
  :short => "-p PORT",
53
64
  :long => "--winrm-port PORT",
54
- :description => "The WinRM port, by default this is 5985",
55
- :default => "5985",
65
+ :description => "The WinRM port, by default this is '5985' for 'plaintext' and '5986' for 'ssl' winrm transport",
66
+ :default => '5985',
56
67
  :proc => Proc.new { |key| Chef::Config[:knife][:winrm_port] = key }
57
68
 
58
69
  option :identity_file,
@@ -60,15 +71,8 @@ class Chef
60
71
  :long => "--identity-file IDENTITY_FILE",
61
72
  :description => "The SSH identity file used for authentication"
62
73
 
63
- option :winrm_transport,
64
- :short => "-t TRANSPORT",
65
- :long => "--winrm-transport TRANSPORT",
66
- :description => "The WinRM transport type. valid choices are [ssl, plaintext]",
67
- :default => 'plaintext',
68
- :proc => Proc.new { |transport| Chef::Config[:knife][:winrm_transport] = transport }
69
-
70
74
  option :kerberos_keytab_file,
71
- :short => "-i KEYTAB_FILE",
75
+ :short => "-T KEYTAB_FILE",
72
76
  :long => "--keytab-file KEYTAB_FILE",
73
77
  :description => "The Kerberos keytab file used for authentication",
74
78
  :proc => Proc.new { |keytab| Chef::Config[:knife][:kerberos_keytab_file] = keytab }
@@ -91,9 +95,31 @@ class Chef
91
95
  :description => "The Certificate Authority (CA) trust file used for SSL transport",
92
96
  :proc => Proc.new { |trust| Chef::Config[:knife][:ca_trust_file] = trust }
93
97
 
98
+ option :winrm_ssl_verify_mode,
99
+ :long => "--winrm-ssl-verify-mode SSL_VERIFY_MODE",
100
+ :description => "The WinRM peer verification mode. Valid choices are [verify_peer, verify_none]",
101
+ :default => :verify_peer,
102
+ :proc => Proc.new { |verify_mode| verify_mode.to_sym }
103
+
104
+ option :winrm_authentication_protocol,
105
+ :long => "--winrm-authentication-protocol AUTHENTICATION_PROTOCOL",
106
+ :description => "The authentication protocol used during WinRM communication. The supported protocols are #{WINRM_AUTH_PROTOCOL_LIST.join(',')}. Default is 'negotiate'.",
107
+ :default => "negotiate",
108
+ :proc => Proc.new { |protocol| Chef::Config[:knife][:winrm_authentication_protocol] = protocol }
109
+
110
+ option :session_timeout,
111
+ :long => "--session-timeout Minutes",
112
+ :description => "The timeout for the client for the maximum length of the WinRM session",
113
+ :default => 30
94
114
  end
95
115
  end
96
116
 
117
+ def locate_config_value(key)
118
+ key = key.to_sym
119
+ value = config[key] || Chef::Config[:knife][key] || default_config[key]
120
+ Chef::Log.debug("Looking for key #{key} and found value #{value}")
121
+ value
122
+ end
97
123
  end
98
124
  end
99
125
  end
@@ -0,0 +1,201 @@
1
+ #
2
+ # Author:: Steven Murawski (<smurawski@chef.io)
3
+ # Copyright:: Copyright (c) 2015 Chef Software, 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
+
20
+ require 'chef/knife'
21
+ require 'chef/knife/winrm_base'
22
+ require 'chef/knife/winrm_shared_options'
23
+
24
+ class Chef
25
+ class Knife
26
+ module WinrmCommandSharedFunctions
27
+ def self.included(includer)
28
+ includer.class_eval do
29
+
30
+ @@ssl_warning_given = false
31
+
32
+ include Chef::Knife::WinrmBase
33
+ include Chef::Knife::WinrmSharedOptions
34
+
35
+ #Overrides Chef::Knife#configure_session, as that code is tied to the SSH implementation
36
+ #Tracked by Issue # 3042 / https://github.com/chef/chef/issues/3042
37
+ def configure_session
38
+ resolve_session_options
39
+ resolve_target_nodes
40
+ session_from_list
41
+ end
42
+
43
+ def resolve_target_nodes
44
+ @list = case config[:manual]
45
+ when true
46
+ @name_args[0].split(" ")
47
+ when false
48
+ r = Array.new
49
+ q = Chef::Search::Query.new
50
+ @action_nodes = q.search(:node, @name_args[0])[0]
51
+ @action_nodes.each do |item|
52
+ i = extract_nested_value(item, config[:attribute])
53
+ r.push(i) unless i.nil?
54
+ end
55
+ r
56
+ end
57
+ if @list.length == 0
58
+ if @action_nodes.length == 0
59
+ ui.fatal("No nodes returned from search!")
60
+ else
61
+ ui.fatal("#{@action_nodes.length} #{@action_nodes.length > 1 ? "nodes":"node"} found, " +
62
+ "but does not have the required attribute (#{config[:attribute]}) to establish the connection. " +
63
+ "Try setting another attribute to open the connection using --attribute.")
64
+ end
65
+ exit 10
66
+ end
67
+ end
68
+
69
+ def validate_password
70
+ if @session_opts[:user] and (not @session_opts[:password])
71
+ @session_opts[:password] = Chef::Config[:knife][:winrm_password] = config[:winrm_password] = get_password
72
+ end
73
+ end
74
+
75
+ private
76
+
77
+ def session_from_list
78
+ @list.each do |item|
79
+ Chef::Log.debug("Adding #{item}")
80
+ @session_opts[:host] = item
81
+ create_winrm_session(@session_opts)
82
+ end
83
+ end
84
+
85
+ def create_winrm_session(options={})
86
+ session = Chef::Knife::WinrmSession.new(options)
87
+ @winrm_sessions ||= []
88
+ @winrm_sessions.push(session)
89
+ end
90
+
91
+ def resolve_session_options
92
+ resolve_winrm_basic_options
93
+ resolve_winrm_auth_settings
94
+ resolve_winrm_kerberos_options
95
+ resolve_winrm_transport_options
96
+ resolve_winrm_ssl_options
97
+ end
98
+
99
+ def resolve_winrm_basic_options
100
+ @session_opts = {}
101
+ @session_opts[:user] = locate_config_value(:winrm_user)
102
+ @session_opts[:password] = locate_config_value(:winrm_password)
103
+ @session_opts[:port] = locate_config_value(:winrm_port)
104
+
105
+ #30 min (Default) OperationTimeout for long bootstraps fix for KNIFE_WINDOWS-8
106
+ @session_opts[:operation_timeout] = locate_config_value(:session_timeout).to_i * 60 if locate_config_value(:session_timeout)
107
+ end
108
+
109
+ def resolve_winrm_kerberos_options
110
+ if config.keys.any? {|k| k.to_s =~ /kerberos/ }
111
+ @session_opts[:transport] = :kerberos
112
+ @session_opts[:keytab] = locate_config_value(:kerberos_keytab_file) if locate_config_value(:kerberos_keytab_file)
113
+ @session_opts[:realm] = locate_config_value(:kerberos_realm) if locate_config_value(:kerberos_realm)
114
+ @session_opts[:service] = locate_config_value(:kerberos_service) if locate_config_value(:kerberos_service)
115
+ end
116
+ end
117
+
118
+ def resolve_winrm_transport_options
119
+ @session_opts[:disable_sspi] = true
120
+ @session_opts[:transport] = locate_config_value(:winrm_transport).to_sym unless @session_opts[:transport] == :kerberos
121
+ if negotiate_auth? && @session_opts[:transport] == :ssl
122
+ Chef::Log.debug("Trying WinRM communication with negotiate authentication and :ssl transport")
123
+ elsif use_windows_native_auth?
124
+ load_windows_specific_gems
125
+ @session_opts[:transport] = :sspinegotiate
126
+ @session_opts[:disable_sspi] = false
127
+ elsif negotiate_auth? && !Chef::Platform.windows?
128
+ ui.warn "The '--winrm-authentication-protocol = negotiate' with 'plaintext' transport is only supported when this tool is invoked from a Windows-based system."
129
+ ui.info "Try '--winrm-authentication-protocol = basic'"
130
+ exit 1
131
+ end
132
+ end
133
+
134
+ def resolve_winrm_ssl_options
135
+ @session_opts[:ca_trust_path] = locate_config_value(:ca_trust_file) if locate_config_value(:ca_trust_file)
136
+ @session_opts[:no_ssl_peer_verification] = no_ssl_peer_verification?(@session_opts[:ca_trust_path])
137
+ warn_no_ssl_peer_verification if @session_opts[:no_ssl_peer_verification]
138
+ end
139
+
140
+ def resolve_winrm_auth_settings
141
+ winrm_auth_protocol = locate_config_value(:winrm_authentication_protocol)
142
+ if ! Chef::Knife::WinrmBase::WINRM_AUTH_PROTOCOL_LIST.include?(winrm_auth_protocol)
143
+ ui.error "Invalid value '#{winrm_auth_protocol}' for --winrm-authentication-protocol option."
144
+ ui.info "Valid values are #{Chef::Knife::WinrmBase::WINRM_AUTH_PROTOCOL_LIST.join(",")}."
145
+ exit 1
146
+ end
147
+
148
+ if winrm_auth_protocol == "basic"
149
+ @session_opts[:basic_auth_only] = true
150
+ else
151
+ @session_opts[:basic_auth_only] = false
152
+ end
153
+ end
154
+
155
+ def no_ssl_peer_verification?(ca_trust_path)
156
+ ca_trust_path.nil? && (config[:winrm_ssl_verify_mode] == :verify_none)
157
+ end
158
+
159
+ def use_windows_native_auth?
160
+ Chef::Platform.windows? && @session_opts[:transport] != :ssl && negotiate_auth?
161
+ end
162
+
163
+ def load_windows_specific_gems
164
+ require 'winrm-s'
165
+ Chef::Log.debug("Applied 'winrm-s' monkey patch and trying WinRM communication with 'sspinegotiate'")
166
+ end
167
+
168
+ def get_password
169
+ @password ||= ui.ask("Enter your password: ") { |q| q.echo = false }
170
+ end
171
+
172
+ # returns true if winrm_authentication_protocol is 'negotiate'
173
+ def negotiate_auth?
174
+ locate_config_value(:winrm_authentication_protocol) == "negotiate"
175
+ end
176
+
177
+ def warn_no_ssl_peer_verification
178
+ if ! @@ssl_warning_given
179
+ @@ssl_warning_given = true
180
+ ui.warn(<<-WARN)
181
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
182
+ SSL validation of HTTPS requests for the WinRM transport is disabled. HTTPS WinRM
183
+ connections are still encrypted, but knife is not able to detect forged replies
184
+ or spoofing attacks.
185
+
186
+ To fix this issue add an entry like this to your knife configuration file:
187
+
188
+ ```
189
+ # Verify all WinRM HTTPS connections (default, recommended)
190
+ knife[:winrm_ssl_verify_mode] = :verify_peer
191
+ ```
192
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
193
+ WARN
194
+ end
195
+ end
196
+
197
+ end
198
+ end
199
+ end
200
+ end
201
+ end
@@ -0,0 +1,72 @@
1
+ #
2
+ # Author:: Steven Murawski <smurawski@chef.io>
3
+ # Copyright:: Copyright (c) 2015 Chef Software, 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 'winrm'
20
+
21
+ class Chef
22
+ class Knife
23
+ class WinrmSession
24
+ attr_reader :host, :endpoint, :port, :output, :error, :exit_code
25
+ def initialize(options)
26
+ @host = options[:host]
27
+ @port = options[:port]
28
+ url = "#{options[:host]}:#{options[:port]}/wsman"
29
+ scheme = options[:transport] == :ssl ? 'https' : 'http'
30
+ @endpoint = "#{scheme}://#{url}"
31
+ opts = Hash.new
32
+ opts = {:user => options[:user], :pass => options[:password], :basic_auth_only => options[:basic_auth_only], :disable_sspi => options[:disable_sspi], :no_ssl_peer_verification => options[:no_ssl_peer_verification]}
33
+
34
+ options[:transport] == :kerberos ? opts.merge!({:service => options[:service], :realm => options[:realm], :keytab => options[:keytab]}) : opts.merge!({:ca_trust_path => options[:ca_trust_path]})
35
+
36
+ Chef::Log.debug("WinRM::WinRMWebService options: #{opts}")
37
+ Chef::Log.debug("Endpoint: #{endpoint}")
38
+ Chef::Log.debug("Transport: #{options[:transport]}")
39
+ @winrm_session = WinRM::WinRMWebService.new(@endpoint, options[:transport], opts)
40
+ @winrm_session.set_timeout(options[:operation_timeout]) if options[:operation_timeout]
41
+ end
42
+
43
+ def relay_command(command)
44
+ remote_id = @winrm_session.open_shell
45
+ command_id = @winrm_session.run_command(remote_id, command)
46
+ Chef::Log.debug("#{@host}[#{remote_id}] => :run_command[#{command}]")
47
+ session_result = get_output(remote_id, command_id)
48
+ @winrm_session.cleanup_command(remote_id, command_id)
49
+ Chef::Log.debug("#{@host}[#{remote_id}] => :command_cleanup[#{command}]")
50
+ @exit_code = session_result[:exitcode]
51
+ @winrm_session.close_shell(remote_id)
52
+ Chef::Log.debug("#{@host}[#{remote_id}] => :shell_close")
53
+ end
54
+
55
+ def get_output(remote_id, command_id)
56
+ @winrm_session.get_command_output(remote_id, command_id) do |out,error|
57
+ print_data(@host, out) if out
58
+ print_data(@host, error, :red) if error
59
+ end
60
+ end
61
+
62
+ def print_data(host, data, color = :cyan)
63
+ if data =~ /\n/
64
+ data.split(/\n/).each { |d| print_data(host, d, color) }
65
+ elsif !data.nil?
66
+ print Chef::Knife::Winrm.ui.color(host, color)
67
+ puts " #{data}"
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,47 @@
1
+ #
2
+ # Author:: Steven Murawski (<smurawski@chef.io)
3
+ # Copyright:: Copyright (c) 2015 Chef Software, 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 'chef/knife'
20
+ require 'chef/encrypted_data_bag_item'
21
+ require 'kconv'
22
+
23
+ class Chef
24
+ class Knife
25
+ module WinrmSharedOptions
26
+
27
+ # Shared command line options for knife winrm and knife wsman test
28
+ def self.included(includer)
29
+ includer.class_eval do
30
+ option :manual,
31
+ :short => "-m",
32
+ :long => "--manual-list",
33
+ :boolean => true,
34
+ :description => "QUERY is a space separated list of servers",
35
+ :default => false
36
+
37
+ option :attribute,
38
+ :short => "-a ATTR",
39
+ :long => "--attribute ATTR",
40
+ :description => "The attribute to use for opening the connection - default is fqdn",
41
+ :default => "fqdn"
42
+ end
43
+ end
44
+
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,44 @@
1
+ #
2
+ # Author:: Steven Murawski (<smurawski@chef.io)
3
+ # Copyright:: Copyright (c) 2015 Chef Software, 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
+ class Chef
20
+ class Knife
21
+ class WsmanEndpoint
22
+ attr_accessor :host
23
+ attr_accessor :wsman_port
24
+ attr_accessor :wsman_url
25
+ attr_accessor :product_version
26
+ attr_accessor :protocol_version
27
+ attr_accessor :product_vendor
28
+ attr_accessor :response_status_code
29
+ attr_accessor :error_message
30
+
31
+ def initialize(name, port, url)
32
+ @host = name
33
+ @wsman_port = port
34
+ @wsman_url = url
35
+ end
36
+
37
+ def to_hash
38
+ hash = {}
39
+ instance_variables.each {|var| hash[var.to_s.delete("@")] = instance_variable_get(var) }
40
+ hash
41
+ end
42
+ end
43
+ end
44
+ end