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

Merge knife-windows into chef #9354

Closed
wants to merge 58 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
11699a3
Merge knife-windows into chef
tas50 Feb 7, 2020
6736185
Fold KnifeWindowsBase into WinrmCommandSharedFunctions
tas50 Feb 26, 2020
31fae10
Sync over the updates made for 16.x
tas50 Jun 26, 2020
3a8c7b3
tty-prompt ui.ask changes
Jul 23, 2020
258275f
Update lib/chef/knife/windows_cert_generate.rb
tas50 Jul 24, 2020
6bba483
Remove unused requires
tas50 Jul 24, 2020
5dbe622
Password -> Passphrase
tas50 Jul 24, 2020
133a902
Avoid nesting and use newer openssl format
tas50 Jul 24, 2020
9a3e543
Convert the plugins to use chef-utils
tas50 Jul 24, 2020
41671b8
Update descriptions
tas50 Jul 24, 2020
8b1d1c3
Remove the deprecated interactive mode
tas50 Jul 24, 2020
30e7e36
Fix the early exits on non-windows hosts
tas50 Jul 24, 2020
3aec7f2
Use our built in prompt tools to handle password input
tas50 Jul 24, 2020
f41f626
Improve how we prompt for passwords and don't mutate config
tas50 Jul 24, 2020
ae8be50
Remove stdout/stderr syncing
tas50 Jul 24, 2020
eeac46b
Remove another stdout/stderr sync
tas50 Jul 24, 2020
18c516f
Gate our requires
tas50 Jul 24, 2020
53e4607
Avoid setting a variable we don't need to
tas50 Jul 24, 2020
a38cb6e
Avoiding having to convert these to ints later
tas50 Jul 24, 2020
4ff23b4
Don't mutate the config by setting a password
tas50 Jul 24, 2020
0856d5e
Simplify the check for existing certs on disk
tas50 Jul 24, 2020
ec51b88
Spelling fix
tas50 Jul 24, 2020
628b324
Add a return for the passphrase
tas50 Jul 24, 2020
c3b2dc2
Use ui.fatal! to format errors with color and exit 1
tas50 Jul 24, 2020
cd5dc2f
Remove unused methods
tas50 Jul 24, 2020
3105b67
Use in: in the knife options
tas50 Jul 24, 2020
3c857db
Use in: in more options
tas50 Jul 24, 2020
bef8b93
Fix descriptions
tas50 Jul 24, 2020
1ea84ef
Update lib/chef/knife/winrm.rb
tas50 Jul 24, 2020
56bc896
Update lib/chef/knife/winrm_base.rb
tas50 Jul 24, 2020
54bf0e9
Update lib/chef/knife/winrm_knife_base.rb
tas50 Jul 24, 2020
9525fe8
Update lib/chef/knife/winrm_knife_base.rb
tas50 Jul 24, 2020
da24159
Update lib/chef/knife/winrm_knife_base.rb
tas50 Jul 24, 2020
95275a2
Update lib/chef/knife/winrm_knife_base.rb
tas50 Jul 24, 2020
0679244
Update lib/chef/knife/winrm_knife_base.rb
tas50 Jul 24, 2020
3820fee
Update lib/chef/knife/winrm_knife_base.rb
tas50 Jul 24, 2020
6c63310
Remove odd require on other plugins within the winrm plugin
tas50 Jul 24, 2020
66e3a6b
Rename confusing config check method
tas50 Jul 24, 2020
e773aa5
Rename more methods
tas50 Jul 24, 2020
d24d1d9
Nuke a method
tas50 Jul 24, 2020
e1fb23e
Update lib/chef/knife/winrm_session.rb
tas50 Jul 24, 2020
2e1ba1c
Simplify code / avoid mutating config
tas50 Jul 24, 2020
4a29e9a
Simplify fetching correct winrm user
tas50 Jul 24, 2020
6f17d71
Rename methods to remove resolve_
tas50 Jul 24, 2020
d7ca400
Update spec/unit/knife/windows_cert_install_spec.rb
tas50 Jul 25, 2020
2eb7e83
Update spec/unit/knife/windows_cert_install_spec.rb
tas50 Jul 25, 2020
8ad14c3
Update lib/chef/knife/windows_cert_generate.rb
tas50 Jul 25, 2020
6bb5d6f
Update lib/chef/knife/wsman_test.rb
tas50 Jul 25, 2020
1a8d2bc
Update lib/chef/knife/wsman_test.rb
tas50 Jul 25, 2020
7550705
Update lib/chef/knife/winrm_shared_options.rb
tas50 Jul 25, 2020
20674d7
Update spec/unit/knife/windows_cert_generate_spec.rb
tas50 Jul 25, 2020
f3ae9d2
Update spec/unit/knife/winrm_session_spec.rb
tas50 Jul 25, 2020
f33f841
Update spec/unit/knife/winrm_spec.rb
tas50 Jul 25, 2020
8c6b93e
Update spec/unit/knife/wsman_test_spec.rb
tas50 Jul 25, 2020
e75d9d9
Remove duplicate support file
tas50 Jul 25, 2020
34d7a7c
Fix failing specs
tas50 Jul 25, 2020
9e6a6f1
Fix cspell and chefstyle errors
tas50 Jul 31, 2020
a4b1675
Chefstyle fix
tas50 Sep 26, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
8 changes: 8 additions & 0 deletions Gemfile.lock
Expand Up @@ -63,6 +63,8 @@ PATH
tty-screen (~> 0.6)
tty-table (~> 0.11)
uuidtools (~> 2.1.5)
winrm (~> 2.1)
winrm-elevated (~> 1.0)
chef (16.5.76-universal-mingw32)
addressable
bcrypt_pbkdf (= 1.1.0.rc1)
Expand Down Expand Up @@ -109,6 +111,8 @@ PATH
win32-process (~> 0.8.2)
win32-service (>= 2.1.5, < 3.0)
win32-taskscheduler (~> 2.0)
winrm (~> 2.1)
winrm-elevated (~> 1.0)
wmi-lite (~> 1.0)

PATH
Expand Down Expand Up @@ -410,6 +414,10 @@ GEM
logging (>= 1.6.1, < 3.0)
nori (~> 2.0)
rubyntlm (~> 0.6.0, >= 0.6.1)
winrm-elevated (1.2.1)
erubi (~> 1.8)
winrm (~> 2.0)
winrm-fs (~> 1.0)
winrm-fs (1.3.3)
erubi (~> 1.8)
logging (>= 1.6.1, < 3.0)
Expand Down
2 changes: 2 additions & 0 deletions chef.gemspec
Expand Up @@ -19,6 +19,8 @@ Gem::Specification.new do |s|
s.add_dependency "chef-utils", "= #{Chef::VERSION}"
s.add_dependency "train-core", "~> 3.2", ">= 3.2.28" # 3.2.28 fixes sudo prompts. See https://github.com/chef/chef/pull/9635
s.add_dependency "train-winrm", ">= 0.2.5"
s.add_dependency "winrm", "~> 2.1"
s.add_dependency "winrm-elevated", "~> 1.0"

s.add_dependency "license-acceptance", ">= 1.0.5", "< 3"
s.add_dependency "mixlib-cli", ">= 2.1.1", "< 3.0"
Expand Down
9 changes: 9 additions & 0 deletions cspell.json
Expand Up @@ -179,6 +179,7 @@
"centos",
"certfile",
"Certstore",
"certutil",
"CFLAGS",
"CFPREFERENCES",
"cfprefsd",
Expand Down Expand Up @@ -663,6 +664,8 @@
"HRSRC",
"Hsiao",
"htop",
"httpcli",
"httpclient",
"httpd",
"Huon",
"hwaddr",
Expand Down Expand Up @@ -765,12 +768,14 @@
"Kauppila",
"kaustubh",
"Kaustubh",
"kconv",
"Keane",
"keepalive",
"keepalives",
"keeppackages",
"kenmacleod",
"kerberos",
"KEYEXCHANGE",
"keyfile",
"keygen",
"keyid",
Expand Down Expand Up @@ -1402,6 +1407,7 @@
"rhscl",
"rindex",
"rjust",
"RMHTTP",
"rmatch",
"rmdir",
"rmgroup",
Expand Down Expand Up @@ -1855,6 +1861,7 @@
"winnt",
"WINNT",
"winprog",
"winrmcert",
"WINVER",
"WIXUI",
"WKSTA",
Expand All @@ -1864,6 +1871,8 @@
"WPARAM",
"wrlinux",
"WSCHILD",
"WSMANIDENTIFY",
"wsmid",
"wstring",
"wtime",
"xabcz",
Expand Down
143 changes: 143 additions & 0 deletions lib/chef/knife/windows_cert_generate.rb
@@ -0,0 +1,143 @@
# Author:: Mukta Aphale (<mukta.aphale@clogeny.com>)
# Copyright:: Copyright (c) 2014-2016 Chef Software, Inc.
# License:: Apache License, Version 2.0
#
# 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 "../knife"
phiggins marked this conversation as resolved.
Show resolved Hide resolved

class Chef
class Knife
class WindowsCertGenerate < Knife

attr_accessor :thumbprint, :hostname

banner "knife windows cert generate FILE_PATH (options)"

deps do
require "openssl" unless defined?(OpenSSL)
require "socket" unless defined?(Socket)
end

option :hostname,
short: "-H HOSTNAME",
long: "--hostname HOSTNAME",
description: "Use to specify the hostname for the listener.
For example, --hostname something.example.com or *.example.com.",
required: true

option :output_file,
short: "-o PATH",
long: "--output-file PATH",
description: "Specifies the file path at which to generate the 3 certificate files of type .pfx, .b64, and .pem. The default is './winrmcert'.",
default: "winrmcert"

option :key_length,
short: "-k LENGTH",
long: "--key-length LENGTH",
description: "Specifies the key length of the certificate. The default is 2048",
default: 2048

option :cert_validity,
short: "-cv MONTHS",
long: "--cert-validity MONTHS",
description: "Specifies how long the certificate will be valid. The default is 24 months",
default: 24

option :cert_passphrase,
short: "-cp PASSWORD",
long: "--cert-passphrase PASSWORD",
description: "Specifies certificate's passphrase."

def generate_key_pair
OpenSSL::PKey::RSA.new(config[:key_length].to_i)
end

def prompt_for_passphrase
loop do
passphrase = ui.ask("Enter certificate passphrase (empty for no passphrase):", echo: false)
confirm_passphrase = ui.ask("Confirm the passphrase:", echo: false)
return passphrase if passphrase == confirm_passphrase

print "Passphrases do not match. Try again.\n" unless passphrase.empty?
end
end
tas50 marked this conversation as resolved.
Show resolved Hide resolved

def generate_certificate(rsa_key)
@hostname = config[:hostname] if config[:hostname]

# Create a self-signed X509 certificate from the rsa_key (unencrypted)
cert = OpenSSL::X509::Certificate.new
cert.version = 2
cert.serial = Random.rand(65534) + 1 # 2 digit byte range random number for better security aspect

cert.subject = OpenSSL::X509::Name.parse "/CN=#{@hostname}"
cert.issuer = cert.subject
cert.public_key = rsa_key.public_key
cert.not_before = Time.now
cert.not_after = cert.not_before + 2 * 365 * config[:cert_validity].to_i * 60 * 60 # 2 years validity
ef = OpenSSL::X509::ExtensionFactory.new
ef.subject_certificate = cert
ef.issuer_certificate = cert
cert.add_extension(ef.create_extension("subjectKeyIdentifier", "hash", false))
cert.add_extension(ef.create_extension("authorityKeyIdentifier", "keyid:always", false))
cert.add_extension(ef.create_extension("extendedKeyUsage", "1.3.6.1.5.5.7.3.1", false))
cert.sign(rsa_key, OpenSSL::Digest.new("SHA1"))
@thumbprint = OpenSSL::Digest::SHA1.new(cert.to_der)
cert
end

def write_certificate_to_file(cert, file_path, rsa_key)
File.open(file_path + ".pem", "wb") { |f| f.print cert.to_pem }
passphrase = config[:cert_passphrase] || prompt_for_passphrase
pfx = OpenSSL::PKCS12.create("#{passphrase}", "winrmcert", rsa_key, cert)
File.open(file_path + ".pfx", "wb") { |f| f.print pfx.to_der }
File.open(file_path + ".b64", "wb") { |f| f.print Base64.strict_encode64(pfx.to_der) }
end

def certificates_already_exist?(file_path)
unless Dir.glob("#{file_path}.*{pem,pfx,b64}").empty?
begin
confirm("Overwrite existing certificates?")
rescue SystemExit # Need to handle this as confirming with N/n raises SystemExit exception
exit!
end
end
end

def run
# takes user specified first cli value as a destination file path for generated cert.
file_path = @name_args.empty? ? config[:output_file].sub(/\.(\w+)$/, "") : @name_args.first

# check if certs already exists at given file path
certificates_already_exist?(file_path)

begin
filename = File.basename(file_path)
rsa_key = generate_key_pair
cert = generate_certificate rsa_key
write_certificate_to_file cert, file_path, rsa_key
ui.info "Generated Certificates:"
ui.info "- #{filename}.pfx - PKCS12 format key pair. Contains public and private keys, can be used with an SSL server."
ui.info "- #{filename}.b64 - Base64 encoded PKCS12 key pair. Contains public and private keys, used by some cloud provider APIs to configure SSL servers."
ui.info "- #{filename}.pem - Base64 encoded public certificate only. Required by the client to connect to the server."
ui.info "Certificate Thumbprint: #{@thumbprint.to_s.upcase}"
rescue => e
ui.fatal!("Failed to generate certificates: #{e}")
end
end

end
end
end
64 changes: 64 additions & 0 deletions lib/chef/knife/windows_cert_install.rb
@@ -0,0 +1,64 @@
# Author:: Mukta Aphale (<mukta.aphale@clogeny.com>)
# Copyright:: Copyright (c) 2014-2016 Chef Software, Inc.
# License:: Apache License, Version 2.0
#
# 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 "../knife"
require "chef-utils" unless defined?(ChefUtils::CANARY)

class Chef
class Knife
class WindowsCertInstall < Knife

banner "knife windows cert install CERT [CERT] (options)"

option :cert_passphrase,
short: "-cp PASSWORD",
long: "--cert-passphrase PASSWORD",
description: "Passphrase for certificate."

def get_cert_passphrase
config[:cert_passphrase] || ui.ask("Enter given certificate's passphrase (empty for no passphrase): ", echo: false)
end

def run
unless ChefUtils.windows?
ui.error "Certificate can be installed on Windows system only"
exit 1
end

if @name_args.empty?
ui.error "Please specify the certificate path. e.g- 'knife windows cert install <path>"
exit 1
end
file_path = @name_args.first
cert_passphrase = get_cert_passphrase

begin
ui.info "Adding certificate to the Windows Certificate Store..."
result = `powershell.exe -Command " '#{cert_passphrase}' | certutil -importPFX '#{file_path}' AT_KEYEXCHANGE"`
if $?.exitstatus == 0
ui.info "Certificate added to Certificate Store"
else
ui.info "Error adding the certificate. Use -VV option for details"
end
Chef::Log.debug "#{result}"
rescue => e
ui.fatal!("Failed to add certificate to the Windows Certificate Store: #{e}")
end
end
end
end
end