Skip to content

Commit

Permalink
Updating chef-winrm completely
Browse files Browse the repository at this point in the history
Signed-off-by: John McCrae <[email protected]>
  • Loading branch information
johnmccrae committed Feb 3, 2025
1 parent cae5029 commit 923cb92
Show file tree
Hide file tree
Showing 54 changed files with 4,603 additions and 0 deletions.
49 changes: 49 additions & 0 deletions chef-winrm.gemspec
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
require 'date'
require File.expand_path('lib/chef-winrm/version', __dir__)

Gem::Specification.new do |s|
s.platform = Gem::Platform::RUBY
s.name = 'chef-winrm'
s.version = WinRM::VERSION
s.date = Date.today.to_s

s.author = ['Dan Wanek', 'Paul Morton', 'Matt Wrock', 'Shawn Neal']
s.email = [
'[email protected]',
'[email protected]',
'[email protected]',
'[email protected]'
]
s.homepage = 'https://github.com/WinRb/WinRM'

s.summary = 'Ruby library for Windows Remote Management'
s.description = <<-EOF
Ruby library for Windows Remote Management
EOF
s.license = 'Apache-2.0'

s.files = Dir.glob('{bin,lib}/**/*') + %w[LICENSE README.md]
s.require_path = 'lib'
s.rdoc_options = %w[-x test/ -x examples/]
s.extra_rdoc_files = %w[README.md LICENSE]

s.bindir = 'bin'
s.executables = ['rwinrm']
s.required_ruby_version = '>= 3.0'
s.add_runtime_dependency 'builder', '>= 2.1.2'
s.add_runtime_dependency 'chef-gyoku', '>= 1.4.1'
s.add_runtime_dependency 'erubi', '~> 1.8'
s.add_runtime_dependency 'gssapi', '~> 1.2'
s.add_runtime_dependency 'httpclient', '~> 2.2', '>= 2.2.0.2'
s.add_runtime_dependency 'logging', ['>= 1.6.1', '< 3.0']
s.add_runtime_dependency 'nori', '= 2.7.0' # nori 2.7.1 has a bug where it throws a NoMethodError for snakecase.
s.add_runtime_dependency 'rexml', '~> 3.3' # needs to load at least 3.3.6 to get past a CVE
s.add_development_dependency 'pry'
s.add_development_dependency 'rake', '>= 10.3', '< 13'
s.add_development_dependency 'rb-readline'
s.add_development_dependency 'rspec', '~> 3.2'
s.add_development_dependency 'rubocop', '~> 1.26.0'
s.add_runtime_dependency 'rubyntlm', '~> 0.6.0', '>= 0.6.3'

s.metadata['rubygems_mfa_required'] = 'true'
end
37 changes: 37 additions & 0 deletions lib/chef-winrm.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Copyright 2010 Dan Wanek <[email protected]>
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

require 'logging'
require_relative 'chef-winrm/version'
require_relative 'chef-winrm/connection'
require_relative 'chef-winrm/exceptions'

# Main WinRM module entry point
module WinRM
# Enable logging if it is requested. We do this before
# anything else so that we can setup the output before
# any logging occurs.
if ENV['WINRM_LOG'] && ENV['WINRM_LOG'] != ''
begin
Logging.logger.root.level = ENV['WINRM_LOG']
Logging.logger.root.appenders = Logging.appenders.stderr
rescue ArgumentError
# This means that the logging level wasn't valid
warn "Invalid WINRM_LOG level is set: #{ENV['WINRM_LOG']}"
warn ''
warn 'Please use one of the standard log levels: ' \
'debug, info, warn, or error'
end
end
end
84 changes: 84 additions & 0 deletions lib/chef-winrm/connection.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# Copyright 2016 Shawn Neal <[email protected]>
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

require_relative 'connection_opts'
require_relative 'http/transport_factory'
require_relative 'shells/shell_factory'
require_relative 'wsmv/wql_query'
require_relative 'wsmv/wql_pull'

module WinRM
# WinRM connection used to establish a session with the remote WinRM service.
class Connection
# Creates a new WinRM connection
# See the ConnectionOpts class for connection options.
def initialize(connection_opts)
configure_connection_opts(connection_opts)
configure_logger
end

attr_accessor :logger

# Creates a new shell on the remote Windows server associated with
# this connection.
# @param shell_type [Symbol] The shell type :cmd or :powershell
# @param shell_opts [Hash] Options targeted for the created shell
# @return [Shell] PowerShell or Cmd shell instance.
def shell(shell_type, shell_opts = {})
shell = shell_factory.create_shell(shell_type, shell_opts)
if block_given?
begin
yield shell
ensure
shell.close
end
else
shell
end
end

# Executes a WQL query against the WinRM connection
# @param wql [String] The wql query
# @param namespace [String] namespace for query - default is root/cimv2/*
# @return [Hash] Hash representation of wql query response (Hash is empty if a block is given)
# @yeild [type, item] Yields the time name and item for every item
def run_wql(wql, namespace = 'root/cimv2/*', &block)
query = WinRM::WSMV::WqlQuery.new(transport, @connection_opts, wql, namespace)
query.process_response(transport.send_request(query.build), &block)
end

private

def configure_connection_opts(connection_opts)
@connection_opts = ConnectionOpts.create_with_defaults(connection_opts)
end

def configure_logger
@logger = Logging.logger[self]
logger.level = :warn
logger.add_appenders(Logging.appenders.stdout)
end

def shell_factory
@shell_factory ||= WinRM::Shells::ShellFactory.new(@connection_opts, transport, logger)
end

def transport
@transport ||= begin
transport_factory = WinRM::HTTP::TransportFactory.new
transport_factory.create_transport(@connection_opts)
end
end
end
end
92 changes: 92 additions & 0 deletions lib/chef-winrm/connection_opts.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# Copyright 2016 Shawn Neal <[email protected]>
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

require 'securerandom'

module WinRM
# WinRM connection options, provides defaults and validation.
class ConnectionOpts < Hash
DEFAULT_OPERATION_TIMEOUT = 60
DEFAULT_RECEIVE_TIMEOUT = DEFAULT_OPERATION_TIMEOUT + 10
DEFAULT_MAX_ENV_SIZE = 153600
DEFAULT_LOCALE = 'en-US'.freeze
DEFAULT_RETRY_DELAY = 10
DEFAULT_RETRY_LIMIT = 3
DEFAULT_USER_AGENT = 'Ruby WinRM Client'.freeze

class << self
def create_with_defaults(overrides)
config = default.merge(overrides)
config = ensure_receive_timeout_is_greater_than_operation_timeout(config)
config.validate
config
end

private

def ensure_receive_timeout_is_greater_than_operation_timeout(config)
if config[:receive_timeout] < config[:operation_timeout]
config[:receive_timeout] = config[:operation_timeout] + 10
end
config
end

def default
config = ConnectionOpts.new
config[:session_id] = SecureRandom.uuid.to_s.upcase
config[:transport] = :negotiate
config[:locale] = DEFAULT_LOCALE
config[:max_envelope_size] = DEFAULT_MAX_ENV_SIZE
config[:operation_timeout] = DEFAULT_OPERATION_TIMEOUT
config[:receive_timeout] = DEFAULT_RECEIVE_TIMEOUT
config[:retry_delay] = DEFAULT_RETRY_DELAY
config[:retry_limit] = DEFAULT_RETRY_LIMIT
config[:user_agent] = DEFAULT_USER_AGENT
config
end
end

def validate
validate_required_fields
validate_data_types
end

private

def validate_required_fields
raise 'endpoint is a required option' unless self[:endpoint]

if self[:client_cert]
raise 'path to client key is required' unless self[:client_key]
else
raise 'user is a required option' unless self[:user]
raise 'password is a required option' unless self[:password]
end
end

def validate_data_types
validate_integer(:retry_limit)
validate_integer(:retry_delay)
validate_integer(:max_envelope_size)
validate_integer(:operation_timeout)
validate_integer(:receive_timeout, self[:operation_timeout])
end

def validate_integer(key, min = 0)
value = self[key]
raise "#{key} must be a Integer" unless value && value.is_a?(Integer)
raise "#{key} must be greater than #{min}" unless value > min
end
end
end
88 changes: 88 additions & 0 deletions lib/chef-winrm/exceptions.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# Copyright 2010 Dan Wanek <[email protected]>
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

module WinRM
# WinRM base class for errors
class WinRMError < StandardError; end

# Authorization Error
class WinRMAuthorizationError < WinRMError; end

# Shell creation error
class InvalidShellError < WinRMError; end

# Exitcode error
class InvalidExitCode < WinRMError; end

# Shell creation error
class InvalidTransportError < WinRMError
attr_reader :invalid_transport

def initialize(invalid_transport, valid_transports)
@invalid_transport = invalid_transport
super(
"Invalid transport '#{invalid_transport}' specified" \
", expected: #{valid_transports.join(', ')}."
)
end
end

# A Fault returned in the SOAP response. The XML node is a WSManFault
class WinRMWSManFault < WinRMError
attr_reader :fault_code
attr_reader :fault_description

def initialize(fault_description, fault_code)
@fault_description = fault_description
@fault_code = fault_code
super("[WSMAN ERROR CODE: #{fault_code}]: #{fault_description}")
end
end

# A Fault returned in the SOAP response. The XML node contains Code, SubCode and Reason
class WinRMSoapFault < WinRMError
attr_reader :code
attr_reader :subcode
attr_reader :reason

def initialize(code, subcode, reason)
@code = code
@subcode = subcode
@reason = reason
super("[SOAP ERROR CODE: #{code} (#{subcode})]: #{reason}")
end
end

# A Fault returned in the SOAP response. The XML node is a MSFT_WmiError
class WinRMWMIError < WinRMError
attr_reader :error_code
attr_reader :error

def initialize(error, error_code)
@error = error
@error_code = error_code
super("[WMI ERROR CODE: #{error_code}]: #{error}")
end
end

# non-200 response without a SOAP fault
class WinRMHTTPTransportError < WinRMError
attr_reader :status_code

def initialize(msg, status_code = nil)
@status_code = status_code
super(msg + " (#{status_code}).")
end
end
end
Loading

0 comments on commit 923cb92

Please sign in to comment.