knife-winops 2.0.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 (54) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +5 -0
  3. data/.rspec +3 -0
  4. data/.travis.yml +30 -0
  5. data/CHANGELOG.md +147 -0
  6. data/DOC_CHANGES.md +22 -0
  7. data/Gemfile +13 -0
  8. data/LICENSE +201 -0
  9. data/README.md +430 -0
  10. data/RELEASE_NOTES.md +17 -0
  11. data/Rakefile +21 -0
  12. data/appveyor.yml +36 -0
  13. data/ci.gemfile +15 -0
  14. data/knife-winops.gemspec +26 -0
  15. data/lib/chef/knife/bootstrap/Chef_bootstrap.erb +44 -0
  16. data/lib/chef/knife/bootstrap/bootstrap.ps1 +134 -0
  17. data/lib/chef/knife/bootstrap/tail.cmd +15 -0
  18. data/lib/chef/knife/bootstrap/windows-chef-client-msi.erb +302 -0
  19. data/lib/chef/knife/bootstrap_windows_base.rb +473 -0
  20. data/lib/chef/knife/bootstrap_windows_ssh.rb +115 -0
  21. data/lib/chef/knife/bootstrap_windows_winrm.rb +102 -0
  22. data/lib/chef/knife/core/windows_bootstrap_context.rb +356 -0
  23. data/lib/chef/knife/knife_windows_base.rb +33 -0
  24. data/lib/chef/knife/windows_cert_generate.rb +155 -0
  25. data/lib/chef/knife/windows_cert_install.rb +68 -0
  26. data/lib/chef/knife/windows_helper.rb +36 -0
  27. data/lib/chef/knife/windows_listener_create.rb +107 -0
  28. data/lib/chef/knife/winrm.rb +127 -0
  29. data/lib/chef/knife/winrm_base.rb +128 -0
  30. data/lib/chef/knife/winrm_knife_base.rb +315 -0
  31. data/lib/chef/knife/winrm_session.rb +101 -0
  32. data/lib/chef/knife/winrm_shared_options.rb +54 -0
  33. data/lib/chef/knife/wsman_endpoint.rb +44 -0
  34. data/lib/chef/knife/wsman_test.rb +118 -0
  35. data/lib/knife-winops/path_helper.rb +242 -0
  36. data/lib/knife-winops/version.rb +6 -0
  37. data/spec/assets/fake_trusted_certs/excluded.txt +2 -0
  38. data/spec/assets/fake_trusted_certs/github.pem +42 -0
  39. data/spec/assets/fake_trusted_certs/google.crt +41 -0
  40. data/spec/assets/win_fake_trusted_cert_script.txt +89 -0
  41. data/spec/dummy_winrm_connection.rb +21 -0
  42. data/spec/functional/bootstrap_download_spec.rb +229 -0
  43. data/spec/spec_helper.rb +93 -0
  44. data/spec/unit/knife/bootstrap_options_spec.rb +164 -0
  45. data/spec/unit/knife/bootstrap_template_spec.rb +98 -0
  46. data/spec/unit/knife/bootstrap_windows_winrm_spec.rb +410 -0
  47. data/spec/unit/knife/core/windows_bootstrap_context_spec.rb +292 -0
  48. data/spec/unit/knife/windows_cert_generate_spec.rb +90 -0
  49. data/spec/unit/knife/windows_cert_install_spec.rb +51 -0
  50. data/spec/unit/knife/windows_listener_create_spec.rb +76 -0
  51. data/spec/unit/knife/winrm_session_spec.rb +101 -0
  52. data/spec/unit/knife/winrm_spec.rb +494 -0
  53. data/spec/unit/knife/wsman_test_spec.rb +209 -0
  54. metadata +157 -0
@@ -0,0 +1,54 @@
1
+ #
2
+ # Original knife-windows author:: Steven Murawski (<smurawski@chef.io)
3
+ # Copyright:: Copyright (c) 2015-2016 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
+
43
+ option :concurrency,
44
+ :short => "-C NUM",
45
+ :long => "--concurrency NUM",
46
+ :description => "The number of allowed concurrent connections",
47
+ :default => 1,
48
+ :proc => lambda { |o| o.to_i }
49
+ end
50
+ end
51
+
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,44 @@
1
+ #
2
+ # Original knife-windows author:: Steven Murawski (<smurawski@chef.io)
3
+ # Copyright:: Copyright (c) 2015-2016 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
@@ -0,0 +1,118 @@
1
+ #
2
+ # Original knife-windows author:: Steven Murawski (<smurawski@chef.io>)
3
+ # Copyright:: Copyright (c) 2015-2016 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 'httpclient'
20
+ require 'chef/knife'
21
+ require 'chef/knife/winrm_knife_base'
22
+ require 'chef/knife/wsman_endpoint'
23
+
24
+ class Chef
25
+ class Knife
26
+ class WsmanTest < Knife
27
+
28
+ include Chef::Knife::WinrmCommandSharedFunctions
29
+
30
+ deps do
31
+ require 'chef/search/query'
32
+ end
33
+
34
+ banner "knife wsman test QUERY (options)"
35
+
36
+ def run
37
+ # pass a dummy password to avoid prompt for password
38
+ # but it does nothing
39
+ @config[:winrm_password] = 'cute_little_kittens'
40
+
41
+ configure_session
42
+ verify_wsman_accessiblity_for_nodes
43
+ end
44
+
45
+ private
46
+
47
+ def verify_wsman_accessiblity_for_nodes
48
+ error_count = 0
49
+ @winrm_sessions.each do |item|
50
+ Chef::Log.debug("checking for WSMAN availability at #{item.endpoint}")
51
+
52
+ ssl_error = nil
53
+ begin
54
+ response = post_identity_request(item.endpoint)
55
+ ui.msg "Connected successfully to #{item.host} at #{item.endpoint}."
56
+ rescue Exception => err
57
+ end
58
+
59
+ output_object = parse_response(item, response)
60
+ output_object.error_message += "\r\nError returned from endpoint: #{err.message}" if err
61
+
62
+ unless output_object.error_message.nil?
63
+ ui.warn "Failed to connect to #{item.host} at #{item.endpoint}."
64
+ if err && err.is_a?(OpenSSL::SSL::SSLError)
65
+ ui.warn "Failure due to an issue with SSL; likely cause would be unsuccessful certificate verification."
66
+ ui.warn "Either ensure your certificate is valid or use '--winrm-ssl-verify-mode verify_none' to ignore verification failures."
67
+ end
68
+ error_count += 1
69
+ end
70
+
71
+ if config[:verbosity] >= 1
72
+ output(output_object)
73
+ end
74
+ end
75
+ if error_count > 0
76
+ ui.error "Failed to connect to #{error_count} nodes."
77
+ exit 1
78
+ end
79
+ end
80
+
81
+ def post_identity_request(endpoint)
82
+ xml = '<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:wsmid="http://schemas.dmtf.org/wbem/wsman/identity/1/wsmanidentity.xsd"><s:Header/><s:Body><wsmid:Identify/></s:Body></s:Envelope>'
83
+ header = {
84
+ 'WSMANIDENTIFY' => 'unauthenticated',
85
+ 'Content-Type' => 'application/soap+xml; charset=UTF-8'
86
+ }
87
+
88
+ client = HTTPClient.new
89
+ Chef::HTTP::DefaultSSLPolicy.new(client.ssl_config).set_custom_certs
90
+ client.ssl_config.verify_mode = OpenSSL::SSL::VERIFY_NONE if resolve_no_ssl_peer_verification
91
+ client.post(endpoint, xml, header)
92
+ end
93
+
94
+ def parse_response(node, response)
95
+ output_object = Chef::Knife::WsmanEndpoint.new(node.host, node.port, node.endpoint)
96
+ output_object.response_status_code = response.status_code unless response.nil?
97
+
98
+ if response.nil? || response.status_code != 200
99
+ output_object.error_message = "No valid WSMan endoint listening at #{node.endpoint}."
100
+ else
101
+ doc = REXML::Document.new(response.body)
102
+ output_object.protocol_version = search_xpath(doc, "//wsmid:ProtocolVersion")
103
+ output_object.product_version = search_xpath(doc, "//wsmid:ProductVersion")
104
+ output_object.product_vendor = search_xpath(doc, "//wsmid:ProductVendor")
105
+ if output_object.protocol_version.to_s.strip.length == 0
106
+ output_object.error_message = "Endpoint #{node.endpoint} on #{node.host} does not appear to be a WSMAN endpoint. Response body was #{response.body}"
107
+ end
108
+ end
109
+ output_object
110
+ end
111
+
112
+ def search_xpath(document, property_name)
113
+ result = REXML::XPath.match(document, property_name)
114
+ result[0].nil? ? '' : result[0].text
115
+ end
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,242 @@
1
+ #
2
+ # Original knife-windows author:: Bryan McLellan <btm@loftninjas.org>
3
+ # Copyright:: Copyright (c) 2014-2016 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
+ # Sourced from Chef::Util::PathHelper.
20
+ # Should be removed when Chef 11 catches up or we stop supporting Chef 11
21
+
22
+ module Knife
23
+ module Windows
24
+ class PathHelper
25
+ # Maximum characters in a standard Windows path (260 including drive letter and NUL)
26
+ WIN_MAX_PATH = 259
27
+
28
+ def self.dirname(path)
29
+ if Chef::Platform.windows?
30
+ # Find the first slash, not counting trailing slashes
31
+ end_slash = path.size
32
+ while true
33
+ slash = path.rindex(/[#{Regexp.escape(File::SEPARATOR)}#{Regexp.escape(path_separator)}]/, end_slash - 1)
34
+ if !slash
35
+ return end_slash == path.size ? '.' : path_separator
36
+ elsif slash == end_slash - 1
37
+ end_slash = slash
38
+ else
39
+ return path[0..slash-1]
40
+ end
41
+ end
42
+ else
43
+ ::File.dirname(path)
44
+ end
45
+ end
46
+
47
+ BACKSLASH = '\\'.freeze
48
+
49
+ def self.path_separator
50
+ if Chef::Platform.windows?
51
+ File::ALT_SEPARATOR || BACKSLASH
52
+ else
53
+ File::SEPARATOR
54
+ end
55
+ end
56
+
57
+ def self.join(*args)
58
+ args.flatten.inject do |joined_path, component|
59
+ # Joined path ends with /
60
+ joined_path = joined_path.sub(/[#{Regexp.escape(File::SEPARATOR)}#{Regexp.escape(path_separator)}]+$/, '')
61
+ component = component.sub(/^[#{Regexp.escape(File::SEPARATOR)}#{Regexp.escape(path_separator)}]+/, '')
62
+ joined_path += "#{path_separator}#{component}"
63
+ end
64
+ end
65
+
66
+ def self.validate_path(path)
67
+ if Chef::Platform.windows?
68
+ unless printable?(path)
69
+ msg = "Path '#{path}' contains non-printable characters. Check that backslashes are escaped with another backslash (e.g. C:\\\\Windows) in double-quoted strings."
70
+ Chef::Log.error(msg)
71
+ raise Chef::Exceptions::ValidationFailed, msg
72
+ end
73
+
74
+ if windows_max_length_exceeded?(path)
75
+ Chef::Log.debug("Path '#{path}' is longer than #{WIN_MAX_PATH}, prefixing with'\\\\?\\'")
76
+ path.insert(0, "\\\\?\\")
77
+ end
78
+ end
79
+
80
+ path
81
+ end
82
+
83
+ def self.windows_max_length_exceeded?(path)
84
+ # Check to see if paths without the \\?\ prefix are over the maximum allowed length for the Windows API
85
+ # http://msdn.microsoft.com/en-us/library/windows/desktop/aa365247%28v=vs.85%29.aspx
86
+ unless path =~ /^\\\\?\\/
87
+ if path.length > WIN_MAX_PATH
88
+ return true
89
+ end
90
+ end
91
+
92
+ false
93
+ end
94
+
95
+ def self.printable?(string)
96
+ # returns true if string is free of non-printable characters (escape sequences)
97
+ # this returns false for whitespace escape sequences as well, e.g. \n\t
98
+ if string =~ /[^[:print:]]/
99
+ false
100
+ else
101
+ true
102
+ end
103
+ end
104
+
105
+ # Produces a comparable path.
106
+ def self.canonical_path(path, add_prefix=true)
107
+ # Rather than find an equivalent for File.absolute_path on 1.8.7, just bail out
108
+ raise NotImplementedError, "This feature is not supported on Ruby versions < 1.9" if RUBY_VERSION.to_f < 1.9
109
+
110
+ # First remove extra separators and resolve any relative paths
111
+ abs_path = File.absolute_path(path)
112
+
113
+ if Chef::Platform.windows?
114
+ # Add the \\?\ API prefix on Windows unless add_prefix is false
115
+ # Downcase on Windows where paths are still case-insensitive
116
+ abs_path.gsub!(::File::SEPARATOR, path_separator)
117
+ if add_prefix && abs_path !~ /^\\\\?\\/
118
+ abs_path.insert(0, "\\\\?\\")
119
+ end
120
+
121
+ abs_path.downcase!
122
+ end
123
+
124
+ abs_path
125
+ end
126
+
127
+ def self.cleanpath(path)
128
+ path = Pathname.new(path).cleanpath.to_s
129
+ # ensure all forward slashes are backslashes
130
+ if Chef::Platform.windows?
131
+ path = path.gsub(File::SEPARATOR, path_separator)
132
+ end
133
+ path
134
+ end
135
+
136
+ def self.paths_eql?(path1, path2)
137
+ canonical_path(path1) == canonical_path(path2)
138
+ end
139
+
140
+ # Note: this method is deprecated. Please use escape_glob_dirs
141
+ # Paths which may contain glob-reserved characters need
142
+ # to be escaped before globbing can be done.
143
+ # http://stackoverflow.com/questions/14127343
144
+ def self.escape_glob(*parts)
145
+ path = cleanpath(join(*parts))
146
+ path.gsub(/[\\\{\}\[\]\*\?]/) { |x| "\\"+x }
147
+ end
148
+
149
+ # This function does not switch to backslashes for windows
150
+ # This is because only forwardslashes should be used with dir (even for windows)
151
+ def self.escape_glob_dir(*parts)
152
+ path = Pathname.new(join(*parts)).cleanpath.to_s
153
+ path.gsub(/[\\\{\}\[\]\*\?]/) { |x| "\\" + x }
154
+ end
155
+
156
+ def self.relative_path_from(from, to)
157
+ pathname = Pathname.new(Chef::Util::PathHelper.cleanpath(to)).relative_path_from(Pathname.new(Chef::Util::PathHelper.cleanpath(from)))
158
+ end
159
+
160
+ # Retrieves the "home directory" of the current user while trying to ascertain the existence
161
+ # of said directory. The path returned uses / for all separators (the ruby standard format).
162
+ # If the home directory doesn't exist or an error is otherwise encountered, nil is returned.
163
+ #
164
+ # If a set of path elements is provided, they are appended as-is to the home path if the
165
+ # homepath exists.
166
+ #
167
+ # If an optional block is provided, the joined path is passed to that block if the home path is
168
+ # valid and the result of the block is returned instead.
169
+ #
170
+ # Home-path discovery is performed once. If a path is discovered, that value is memoized so
171
+ # that subsequent calls to home_dir don't bounce around.
172
+ #
173
+ # See self.all_homes.
174
+ def self.home(*args)
175
+ @@home_dir ||= self.all_homes { |p| break p }
176
+ if @@home_dir
177
+ path = File.join(@@home_dir, *args)
178
+ block_given? ? (yield path) : path
179
+ end
180
+ end
181
+
182
+ # See self.home. This method performs a similar operation except that it yields all the different
183
+ # possible values of 'HOME' that one could have on this platform. Hence, on windows, if
184
+ # HOMEDRIVE\HOMEPATH and USERPROFILE are different, the provided block will be called twice.
185
+ # This method goes out and checks the existence of each location at the time of the call.
186
+ #
187
+ # The return is a list of all the returned values from each block invocation or a list of paths
188
+ # if no block is provided.
189
+ def self.all_homes(*args)
190
+ paths = []
191
+ if Chef::Platform.windows?
192
+ # By default, Ruby uses the the following environment variables to determine Dir.home:
193
+ # HOME
194
+ # HOMEDRIVE HOMEPATH
195
+ # USERPROFILE
196
+ # Ruby only checks to see if the variable is specified - not if the directory actually exists.
197
+ # On Windows, HOMEDRIVE HOMEPATH can point to a different location (such as an unavailable network mounted drive)
198
+ # while USERPROFILE points to the location where the user application settings and profile are stored. HOME
199
+ # is not defined as an environment variable (usually). If the home path actually uses UNC, then the prefix is
200
+ # HOMESHARE instead of HOMEDRIVE.
201
+ #
202
+ # We instead walk down the following and only include paths that actually exist.
203
+ # HOME
204
+ # HOMEDRIVE HOMEPATH
205
+ # HOMESHARE HOMEPATH
206
+ # USERPROFILE
207
+
208
+ paths << ENV['HOME']
209
+ paths << ENV['HOMEDRIVE'] + ENV['HOMEPATH'] if ENV['HOMEDRIVE'] && ENV['HOMEPATH']
210
+ paths << ENV['HOMESHARE'] + ENV['HOMEPATH'] if ENV['HOMESHARE'] && ENV['HOMEPATH']
211
+ paths << ENV['USERPROFILE']
212
+ end
213
+ paths << Dir.home if ENV['HOME']
214
+
215
+ # Depending on what environment variables we're using, the slashes can go in any which way.
216
+ # Just change them all to / to keep things consistent.
217
+ # Note: Maybe this is a bad idea on some unixy systems where \ might be a valid character depending on
218
+ # the particular brand of kool-aid you consume. This code assumes that \ and / are both
219
+ # path separators on any system being used.
220
+ paths = paths.map { |home_path| home_path.gsub(path_separator, ::File::SEPARATOR) if home_path }
221
+
222
+ # Filter out duplicate paths and paths that don't exist.
223
+ valid_paths = paths.select { |home_path| home_path && Dir.exists?(home_path) }
224
+ valid_paths = valid_paths.uniq
225
+
226
+ # Join all optional path elements at the end.
227
+ # If a block is provided, invoke it - otherwise just return what we've got.
228
+ joined_paths = valid_paths.map { |home_path| File.join(home_path, *args) }
229
+ if block_given?
230
+ joined_paths.each { |p| yield p }
231
+ else
232
+ joined_paths
233
+ end
234
+ end
235
+
236
+ end
237
+ end
238
+ end
239
+
240
+ # Break a require loop when require chef/util/path_helper
241
+ require 'chef/platform'
242
+ require 'chef/exceptions'