diff options
author | Claire McQuin <claire@getchef.com> | 2014-09-09 15:23:00 -0700 |
---|---|---|
committer | Claire McQuin <claire@getchef.com> | 2014-09-10 11:41:12 -0700 |
commit | b050cb5c6d37a07928f5dafd678f569818dd97ce (patch) | |
tree | 9ede7393533a18b247d2507459c7abddd75d4472 | |
parent | 3fb87cc744d1e1134476496dedc9125a25add859 (diff) | |
download | chef-b050cb5c6d37a07928f5dafd678f569818dd97ce.tar.gz |
Detect invalid X509 certificates.
-rw-r--r-- | lib/chef/knife/ssl_check.rb | 56 | ||||
-rw-r--r-- | spec/unit/knife/ssl_check_spec.rb | 47 |
2 files changed, 96 insertions, 7 deletions
diff --git a/lib/chef/knife/ssl_check.rb b/lib/chef/knife/ssl_check.rb index e98469d5aa..f7edd6b019 100644 --- a/lib/chef/knife/ssl_check.rb +++ b/lib/chef/knife/ssl_check.rb @@ -106,6 +106,54 @@ class Chef end end + def verify_X509 + cert_debug_msg = "" + if configuration.trusted_certs_dir && Dir.exist?(configuration.trusted_certs_dir) + # Check each trusted certificate to ensure that it meets X509 standard. + Dir.glob(File.join(configuration.trusted_certs_dir, "*.{crt,pem}")).each do |cert_name| + store = OpenSSL::X509::Store.new + cert = OpenSSL::X509::Certificate.new(IO.read(File.expand_path(cert))) + begin + store.add(cert) + # test if the store can verify the cert we just added + unless store.verify(cert) # true if verified, false if not + cert_debug_msg << "#{File.expand_path(cert_name)}: #{store.error_string}\n" + end + rescue OpenSSL::X509::StoreError + cert_debug_msg << "#{File.expand_path(cert_name)}: #{store.error_string}\n" + end + end + end + + unless cert_debug_msg.empty? + ui.msg("\n#{ui.color("Configuration Info:", :bold)}\n\n") + debug_ssl_settings + debug_chef_ssl_config + + ui.warn(<<-BAD_CERTS +There are invalid certificates in your trusted_certs_dir. +Although Chef may be configured to trust these certificates, +OpenSSL may not use the following certificates when verifying SSL connections: + +#{cert_debug_msg} + +#{ui.color("TO FIX THESE WARNINGS:", :bold)} + +We are working on documentation for generating self-signed certificates with Subject +Alternative Names (SANs). For now, please refer to the discussion on GitHub Issue 1700 + + https://github.com/opscode/chef/issues/1700 + +and direct any further questions to github.com/opscode/chef/issues or the chef +mailing lists. + +BAD_CERTS + # @TODO: ^ needs URL + return false + end + return true + end + def verify_cert ui.msg("Connecting to host #{host}:#{port}") verify_peer_socket.connect @@ -148,7 +196,7 @@ where your chef-server runs: /var/opt/chef-server/nginx/ca/SERVER_HOSTNAME.crt -Copy that file to you trusted_certs_dir (currently: #{configuration.trusted_certs_dir}) +Copy that file to your trusted_certs_dir (currently: #{configuration.trusted_certs_dir}) using SSH/SCP or some other secure method, then re-run this command to confirm that the server's certificate is now trusted. @@ -197,7 +245,7 @@ ADVICE def run validate_uri - if verify_cert && verify_cert_host + if verify_X509 && verify_cert && verify_cert_host ui.msg "Successfully verified certificates from `#{host}'" else exit 1 @@ -207,7 +255,3 @@ ADVICE end end end - - - - diff --git a/spec/unit/knife/ssl_check_spec.rb b/spec/unit/knife/ssl_check_spec.rb index 32405a5977..6896a6b80f 100644 --- a/spec/unit/knife/ssl_check_spec.rb +++ b/spec/unit/knife/ssl_check_spec.rb @@ -95,6 +95,49 @@ E end end + describe "verifying trusted certificate X509 properties" do + let(:name_args) { %w{https://foo.example.com:8443} } + + let(:trusted_certs_dir) { File.join(CHEF_SPEC_DATA, "trusted_certs") } + let(:trusted_certs) { [File.join(trusted_certs_dir, "example.crt")] } + + let(:store) { OpenSSL::X509::Store.new } + let(:certificate) { OpenSSL::X509::Certificate.new(IO.read(trusted_certs[0])) } + + before do + Chef::Config[:trusted_certs_dir] = trusted_certs_dir + Dir.stub(:glob).with(File.join(trusted_certs_dir, "*.{crt,pem}")).and_return(trusted_certs) + store.stub(:add).with(certificate) + OpenSSL::X509::Store.stub(:new).and_return(store) + OpenSSL::X509::Certificate.stub(:new).with(IO.read(trusted_certs[0])).and_return(certificate) + ssl_check.stub(:verify_cert).and_return(true) + ssl_check.stub(:verify_cert_host).and_return(true) + end + + context "when the trusted certificates have valid X509 properties" do + before do + store.stub(:verify).with(certificate).and_return(true) + end + + it "does not generate any X509 warnings" do + expect(ssl_check.ui).not_to receive(:warn).with(/There are invalid certificates in your trusted_certs_dir/) + ssl_check.run + end + end + + context "when the trusted certificates have invalid X509 properties" do + before do + store.stub(:verify).with(certificate).and_return(false) + store.stub(:error_string).and_return("unable to get local issuer certificate") + end + + it "generates a warning message with invalid certificate file names" do + expect(ssl_check.ui).to receive(:warn).with(/#{trusted_certs[0]}: unable to get local issuer certificate/) + ssl_check.run + end + end + end + describe "verifying the remote certificate" do let(:name_args) { %w{https://foo.example.com:8443} } @@ -117,6 +160,7 @@ E context "when the remote host's certificate is valid" do before do + ssl_check.should_receive(:verify_X509).and_return(true) # X509 valid certs (no warn) ssl_socket.should_receive(:connect) # no error ssl_socket.should_receive(:post_connection_check).with("foo.example.com") # no error end @@ -148,6 +192,7 @@ E context "when the certificate's CN does not match the hostname" do before do + ssl_check.should_receive(:verify_X509).and_return(true) # X509 valid certs ssl_socket.should_receive(:connect) # no error ssl_socket.should_receive(:post_connection_check). with("foo.example.com"). @@ -167,6 +212,7 @@ E context "when the cert is not signed by any trusted authority" do before do + ssl_check.should_receive(:verify_X509).and_return(true) # X509 valid certs ssl_socket.should_receive(:connect). and_raise(OpenSSL::SSL::SSLError) ssl_socket_for_debug.should_receive(:connect) @@ -184,4 +230,3 @@ E end end - |