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

Let's discuss getting winrm ssl to talk to self-signed certs #2

Open
wants to merge 3 commits into
base: damaster
Choose a base branch
from
Open
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
139 changes: 125 additions & 14 deletions lib/chef/provisioning/aws_driver/driver.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
#require 'byebug'
require 'pry'
require 'pry-byebug'
require 'chef/mixin/shell_out'
require 'chef/mixin/deep_merge'
require 'chef/provisioning/driver'
Expand Down Expand Up @@ -583,7 +586,7 @@ def ready_machine(action_handler, machine_spec, machine_options)
end
wait_until_ready_machine(action_handler, machine_spec, instance)
end

1 # byebug
wait_for_transport(action_handler, machine_spec, machine_options)
machine_for(machine_spec, machine_options, instance)
end
Expand Down Expand Up @@ -713,7 +716,7 @@ def machine_for(machine_spec, machine_options, instance = nil)
raise "Instance for node #{machine_spec.name} has not been created!"
end

if machine_spec.reference['is_windows']
if machine_spec.reference['is_windows']<
Chef::Provisioning::Machine::WindowsMachine.new(machine_spec, transport_for(machine_spec, machine_options, instance), convergence_strategy_for(machine_spec, machine_options))
else
Chef::Provisioning::Machine::UnixMachine.new(machine_spec, transport_for(machine_spec, machine_options, instance), convergence_strategy_for(machine_spec, machine_options))
Expand Down Expand Up @@ -893,25 +896,133 @@ def default_ami_for_region(region, criteria = {})

def create_winrm_transport(machine_spec, machine_options, instance)
remote_host = determine_remote_host(machine_spec, instance)
username = machine_spec.reference[:winrm_username] || 'Administrator'
# default to http for now, should upgrade to https when knife support self-signed
transport_type = machine_options[:winrm_transport] || 'http'
type = case transport_type
when 'http'
:plaintext
when 'https'
:ssl
end
if machine_spec.reference[:winrm_port]
port = machine_spec.reference[:winrm_port]
else #default port
port = case transport_type
when 'http'
'5985'
when 'https'
'5986'
end
end
endpoint = "#{transport_type}://#{remote_host}:#{port}/wsman"

port = machine_spec.reference['winrm_port'] || 5985
endpoint = "http://#{remote_host}:#{port}/wsman"
type = :plaintext
pem_bytes = get_private_key(instance.key_name)
encrypted_admin_password = wait_for_admin_password(machine_spec)
if machine_options[:password]
password = machine_options[:password]
else # pull from ec2 and store in reference
if machine_spec.reference[:winrm_encrypted_password]
decoded = Base64.decode64(machine_spec.reference[:winrm_encrypted_password])
else
encrypted_admin_password = wait_for_admin_password(machine_spec)
machine_spec.reference[:winrm_encrypted_password]=encrypted_admin_password
# ^^^ should be saved
decoded = Base64.decode64(encrypted_admin_password)
end
# decrypt so we can utilize
private_key = OpenSSL::PKey::RSA.new(get_private_key(instance.key_name))
password = private_key.private_decrypt decoded
end

decoded = Base64.decode64(encrypted_admin_password)
private_key = OpenSSL::PKey::RSA.new(pem_bytes)
decrypted_password = private_key.private_decrypt decoded
disable_sspi = machine_options[:winrm_disable_sspi] || false # default to Negotiate
basic_auth_only = machine_options[:winrm_basic_auth_only] || false # disallow Basic auth by default
no_ssl_peer_verification = machine_options[:winrm_no_ssl_peer_verification] || false #disallow MITM potential by default

winrm_options = {
:user => machine_spec.reference['winrm_username'] || 'Administrator',
:pass => decrypted_password,
:disable_sspi => true,
:basic_auth_only => true
user: username,
pass: password,
disable_sspi: disable_sspi,
basic_auth_only: basic_auth_only,
no_ssl_peer_verification: no_ssl_peer_verification,
}

if no_ssl_peer_verification or type != :ssl
# we won't verify certs
elsif machine_spec.reference[:winrm_ssl_cert]
# we have stored the cert
else
# we need to retrieve the cert and verify it by connecting just to
# retrieve the ssl certificate and compare it to what we see in the
# console logs
console_lines = Base64.decode64(instance.console_output.data.output).lines
noverify_peer_context = OpenSSL::SSL::SSLContext.new
noverify_peer_context.verify_mode = OpenSSL::SSL::VERIFY_NONE
tcp_connection = TCPSocket.new(instance.private_ip_address, '5986')
shady_ssl_connection = OpenSSL::SSL::SSLSocket.new(tcp_connection, noverify_peer_context)
shady_ssl_connection.connect
shady_ssl_connection.peer_cert_chain
winrm_cert = shady_ssl_connection.peer_cert_chain.first

rdp_thumbprint = console_lines.grep(
/RDPCERTIFICATE-THUMBPRINT/)[-1].split(': ').last.chomp
rdp_subject = console_lines.grep(
/RDPCERTIFICATE-SUBJECTNAME/)[-1].split(': ').last.chomp
winrm_subject = winrm_cert.subject.to_s.split('=').last.upcase
winrm_thumbprint=OpenSSL::Digest::SHA1.new(winrm_cert.to_der).to_s.upcase

if rdp_subject != winrm_subject or rdp_thumbprint != winrm_thumbprint
Chef::Log.fatal "Winrm ssl port certificate differs from rdp console logs"
end
machine_spec.reference[:winrm_ssl_subject]=winrm_subject
machine_spec.reference[:winrm_ssl_thumbprint]=winrm_thumbprint
machine_spec.reference[:winrm_ssl_cert]=winrm_cert.to_pem
end

# If we have a cert, use it for verification
if machine_spec.reference[:winrm_ssl_cert]
FileUtils.mkdir_p(Chef::Config.trusted_certs_dir)
filename = File.join(Chef::Config.trusted_certs_dir, "#{machine_spec.name}.crt")
if File.exists?(filename)
Chef::Log.warn("Existing cert for #{winrm_subject} in #{filename}")
else
Chef::Log.warn("Adding certificate for #{winrm_subject} in #{filename}")
File.open(filename, File::CREAT|File::TRUNC|File::RDWR, 0644) do |f|
f.print(machine_spec.reference[:winrm_ssl_cert])
end
end
winrm_options[:ca_trust_path] = filename
end
Chef::Provisioning::Transport::WinRM.new("#{endpoint}", type, winrm_options, {})
binding.pry
# in order for ssl to work, we need to tell ssl that it's ok that ssl_subject doesn't
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@adamedx this is where I could use some winrm magic... @mwrock ?

# match the ip
#[19] pry(#<Chef::Provisioning::AWSDriver::Driver>)> endpoint.split('/')[2].split(':').first
# => "10.113.70.104"
# [20] pry(#<Chef::Provisioning::AWSDriver::Driver>)> machine_spec.reference[:winrm_ssl_subject]
# => "IP-0A714668"
#
# [1] pry(#<Chef::Provisioning::AWSDriver::Driver>)> Chef::Provisioning::Transport::WinRM.new("#{endpoint}", type, winrm_options, {}).execute('hostname')
# OpenSSL::SSL::SSLError: SSL_connect returned=1 errno=0 state=error: certificate verify failed
# from /home/hh/.rvm/gems/ruby-2.2.0/gems/httpclient-2.6.0.1/lib/httpclient/session.rb:307:in `connect'
# [2] pry(#<Chef::Provisioning::AWSDriver::Driver>)> wtf!
# Exception: OpenSSL::SSL::SSLError: SSL_connect returned=1 errno=0 state=error: certificate verify failed
# --
# 0: /home/hh/.rvm/gems/ruby-2.2.0/gems/httpclient-2.6.0.1/lib/httpclient/session.rb:307:in `connect'
# 1: /home/hh/.rvm/gems/ruby-2.2.0/gems/httpclient-2.6.0.1/lib/httpclient/session.rb:307:in `ssl_connect'
# 2: /home/hh/.rvm/gems/ruby-2.2.0/gems/httpclient-2.6.0.1/lib/httpclient/session.rb:755:in `block in connect'
# 3: /home/hh/.rvm/rubies/ruby-2.2.0/lib/ruby/2.2.0/timeout.rb:89:in `block in timeout'
# 4: /home/hh/.rvm/rubies/ruby-2.2.0/lib/ruby/2.2.0/timeout.rb:99:in `call'
# 5: /home/hh/.rvm/rubies/ruby-2.2.0/lib/ruby/2.2.0/timeout.rb:99:in `timeout'
# 6: /home/hh/.rvm/rubies/ruby-2.2.0/lib/ruby/2.2.0/timeout.rb:125:in `timeout'
# 7: /home/hh/.rvm/gems/ruby-2.2.0/gems/httpclient-2.6.0.1/lib/httpclient/session.rb:746:in `connect'
# 8: /home/hh/.rvm/gems/ruby-2.2.0/gems/httpclient-2.6.0.1/lib/httpclient/session.rb:612:in `query'
# 9: /home/hh/.rvm/gems/ruby-2.2.0/gems/httpclient-2.6.0.1/lib/httpclient/session.rb:164:in `query'
# [3] pry(#<Chef::Provisioning::AWSDriver::Driver>)> winrm_options
# => {:user=>"Administrator",
# :pass=>"(xntd8f=-HNnuJ3",
# :disable_sspi=>false,
# :basic_auth_only=>false,
# :no_ssl_peer_verification=>false,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you want :no_ssl_peer_verification to be true set in machine_options[:winrm_no_ssl_peer_verification]
see code sample in https://github.com/WinRb/WinRM#ssl

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mwrock
The purpose here is to make it easy to copy the already existing self signed cert generated by windows on first boot for rdp, and reuse it for winrm. The Subject CN of that cert does not contain a valid fqdn, it's usually WIN-RANDOM or IP-HEXIPV4.

I'm trying to duplicate the utility of ssh where we verify the fingerprint of the connection to a particular ip (and verify it). See http://www.rackspace.com/knowledge_center/article/rackspace-cloud-essentials-checking-a-server%E2%80%99s-ssh-host-fingerprint-with-the-web-console

I'm looking for a way to explicitly pass self-signed ssl certificates per https connection. The only reason it's failing right now is the Certificate CN doesn't match the ip we attempt to connect to.

If we aren't able to actually verify self-signed ssl certs and bind them to a particular winrm target it kinda makes self-signed certs useless unless we regenerate them to match their IP or a dns name that reaches them... which is not easy to do in ec2 user_data.

I'm currently using the following:

winrm quickconfig -q                                                                                                                           
winrm set winrm/config/winrs '@{MaxMemoryPerShellMB="300"}'                                                                                    
winrm set winrm/config '@{MaxTimeoutms="1800000"}'                                                                                             

netsh advfirewall firewall add rule name="WinRM 5986" protocol=TCP dir=in localport=5986 action=allow                                          

$SourceStoreScope = 'LocalMachine'                                                                                                             
$SourceStorename = 'Remote Desktop'                                                                                                            

$SourceStore = New-Object  -TypeName System.Security.Cryptography.X509Certificates.X509Store  -ArgumentList $SourceStorename, $SourceStoreScop\
e                                                                                                                                              
$SourceStore.Open([System.Security.Cryptography.X509Certificates.OpenFlags]::ReadOnly)                                                         

$cert = $SourceStore.Certificates | Where-Object  -FilterScript {                                                                              
    $_.subject -like '*'                                                                                                                       
}                                                                                                                                              

$DestStoreScope = 'LocalMachine'                                                                                                               
$DestStoreName = 'My'                                                                                                                          

$DestStore = New-Object  -TypeName System.Security.Cryptography.X509Certificates.X509Store  -ArgumentList $DestStoreName, $DestStoreScope      
$DestStore.Open([System.Security.Cryptography.X509Certificates.OpenFlags]::ReadWrite)                                                          
$DestStore.Add($cert)                                                                                                                          

$SourceStore.Close()                                                                                                                           
$DestStore.Close()                                                                                                                             

winrm create winrm/config/listener?Address=*+Transport=HTTPS  `@`{Hostname=`"($certId)`"`;CertificateThumbprint=`"($cert.Thumbprint)`"`}       

net stop winrm                                                                                                                                 
sc config winrm start=auto                                                                                                                     
net start winrm 

# :ca_trust_path=>"/home/hh/stratalux/lifelock/misc/provisioning/.chef/trusted_certs/base-2012-hardened-6.crt"}
end

def wait_for_admin_password(machine_spec)
Expand Down