Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WinRM certificate auth #438

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,10 @@ transport as follows using the `-t` (or `--winrm-transport`) option with the
knife bootstrap windows winrm -t ssl web1.cloudapp.net -r "server::web" -x "proddomain\webuser" -P "super_secret_password" -f ~/mycert.crt
knife bootstrap windows winrm -t ssl db1.cloudapp.net -r "server::db" -x "localadmin" -P "super_secret_password" ~/mycert.crt

Client certificates can be used for authentication in lieu username/password credentials:

knife bootstrap windows winrm -t ssl web1.cloudapp.net --winrm-authentication-protocol cert --winrm-client-cert ~/myclient.crt --winrm-client-key ~/myclient.key -f ~/mycert.crt

### Troubleshooting authentication

Unencrypted traffic with Basic authentication should only be used for low level wire protocol debugging. The configuration for plain text connectivity to
Expand All @@ -346,7 +350,7 @@ authentication; an account local to the remote system must be used.

### Platform WinRM authentication support

`knife-windows` supports `Kerberos`, `Negotiate`, and `Basic` authentication
`knife-windows` supports `Kerberos`, `Negotiate`, `Certificate`, and `Basic` authentication
for WinRM communication.

The following table shows the authentication protocols that can be used with
Expand Down
12 changes: 11 additions & 1 deletion lib/chef/knife/winrm_base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ class Knife
module WinrmBase

# It includes supported WinRM authentication protocol.
WINRM_AUTH_PROTOCOL_LIST ||= %w{basic negotiate kerberos}
WINRM_AUTH_PROTOCOL_LIST ||= %w{basic negotiate kerberos cert}

# :nodoc:
# Would prefer to do this in a rational way, but can't be done b/c of
Expand Down Expand Up @@ -96,6 +96,16 @@ def self.included(includer)
:description => "The Certificate Authority (CA) trust file used for SSL transport",
:proc => Proc.new { |trust| Chef::Config[:knife][:ca_trust_file] = trust }

option :winrm_client_cert,
:long => "--winrm-client-cert CERT_FILE",
:description => "Certificate to use when --winrm-authentication-protocol is set to 'cert'",
:proc => Proc.new { |crt| Chef::Config[:knife][:winrm_client_cert] = crt }

option :winrm_client_key,
:long => "--winrm-client-key KEY_FILE",
:description => "Certificate to use when --winrm-authentication-protocol is set to 'cert'",
:proc => Proc.new { |key| Chef::Config[:knife][:winrm_client_key] = key }

option :winrm_ssl_verify_mode,
:long => "--winrm-ssl-verify-mode SSL_VERIFY_MODE",
:description => "The WinRM peer verification mode. Valid choices are [verify_peer, verify_none]",
Expand Down
25 changes: 19 additions & 6 deletions lib/chef/knife/winrm_knife_base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ def self.included(includer)
def validate_winrm_options!
winrm_auth_protocol = locate_config_value(:winrm_authentication_protocol)

if ! Chef::Knife::WinrmBase::WINRM_AUTH_PROTOCOL_LIST.include?(winrm_auth_protocol)
if ! Chef::Knife::WinrmBase::WINRM_AUTH_PROTOCOL_LIST.include?(winrm_auth_protocol.to_s)
ui.error "Invalid value '#{winrm_auth_protocol}' for --winrm-authentication-protocol option."
ui.info "Valid values are #{Chef::Knife::WinrmBase::WINRM_AUTH_PROTOCOL_LIST.join(",")}."
exit 1
Expand Down Expand Up @@ -112,6 +112,7 @@ def extract_nested_value(data, nested_value_spec)

def run_command(command = '')
relay_winrm_command(command)
@exit_code = 0
check_for_errors!
@exit_code
end
Expand Down Expand Up @@ -201,8 +202,6 @@ def create_winrm_session(options={})

def resolve_session_options
@session_opts = {
user: resolve_winrm_user,
password: locate_config_value(:winrm_password),
port: locate_config_value(:winrm_port),
operation_timeout: resolve_winrm_session_timeout,
basic_auth_only: resolve_winrm_basic_auth,
Expand All @@ -214,8 +213,18 @@ def resolve_session_options
codepage: locate_config_value(:winrm_codepage)
}

if @session_opts[:user] and (not @session_opts[:password])
@session_opts[:password] = Chef::Config[:knife][:winrm_password] = config[:winrm_password] = get_password
if cert_auth?
@session_opts[:winrm_client_cert] = locate_config_value(:winrm_client_cert) if locate_config_value(:winrm_client_cert)
@session_opts[:winrm_client_key] = locate_config_value(:winrm_client_key) if locate_config_value(:winrm_client_key)
@session_opts[:transport] = :ssl
@session_opts.delete(:user)
@session_opts.delete(:password)
else
@session_opts[:user] = resolve_winrm_user
@session_opts[:password] = locate_config_value(:winrm_password)
if @session_opts[:user] and (not @session_opts[:password])
@session_opts[:password] = Chef::Config[:knife][:winrm_password] = config[:winrm_password] = get_password
end
end

if @session_opts[:transport] == :kerberos
Expand Down Expand Up @@ -284,8 +293,12 @@ def get_password
@password ||= ui.ask("Enter your password: ") { |q| q.echo = false }
end

def cert_auth?
locate_config_value(:winrm_authentication_protocol).to_s == "cert"
end

def negotiate_auth?
locate_config_value(:winrm_authentication_protocol) == "negotiate"
locate_config_value(:winrm_authentication_protocol).to_s == "negotiate"
end

def warn_no_ssl_peer_verification
Expand Down
22 changes: 17 additions & 5 deletions lib/chef/knife/winrm_session.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,20 +27,23 @@ class WinrmSession

def initialize(options)
configure_proxy

@host = options[:host]
@port = options[:port]
@user = options[:user]
@shell_args = [ options[:shell] ]
@shell_args << { codepage: options[:codepage] } if options[:shell] == :cmd
url = "#{options[:host]}:#{options[:port]}/wsman"
scheme = options[:transport] == :ssl ? 'https' : 'http'
if options[:transport] == :ssl
scheme = 'https'
@port ||= 5986
else
scheme = 'http'
@port ||= 5985
end
url = "#{@host}:#{@port}/wsman"
@endpoint = "#{scheme}://#{url}"

opts = Hash.new
opts = {
user: @user,
password: options[:password],
basic_auth_only: options[:basic_auth_only],
disable_sspi: options[:disable_sspi],
no_ssl_peer_verification: options[:no_ssl_peer_verification],
Expand All @@ -50,6 +53,15 @@ def initialize(options)
}
options[:transport] == :kerberos ? opts.merge!({:service => options[:service], :realm => options[:realm]}) : opts.merge!({:ca_trust_path => options[:ca_trust_path]})
opts[:operation_timeout] = options[:operation_timeout] if options[:operation_timeout]
if options[:winrm_client_cert] and options[:winrm_client_key]
opts[:client_cert] = options[:winrm_client_cert]
opts[:client_key] = options[:winrm_client_key]
opts.delete(:user)
opts.delete(:password)
else
opts[:user] = @user
opts[:password] = options[:password]
end
Chef::Log.debug("WinRM::WinRMWebService options: #{opts}")
Chef::Log.debug("Endpoint: #{endpoint}")
Chef::Log.debug("Transport: #{options[:transport]}")
Expand Down