summaryrefslogtreecommitdiff
path: root/lib/chef/knife/ssl_fetch.rb
diff options
context:
space:
mode:
authordanielsdeleo <dan@opscode.com>2013-10-29 16:02:38 -0700
committerdanielsdeleo <dan@getchef.com>2014-03-19 22:32:30 -0700
commitcc2307a9b7774c1b8a70066c84961ecbfd05d5d5 (patch)
treeaeb4778ef163c00f04d071618d0c7c40941a5a9e /lib/chef/knife/ssl_fetch.rb
parent09f22372674b88e6e3ee7bb9aff406862ae0f27c (diff)
downloadchef-cc2307a9b7774c1b8a70066c84961ecbfd05d5d5.tar.gz
Add SSL check and certificate fetching commands to knife
Fixes CHEF-4711
Diffstat (limited to 'lib/chef/knife/ssl_fetch.rb')
-rw-r--r--lib/chef/knife/ssl_fetch.rb145
1 files changed, 145 insertions, 0 deletions
diff --git a/lib/chef/knife/ssl_fetch.rb b/lib/chef/knife/ssl_fetch.rb
new file mode 100644
index 0000000000..5626a5610d
--- /dev/null
+++ b/lib/chef/knife/ssl_fetch.rb
@@ -0,0 +1,145 @@
+#
+# Author:: Daniel DeLeo (<dan@getchef.com>)
+# Copyright:: Copyright (c) 2014 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 'chef/knife/ssl_fetch'
+require 'chef/config'
+
+class Chef
+ class Knife
+ class SslFetch < Chef::Knife
+
+ deps do
+ require 'pp'
+ require 'socket'
+ require 'uri'
+ require 'openssl'
+ end
+
+ banner "knife ssl fetch [URL] (options)"
+
+ def initialize(*args)
+ super
+ @uri = nil
+ end
+
+ def uri
+ @uri ||= begin
+ Chef::Log.debug("Checking SSL cert on #{given_uri}")
+ URI.parse(given_uri)
+ end
+ end
+
+ def given_uri
+ (name_args[0] or Chef::Config.chef_server_url)
+ end
+
+ def host
+ uri.host
+ end
+
+ def port
+ uri.port
+ end
+
+ def validate_uri
+ unless host && port
+ invalid_uri!
+ end
+ rescue URI::Error
+ invalid_uri!
+ end
+
+ def invalid_uri!
+ ui.error("Given URI: `#{given_uri}' is invalid")
+ show_usage
+ exit 1
+ end
+
+ def remote_cert_chain
+ tcp_connection = TCPSocket.new(host, port)
+ shady_ssl_connection = OpenSSL::SSL::SSLSocket.new(tcp_connection, noverify_peer_ssl_context)
+ shady_ssl_connection.connect
+ shady_ssl_connection.peer_cert_chain
+ end
+
+ def noverify_peer_ssl_context
+ @noverify_peer_ssl_context ||= begin
+ noverify_peer_context = OpenSSL::SSL::SSLContext.new
+ noverify_peer_context.verify_mode = OpenSSL::SSL::VERIFY_NONE
+ noverify_peer_context
+ end
+ end
+
+
+ def cn_of(certificate)
+ subject = certificate.subject
+ cn_field_tuple = subject.to_a.find {|field| field[0] == "CN" }
+ cn_field_tuple[1]
+ end
+
+ # Convert the CN of a certificate into something that will work well as a
+ # filename. To do so, all `*` characters are converted to the string
+ # "wildcard" and then all characters other than alphanumeric and hypen
+ # characters are converted to underscores.
+ # NOTE: There is some confustion about what the CN will contain when
+ # using internationalized domain names. RFC 6125 mandates that the ascii
+ # representation be used, but it is not clear whether this is followed in
+ # practice.
+ # https://tools.ietf.org/html/rfc6125#section-6.4.2
+ def normalize_cn(cn)
+ cn.gsub("*", "wildcard").gsub(/[^[:alnum:]\-]/, '_')
+ end
+
+ def configuration
+ Chef::Config
+ end
+
+ def trusted_certs_dir
+ configuration.trusted_certs_dir
+ end
+
+ def write_cert(cert)
+ FileUtils.mkdir_p(trusted_certs_dir)
+ cn = cn_of(cert)
+ filename = File.join(trusted_certs_dir, "#{normalize_cn(cn)}.crt")
+ ui.msg("Adding certificate for #{cn} in #{filename}")
+ File.open(filename, File::CREAT|File::TRUNC|File::RDWR, 0644) do |f|
+ f.print(cert.to_s)
+ end
+ end
+
+ def run
+ validate_uri
+ ui.warn(<<-TRUST_TRUST)
+Certificates from #{host} will be fetched and placed in your trusted_cert
+directory (#{trusted_certs_dir}).
+
+Knife has no means to verify these are the correct certificates. You should
+verify the authenticity of these certificates after downloading.
+
+TRUST_TRUST
+ remote_cert_chain.each do |cert|
+ write_cert(cert)
+ end
+ end
+
+
+ end
+ end
+end
+