Check all hosts for shared certificates
authorTom Hughes <tom@compton.nu>
Fri, 26 Jan 2018 15:22:34 +0000 (15:22 +0000)
committerTom Hughes <tom@compton.nu>
Fri, 26 Jan 2018 15:23:35 +0000 (15:23 +0000)
cookbooks/letsencrypt/files/default/bin/check-certificate
cookbooks/letsencrypt/recipes/default.rb
cookbooks/letsencrypt/templates/default/check-certificates.erb

index 303314f..35fbbed 100755 (executable)
@@ -1,36 +1,52 @@
 #!/usr/bin/ruby
 
-require "net/http"
+require "socket"
+require "openssl"
 
-domain = ARGV.first
+host = ARGV.shift
+address = ARGV.shift
+domains = ARGV
+
+context = OpenSSL::SSL::SSLContext.new
+context.verify_mode = OpenSSL::SSL::VERIFY_NONE
 
 begin
-  connection = Net::HTTP.start(domain, :use_ssl => true)
-  certificate = connection.peer_cert
+  socket = TCPSocket.new(address, 443)
 
-  if Time.now < certificate.not_before
-    puts "Certificate #{domain} not valid until #{certificate.not_before}"
-  elsif certificate.not_after - Time.now < 21 * 86400
-    puts "Certificate #{domain} expires at #{certificate.not_after}"
-  else
-    subject_alt_name = certificate.extensions.find { |e| e.oid == "subjectAltName" }
+  ssl = OpenSSL::SSL::SSLSocket.new(socket, context)
+  ssl.sync_close = true
+  ssl.hostname = domains.first
+  ssl.connect
+rescue StandardError => error
+  puts "Error connecting to #{host}: #{error.message}"
+end
 
-    if subject_alt_name.nil?
-      puts "Certificate #{domain} has no subjectAltName"
-    else
-      alt_names = subject_alt_name.value.split(/\s*,\s*/).sort
+certificate = ssl.peer_cert
 
-      ARGV.sort.each do |expected|
-        puts "Certificate #{domain} is missing subjectAltName #{expected}" unless alt_names.shift == "DNS:#{expected}"
-      end
+if Time.now < certificate.not_before
+  puts "Certificate #{domains.first} on #{host} not valid until #{certificate.not_before}"
+elsif certificate.not_after - Time.now < 21 * 86400
+  puts "Certificate #{domains.first} on #{host} expires at #{certificate.not_after}"
+else
+  subject_alt_name = certificate.extensions.find { |e| e.oid == "subjectAltName" }
+
+  if subject_alt_name.nil?
+    puts "Certificate #{domains.first} on #{host} has no subjectAltName"
+  else
+    alt_names = subject_alt_name.value.split(/\s*,\s*/).map { |n| n.sub(/^DNS:/, "") }
 
-      alt_names.each do |name|
-        puts "Certificate #{domain} has unexpected subjectAltName #{name}"
+    domains.each do |domain|
+      if alt_names.include?(domain)
+        alt_names.delete(domain)
+      else
+        puts "Certificate #{domains.first} on #{host} is missing subjectAltName #{domain}"
       end
     end
-  end
 
-  connection.finish
-rescue StandardError => error
-  puts "Error connecting to #{domain}: #{error.message}"
+    alt_names.each do |name|
+      puts "Certificate #{domains.first} on #{host} has unexpected subjectAltName #{name}"
+    end
+  end
 end
+
+ssl.close
index a2c42e1..34e9be8 100644 (file)
@@ -121,7 +121,11 @@ directory "/srv/acme.openstreetmap.org/requests" do
 end
 
 certificates = search(:node, "letsencrypt:certificates").each_with_object({}) do |n, c|
-  c.merge!(n[:letsencrypt][:certificates])
+  n[:letsencrypt][:certificates].each do |name, details|
+    c[name] ||= details.merge(:nodes => [])
+
+    c[name][:nodes] << { :name => n[:fqdn], :address => n[:ipaddress] }
+  end
 end
 
 certificates.each do |name, details|
index d03e98a..d07bacf 100644 (file)
@@ -1,5 +1,7 @@
 #!/bin/sh
 
 <% @certificates.each_value do |certificate| -%>
-/srv/acme.openstreetmap.org/bin/check-certificate <%= certificate[:domains].join(" ") %>
+<% certificate[:nodes].each do |host| -%>
+/srv/acme.openstreetmap.org/bin/check-certificate <%= host[:name] %> <%= host[:address] %> <%= certificate[:domains].join(" ") %>
+<% end -%>
 <% end -%>