summaryrefslogtreecommitdiff
path: root/lib/leap_cli/commands/ca.rb
diff options
context:
space:
mode:
Diffstat (limited to 'lib/leap_cli/commands/ca.rb')
-rw-r--r--lib/leap_cli/commands/ca.rb649
1 files changed, 237 insertions, 412 deletions
diff --git a/lib/leap_cli/commands/ca.rb b/lib/leap_cli/commands/ca.rb
index 1b311eee..3c5fc7d5 100644
--- a/lib/leap_cli/commands/ca.rb
+++ b/lib/leap_cli/commands/ca.rb
@@ -1,8 +1,3 @@
-autoload :OpenSSL, 'openssl'
-autoload :CertificateAuthority, 'certificate_authority'
-autoload :Date, 'date'
-require 'digest/md5'
-
module LeapCli; module Commands
desc "Manage X.509 certificates"
@@ -35,41 +30,15 @@ module LeapCli; module Commands
cert.desc 'Creates a Diffie-Hellman parameter file, needed for forward secret OpenVPN ciphers.' # (needed for server-side of some TLS connections)
cert.command :dh do |dh|
dh.action do |global_options,options,args|
- long_running do
- if cmd_exists?('certtool')
- log 0, 'Generating DH parameters (takes a long time)...'
- output = assert_run!('certtool --generate-dh-params --sec-param high')
- output.sub! /.*(-----BEGIN DH PARAMETERS-----.*-----END DH PARAMETERS-----).*/m, '\1'
- output << "\n"
- write_file!(:dh_params, output)
- else
- log 0, 'Generating DH parameters (takes a REALLY long time)...'
- output = OpenSSL::PKey::DH.generate(3248).to_pem
- write_file!(:dh_params, output)
- end
- end
+ generate_dh
end
end
- #
- # hints:
- #
- # inspect CSR:
- # openssl req -noout -text -in files/cert/x.csr
- #
- # generate CSR with openssl to see how it compares:
- # openssl req -sha256 -nodes -newkey rsa:2048 -keyout example.key -out example.csr
- #
- # validate a CSR:
- # http://certlogik.com/decoder/
- #
- # nice details about CSRs:
- # http://www.redkestrel.co.uk/Articles/CSR.html
- #
cert.desc "Creates a CSR for use in buying a commercial X.509 certificate."
cert.long_desc "Unless specified, the CSR is created for the provider's primary domain. "+
"The properties used for this CSR come from `provider.ca.server_certificates`, "+
"but may be overridden here."
+ cert.arg_name "DOMAIN"
cert.command :csr do |csr|
csr.flag 'domain', :arg_name => 'DOMAIN', :desc => 'Specify what domain to create the CSR for.'
csr.flag ['organization', 'O'], :arg_name => 'ORGANIZATION', :desc => "Override default O in distinguished name."
@@ -81,70 +50,26 @@ module LeapCli; module Commands
csr.flag :bits, :arg_name => 'BITS', :desc => "Override default certificate bit length"
csr.flag :digest, :arg_name => 'DIGEST', :desc => "Override default signature digest"
csr.action do |global_options,options,args|
- assert_config! 'provider.domain'
- assert_config! 'provider.name'
- assert_config! 'provider.default_language'
- assert_config! 'provider.ca.server_certificates.bit_size'
- assert_config! 'provider.ca.server_certificates.digest'
- domain = options[:domain] || provider.domain
-
- unless global_options[:force]
- assert_files_missing! [:commercial_key, domain], [:commercial_csr, domain],
- :msg => 'If you really want to create a new key and CSR, remove these files first or run with --force.'
- end
-
- server_certificates = provider.ca.server_certificates
-
- # RSA key
- keypair = CertificateAuthority::MemoryKeyMaterial.new
- bit_size = (options[:bits] || server_certificates.bit_size).to_i
- log :generating, "%s bit RSA key" % bit_size do
- keypair.generate_key(bit_size)
- write_file! [:commercial_key, domain], keypair.private_key.to_pem
- end
-
- # CSR
- dn = CertificateAuthority::DistinguishedName.new
- dn.common_name = domain
- dn.organization = options[:organization] || provider.name[provider.default_language]
- dn.ou = options[:organizational_unit] # optional
- dn.email_address = options[:email] # optional
- dn.country = options[:country] || server_certificates['country'] # optional
- dn.state = options[:state] || server_certificates['state'] # optional
- dn.locality = options[:locality] || server_certificates['locality'] # optional
-
- digest = options[:digest] || server_certificates.digest
- log :generating, "CSR with #{digest} digest and #{print_dn(dn)}" do
- csr = create_csr(dn, keypair, digest)
- request = csr.to_x509_csr
- write_file! [:commercial_csr, domain], csr.to_pem
- end
+ generate_csr(global_options, options, args)
+ end
+ end
- # Sign using our own CA, for use in testing but hopefully not production.
- # It is not that commerical CAs are so secure, it is just that signing your own certs is
- # a total drag for the user because they must click through dire warnings.
- #if options[:sign]
- log :generating, "self-signed x509 server certificate for testing purposes" do
- cert = csr.to_cert
- cert.serial_number.number = cert_serial_number(domain)
- cert.not_before = yesterday
- cert.not_after = yesterday.advance(:years => 1)
- cert.parent = ca_root
- cert.sign! domain_test_signing_profile
- write_file! [:commercial_cert, domain], cert.to_pem
- log "please replace this file with the real certificate you get from a CA using #{Path.relative_path([:commercial_csr, domain])}"
- end
- #end
+ cert.desc "Register an authorization key with the CA letsencrypt.org"
+ cert.long_desc "This only needs to be done once."
+ cert.command :register do |register|
+ register.action do |global, options, args|
+ do_register_key(global, options, args)
+ end
+ end
- # FAKE CA
- unless file_exists? :commercial_ca_cert
- log :using, "generated CA in place of commercial CA for testing purposes" do
- write_file! :commercial_ca_cert, read_file!(:ca_cert)
- log "please also replace this file with the CA cert from the commercial authority you use."
- end
- end
+ cert.desc "Renews a certificate using the CA letsencrypt.org"
+ cert.arg_name "DOMAIN"
+ cert.command :renew do |renew|
+ renew.action do |global, options, args|
+ do_renew_cert(global, options, args)
end
end
+
end
protected
@@ -153,6 +78,7 @@ module LeapCli; module Commands
# will generate new certificates for the specified nodes, if needed.
#
def update_certificates(nodes, options={})
+ require 'leap_cli/x509'
assert_files_exist! :ca_cert, :ca_key, :msg => 'Run `leap cert ca` to create them'
assert_config! 'provider.ca.server_certificates.bit_size'
assert_config! 'provider.ca.server_certificates.digest'
@@ -160,382 +86,281 @@ module LeapCli; module Commands
assert_config! 'common.x509.use'
nodes.each_node do |node|
- warn_if_commercial_cert_will_soon_expire(node)
+ node.warn_if_commercial_cert_will_soon_expire
if !node.x509.use
remove_file!([:node_x509_key, node.name])
remove_file!([:node_x509_cert, node.name])
- elsif options[:force] || cert_needs_updating?(node)
- generate_cert_for_node(node)
+ elsif options[:force] || node.cert_needs_updating?
+ node.generate_cert
end
end
end
+ #
+ # yields client key and cert suitable for testing
+ #
+ def generate_test_client_cert(prefix=nil)
+ require 'leap_cli/x509'
+ cert = CertificateAuthority::Certificate.new
+ cert.serial_number.number = cert_serial_number(provider.domain)
+ cert.subject.common_name = [prefix, random_common_name(provider.domain)].join
+ cert.not_before = X509.yesterday
+ cert.not_after = X509.yesterday.advance(:years => 1)
+ cert.key_material.generate_key(1024) # just for testing, remember!
+ cert.parent = client_ca_root
+ cert.sign! client_test_signing_profile
+ yield cert.key_material.private_key.to_pem, cert.to_pem
+ end
+
private
def generate_new_certificate_authority(key_file, cert_file, common_name)
+ require 'leap_cli/x509'
assert_files_missing! key_file, cert_file
assert_config! 'provider.ca.name'
assert_config! 'provider.ca.bit_size'
assert_config! 'provider.ca.life_span'
- root = CertificateAuthority::Certificate.new
+ root = X509.new_ca(provider.ca, common_name)
- # set subject
- root.subject.common_name = common_name
- possible = ['country', 'state', 'locality', 'organization', 'organizational_unit', 'email_address']
- provider.ca.keys.each do |key|
- if possible.include?(key)
- root.subject.send(key + '=', provider.ca[key])
+ write_file!(key_file, root.key_material.private_key.to_pem)
+ write_file!(cert_file, root.to_pem)
+ end
+
+ def generate_dh
+ require 'leap_cli/x509'
+ long_running do
+ if cmd_exists?('certtool')
+ log 0, 'Generating DH parameters (takes a long time)...'
+ output = assert_run!('certtool --generate-dh-params --sec-param high')
+ output.sub!(/.*(-----BEGIN DH PARAMETERS-----.*-----END DH PARAMETERS-----).*/m, '\1')
+ output << "\n"
+ write_file!(:dh_params, output)
+ else
+ log 0, 'Generating DH parameters (takes a REALLY long time)...'
+ output = OpenSSL::PKey::DH.generate(3248).to_pem
+ write_file!(:dh_params, output)
end
end
+ end
- # set expiration
- root.not_before = yesterday
- root.not_after = yesterday_advance(provider.ca.life_span)
-
- # generate private key
- root.serial_number.number = 1
- root.key_material.generate_key(provider.ca.bit_size)
+ #
+ # hints:
+ #
+ # inspect CSR:
+ # openssl req -noout -text -in files/cert/x.csr
+ #
+ # generate CSR with openssl to see how it compares:
+ # openssl req -sha256 -nodes -newkey rsa:2048 -keyout example.key -out example.csr
+ #
+ # validate a CSR:
+ # http://certlogik.com/decoder/
+ #
+ # nice details about CSRs:
+ # http://www.redkestrel.co.uk/Articles/CSR.html
+ #
+ def generate_csr(global_options, options, args)
+ require 'leap_cli/x509'
+ assert_config! 'provider.domain'
+ assert_config! 'provider.name'
+ assert_config! 'provider.default_language'
+ assert_config! 'provider.ca.server_certificates.bit_size'
+ assert_config! 'provider.ca.server_certificates.digest'
- # sign self
- root.signing_entity = true
- root.parent = root
- root.sign!(ca_root_signing_profile)
+ server_certificates = provider.ca.server_certificates
+ options[:domain] ||= args.first || provider.domain
+ options[:organization] ||= provider.name[provider.default_language]
+ options[:country] ||= server_certificates['country']
+ options[:state] ||= server_certificates['state']
+ options[:locality] ||= server_certificates['locality']
+ options[:bits] ||= server_certificates.bit_size
+ options[:digest] ||= server_certificates.digest
+
+ unless global_options[:force]
+ assert_files_missing! [:commercial_key, options[:domain]], [:commercial_csr, options[:domain]],
+ :msg => 'If you really want to create a new key and CSR, remove these files first or run with --force.'
+ end
- # save
- write_file!(key_file, root.key_material.private_key.to_pem)
- write_file!(cert_file, root.to_pem)
+ X509.create_csr_and_cert(options)
end
#
- # returns true if the certs associated with +node+ need to be regenerated.
+ # letsencrypt.org
#
- def cert_needs_updating?(node)
- if !file_exists?([:node_x509_cert, node.name], [:node_x509_key, node.name])
- return true
- else
- cert = load_certificate_file([:node_x509_cert, node.name])
- if !created_by_authority?(cert, ca_root)
- log :updating, "cert for node '#{node.name}' because it was signed by an old CA root cert."
- return true
- end
- if cert.not_after < Time.now.advance(:months => 2)
- log :updating, "cert for node '#{node.name}' because it will expire soon"
- return true
- end
- if cert.subject.common_name != node.domain.full
- log :updating, "cert for node '#{node.name}' because domain.full has changed (was #{cert.subject.common_name}, now #{node.domain.full})"
- return true
+
+ def do_register_key(global, options, args)
+ require 'leap_cli/acme'
+ assert_config! 'provider.contacts.default'
+ contact = manager.provider.contacts.default.first
+
+ if file_exists?(:acme_key) && !global[:force]
+ bail! do
+ log "the authorization key for letsencrypt.org already exists"
+ log "run with --force if you really want to register a new key."
end
- cert.openssl_body.extensions.each do |ext|
- if ext.oid == "subjectAltName"
- ips = []
- dns_names = []
- ext.value.split(",").each do |value|
- value.strip!
- ips << $1 if value =~ /^IP Address:(.*)$/
- dns_names << $1 if value =~ /^DNS:(.*)$/
- end
- dns_names.sort!
- if ips.first != node.ip_address
- log :updating, "cert for node '#{node.name}' because ip_address has changed (from #{ips.first} to #{node.ip_address})"
- return true
- elsif dns_names != dns_names_for_node(node)
- log :updating, "cert for node '#{node.name}' because domain name aliases have changed\n from: #{dns_names.inspect}\n to: #{dns_names_for_node(node).inspect})"
- return true
- end
+ else
+ private_key = Acme.new_private_key
+ registration = nil
+
+ log(:registering, "letsencrypt.org authorization key using contact `%s`" % contact) do
+ acme = Acme.new(key: private_key)
+ registration = acme.register(contact)
+ if registration
+ log 'success!', :color => :green, :style => :bold
+ else
+ bail! "could not register authorization key."
end
end
- end
- return false
- end
- def created_by_authority?(cert, ca)
- authority_key_id = cert.extensions["authorityKeyIdentifier"].identifier.sub(/^keyid:/, '')
- authority_key_id == public_key_id_for_ca(ca)
- end
-
- # calculate the "key id" for a root CA, that matches the value
- # Authority Key Identifier in the x509 extensions of a cert.
- def public_key_id_for_ca(ca_cert)
- @ca_key_ids ||= {}
- @ca_key_ids[ca_cert.object_id] ||= begin
- pubkey = ca_cert.key_material.public_key
- seq = OpenSSL::ASN1::Sequence([
- OpenSSL::ASN1::Integer.new(pubkey.n),
- OpenSSL::ASN1::Integer.new(pubkey.e)
- ])
- Digest::SHA1.hexdigest(seq.to_der).upcase.scan(/../).join(':')
+ log :saving, "authorization key for letsencrypt.org" do
+ write_file!(:acme_key, private_key.to_pem)
+ write_file!(:acme_info, JSON.sorted_generate({
+ id: registration.id,
+ contact: registration.contact,
+ key: registration.key,
+ uri: registration.uri
+ }))
+ log :warning, "keep key file private!"
+ end
end
end
- def warn_if_commercial_cert_will_soon_expire(node)
- dns_names_for_node(node).each do |domain|
- if file_exists?([:commercial_cert, domain])
- cert = load_certificate_file([:commercial_cert, domain])
- path = Path.relative_path([:commercial_cert, domain])
- if cert.not_after < Time.now.utc
- log :error, "the commercial certificate '#{path}' has EXPIRED! " +
- "You should renew it with `leap cert csr --domain #{domain}`."
- elsif cert.not_after < Time.now.advance(:months => 2)
- log :warning, "the commercial certificate '#{path}' will expire soon (#{cert.not_after}). "+
- "You should renew it with `leap cert csr --domain #{domain}`."
- end
- end
+ def assert_no_errors!(msg)
+ yield
+ rescue StandardError => exc
+ bail! :error, msg do
+ log exc.to_s
end
end
- def generate_cert_for_node(node)
- return if node.x509.use == false
-
- cert = CertificateAuthority::Certificate.new
-
- # set subject
- cert.subject.common_name = node.domain.full
- cert.serial_number.number = cert_serial_number(node.domain.full)
+ def do_renew_cert(global, options, args)
+ require 'leap_cli/acme'
+ require 'leap_cli/ssh'
+ require 'socket'
+ require 'net/http'
- # set expiration
- cert.not_before = yesterday
- cert.not_after = yesterday_advance(provider.ca.server_certificates.life_span)
+ csr = nil
+ account_key = nil
+ cert = nil
+ acme = nil
- # generate key
- cert.key_material.generate_key(provider.ca.server_certificates.bit_size)
-
- # sign
- cert.parent = ca_root
- cert.sign!(server_signing_profile(node))
-
- # save
- write_file!([:node_x509_key, node.name], cert.key_material.private_key.to_pem)
- write_file!([:node_x509_cert, node.name], cert.to_pem)
- end
-
- #
- # yields client key and cert suitable for testing
- #
- def generate_test_client_cert(prefix=nil)
- cert = CertificateAuthority::Certificate.new
- cert.serial_number.number = cert_serial_number(provider.domain)
- cert.subject.common_name = [prefix, random_common_name(provider.domain)].join
- cert.not_before = yesterday
- cert.not_after = yesterday.advance(:years => 1)
- cert.key_material.generate_key(1024) # just for testing, remember!
- cert.parent = client_ca_root
- cert.sign! client_test_signing_profile
- yield cert.key_material.private_key.to_pem, cert.to_pem
- end
-
- #
- # creates a CSR and returns it.
- # with the correct extReq attribute so that the CA
- # doens't generate certs with extensions we don't want.
- #
- def create_csr(dn, keypair, digest)
- csr = CertificateAuthority::SigningRequest.new
- csr.distinguished_name = dn
- csr.key_material = keypair
- csr.digest = digest
-
- # define extensions manually (library doesn't support setting these on CSRs)
- extensions = []
- extensions << CertificateAuthority::Extensions::BasicConstraints.new.tap {|basic|
- basic.ca = false
- }
- extensions << CertificateAuthority::Extensions::KeyUsage.new.tap {|keyusage|
- keyusage.usage = ["digitalSignature", "keyEncipherment"]
- }
- extensions << CertificateAuthority::Extensions::ExtendedKeyUsage.new.tap {|extkeyusage|
- extkeyusage.usage = [ "serverAuth"]
- }
-
- # convert extensions to attribute 'extReq'
- # aka "Requested Extensions"
- factory = OpenSSL::X509::ExtensionFactory.new
- attrval = OpenSSL::ASN1::Set([OpenSSL::ASN1::Sequence(
- extensions.map{|e| factory.create_ext(e.openssl_identifier, e.to_s, e.critical)}
- )])
- attrs = [
- OpenSSL::X509::Attribute.new("extReq", attrval),
- ]
- csr.attributes = attrs
-
- return csr
- end
+ #
+ # sanity check the domain
+ #
+ domain = args.first
+ nodes = nodes_for_domain(domain)
+ domain_ready_for_acme!(domain)
- def ca_root
- @ca_root ||= begin
- load_certificate_file(:ca_cert, :ca_key)
+ #
+ # load key material
+ #
+ assert_files_exist!([:commercial_key, domain], [:commercial_csr, domain],
+ :msg => 'Please create the CSR first with `leap cert csr %s`' % domain)
+ assert_no_errors!("Could not load #{path([:commercial_csr, domain])}") do
+ csr = Acme.load_csr(read_file!([:commercial_csr, domain]))
end
- end
-
- def client_ca_root
- @client_ca_root ||= begin
- load_certificate_file(:client_ca_cert, :client_ca_key)
+ assert_files_exist!(:acme_key,
+ :msg => "Please run `leap cert register` first. This only needs to be done once.")
+ assert_no_errors!("Could not load #{path(:acme_key)}") do
+ account_key = Acme.load_private_key(read_file!(:acme_key))
end
- end
- def load_certificate_file(crt_file, key_file=nil, password=nil)
- crt = read_file!(crt_file)
- openssl_cert = OpenSSL::X509::Certificate.new(crt)
- cert = CertificateAuthority::Certificate.from_openssl(openssl_cert)
- if key_file
- key = read_file!(key_file)
- cert.key_material.private_key = OpenSSL::PKey::RSA.new(key, password)
+ #
+ # check authorization for this domain
+ #
+ log :checking, "authorization"
+ acme = Acme.new(domain: domain, key: account_key)
+ status, message = acme.authorize do |challenge|
+ log(:uploading, 'challenge to server %s' % domain) do
+ SSH.remote_command(nodes) do |ssh, host|
+ ssh.scripts.upload_acme_challenge(challenge.token, challenge.file_content)
+ end
+ end
+ log :waiting, "for letsencrypt.org to verify challenge"
+ end
+ if status == 'valid'
+ log 'authorized!', color: :green, style: :bold
+ elsif status == 'error'
+ bail! :error, message
+ elsif status == 'unauthorized'
+ bail!(:unauthorized, message, color: :yellow, style: :bold) do
+ log 'You must first run `leap cert register` to register the account key with letsencrypt.org'
+ end
end
- return cert
- end
-
- def ca_root_signing_profile
- {
- "extensions" => {
- "basicConstraints" => {"ca" => true},
- "keyUsage" => {
- "usage" => ["critical", "keyCertSign"]
- },
- "extendedKeyUsage" => {
- "usage" => []
- }
- }
- }
- end
- #
- # For keyusage, openvpn server certs can have keyEncipherment or keyAgreement.
- # Web browsers seem to break without keyEncipherment.
- # For now, I am using digitalSignature + keyEncipherment
- #
- # * digitalSignature -- for (EC)DHE cipher suites
- # "The digitalSignature bit is asserted when the subject public key is used
- # with a digital signature mechanism to support security services other
- # than certificate signing (bit 5), or CRL signing (bit 6). Digital
- # signature mechanisms are often used for entity authentication and data
- # origin authentication with integrity."
- #
- # * keyEncipherment ==> for plain RSA cipher suites
- # "The keyEncipherment bit is asserted when the subject public key is used for
- # key transport. For example, when an RSA key is to be used for key management,
- # then this bit is set."
- #
- # * keyAgreement ==> for used with DH, not RSA.
- # "The keyAgreement bit is asserted when the subject public key is used for key
- # agreement. For example, when a Diffie-Hellman key is to be used for key
- # management, then this bit is set."
- #
- # digest options: SHA512, SHA256, SHA1
- #
- def server_signing_profile(node)
- {
- "digest" => provider.ca.server_certificates.digest,
- "extensions" => {
- "keyUsage" => {
- "usage" => ["digitalSignature", "keyEncipherment"]
- },
- "extendedKeyUsage" => {
- "usage" => ["serverAuth", "clientAuth"]
- },
- "subjectAltName" => {
- "ips" => [node.ip_address],
- "dns_names" => dns_names_for_node(node)
- }
- }
- }
+ log :fetching, "new certificate from letsencrypt.org"
+ assert_no_errors!("could not renew certificate") do
+ cert = acme.get_certificate(csr)
+ end
+ log 'success', color: :green, style: :bold
+ write_file!([:commercial_cert, domain], cert.fullchain_to_pem)
+ log 'You should now run `leap deploy` to deploy the new certificate.'
end
#
- # This is used when signing the main cert for the provider's domain
- # with our own CA (for testing purposes). Typically, this cert would
- # be purchased from a commercial CA, and not signed this way.
+ # Returns a hash of nodes that match this domain. It also checks:
#
- def domain_test_signing_profile
- {
- "digest" => "SHA256",
- "extensions" => {
- "keyUsage" => {
- "usage" => ["digitalSignature", "keyEncipherment"]
- },
- "extendedKeyUsage" => {
- "usage" => ["serverAuth"]
- }
- }
- }
- end
-
+ # * a node configuration has this domain
+ # * the dns for the domain exists
#
- # This is used when signing a dummy client certificate that is only to be
- # used for testing.
+ # This method will bail if any checks fail.
#
- def client_test_signing_profile
- {
- "digest" => "SHA256",
- "extensions" => {
- "keyUsage" => {
- "usage" => ["digitalSignature"]
- },
- "extendedKeyUsage" => {
- "usage" => ["clientAuth"]
- }
- }
- }
- end
-
- def dns_names_for_node(node)
- names = [node.domain.internal, node.domain.full]
- if node['dns'] && node.dns['aliases'] && node.dns.aliases.any?
- names += node.dns.aliases
+ def nodes_for_domain(domain)
+ bail! { log 'Argument DOMAIN is required' } if domain.nil? || domain.empty?
+ nodes = manager.nodes['dns.aliases' => domain]
+ if nodes.empty?
+ bail! :error, "There are no nodes configured for domain `%s`" % domain
end
- names.compact!
- names.sort!
- names.uniq!
- return names
+ begin
+ ips = Socket.getaddrinfo(domain, 'http').map {|record| record[2]}.uniq
+ nodes = nodes['ip_address' => ips]
+ if nodes.empty?
+ bail! do
+ log :error, "The domain `%s` resolves to [%s]" % [domain, ips.join(', ')]
+ log :error, "But there no nodes configured for this domain with these adddresses."
+ end
+ end
+ rescue SocketError
+ bail! :error, "Could not resolve the DNS for `#{domain}`. Without a DNS " +
+ "entry for this domain, authorization will not work."
+ end
+ return nodes
end
#
- # For cert serial numbers, we need a non-colliding number less than 160 bits.
- # md5 will do nicely, since there is no need for a secure hash, just a short one.
- # (md5 is 128 bits)
+ # runs the following checks on the domain:
#
- def cert_serial_number(domain_name)
- Digest::MD5.hexdigest("#{domain_name} -- #{Time.now}").to_i(16)
- end
-
+ # * we are able to get /.well-known/acme-challenge/ok
#
- # for the random common name, we need a text string that will be unique across all certs.
- # ruby 1.8 doesn't have a built-in uuid generator, or we would use SecureRandom.uuid
+ # This method will bail if any checks fail.
#
- def random_common_name(domain_name)
- cert_serial_number(domain_name).to_s(36)
- end
-
- # prints CertificateAuthority::DistinguishedName fields
- def print_dn(dn)
- fields = {}
- [:common_name, :locality, :state, :country, :organization, :organizational_unit, :email_address].each do |attr|
- fields[attr] = dn.send(attr) if dn.send(attr)
- end
- fields.inspect
- end
-
- ##
- ## TIME HELPERS
- ##
- ## note: we use 'yesterday' instead of 'today', because times are in UTC, and some people on the planet
- ## are behind UTC.
- ##
-
- def yesterday
- t = Time.now - 24*24*60
- Time.utc t.year, t.month, t.day
- end
-
- def yesterday_advance(string)
- number, unit = string.split(' ')
- unless ['years', 'months', 'days', 'hours', 'minutes'].include? unit
- bail!("The time property '#{string}' is missing a unit (one of: years, months, days, hours, minutes).")
- end
- unless number.to_i.to_s == number
- bail!("The time property '#{string}' is missing a number.")
+ def domain_ready_for_acme!(domain)
+ begin
+ uri = URI("https://#{domain}/.well-known/acme-challenge/ok")
+ options = {
+ use_ssl: true,
+ open_timeout: 5,
+ verify_mode: OpenSSL::SSL::VERIFY_NONE
+ }
+ Net::HTTP.start(uri.host, uri.port, options) do |http|
+ http.request(Net::HTTP::Get.new(uri)) do |response|
+ if !response.is_a?(Net::HTTPSuccess)
+ bail!(:error, "Could not GET %s" % uri) do
+ log "%s %s" % [response.code, response.message]
+ log "You may need to run `leap deploy`"
+ end
+ end
+ end
+ end
+ rescue Errno::ETIMEDOUT, Net::OpenTimeout
+ bail! :error, "Connection attempt timed out: %s" % uri
+ rescue Interrupt
+ bail!
+ rescue StandardError => exc
+ bail!(:error, "Could not GET %s" % uri) do
+ log exc.to_s
+ end
end
- yesterday.advance(unit.to_sym => number.to_i)
end
end; end