class LECLI::CertificateBuilder
Helper class to generate certs and access the default options
Constants
- YAML_FILENAME
Attributes
production[RW]
Public Class Methods
load_options(config_file:)
click to toggle source
# File lib/lecli/certificate_builder.rb, line 49 def self.load_options(config_file:) opts = LECLI::CertificateBuilder.runtime_defaults opts.merge!(YAML.load_file(config_file)) if File.file?(config_file) required_options = LECLI::CertificateBuilder.required_options # Should return nil if all required options are not present opts if (opts.keys & required_options).count == required_options.count end
new() { |self| ... }
click to toggle source
# File lib/lecli/certificate_builder.rb, line 13 def initialize @challenges = [] @production = false # Pass a block to edit the new object for prod/staging or other options yield self if block_given? prod_url = 'https://acme-v02.api.letsencrypt.org/directory' staging_url = 'https://acme-staging-v02.api.letsencrypt.org/directory' @endpoint = @production ? prod_url : staging_url end
persist_defaults_file(override:)
click to toggle source
# File lib/lecli/certificate_builder.rb, line 58 def self.persist_defaults_file(override:) opts = LECLI::CertificateBuilder.sample_options if !File.file?(YAML_FILENAME) || override File.write(YAML_FILENAME, opts.to_yaml) puts YAML_FILENAME else puts "#{YAML_FILENAME} already exists. Try `lecli help yaml`" end end
required_options()
click to toggle source
# File lib/lecli/certificate_builder.rb, line 25 def self.required_options ['domains', 'common_name', 'account_email'] end
runtime_defaults()
click to toggle source
# File lib/lecli/certificate_builder.rb, line 41 def self.runtime_defaults { 'request_key' => 'request.pem', 'certificate_key' => 'certificate.pem', 'challenges_relative_path' => 'challenges' } end
sample_options()
click to toggle source
# File lib/lecli/certificate_builder.rb, line 29 def self.sample_options { 'domains' => ['example.com', 'test.net'], 'common_name' => 'example.com', 'account_email' => 'test@account.com', 'request_key' => 'request.pem', 'certificate_key' => 'certificate.pem', 'challenges_relative_path' => 'challenges', 'success_callback_script' => 'deploy.sh' } end
Public Instance Methods
generate_certs(options)
click to toggle source
# File lib/lecli/certificate_builder.rb, line 68 def generate_certs(options) success = true begin request_challenges(options: options) sleep(3) # We are unaware of challenge hosting, better give extra time request_challenge_validation request_key = finalize_order( domains: options['domains'], title: options['common_name'] ) write_certificate( cert: @order.certificate, relative_path: options['certificate_key'] ) write_certificate( cert: request_key, relative_path: options['request_key'] ) rescue Acme::Client::Error::RateLimited => e puts e.message success = false end success end
Private Instance Methods
create_order(email:, domains:)
click to toggle source
# File lib/lecli/certificate_builder.rb, line 120 def create_order(email:, domains:) pkey = OpenSSL::PKey::RSA.new(4096) client = Acme::Client.new(private_key: pkey, directory: @endpoint) client.new_account( contact: "mailto:#{email}", terms_of_service_agreed: true ) @order = client.new_order(identifiers: domains) end
finalize_order(domains:, title:)
click to toggle source
# File lib/lecli/certificate_builder.rb, line 108 def finalize_order(domains:, title:) request_key = OpenSSL::PKey::RSA.new(4096) csr = Acme::Client::CertificateRequest.new( private_key: request_key, names: domains, subject: { common_name: title } ) @order.finalize(csr: csr) sleep(1) while @order.status == 'processing' request_key end
persist_challenge_tokens()
click to toggle source
# File lib/lecli/certificate_builder.rb, line 160 def persist_challenge_tokens @order.authorizations.each do |authorization| challenge = authorization.http token_path = File.join(@challenges_dir, challenge.token) File.write(token_path, challenge.file_content) @challenges << challenge end end
request_challenge_validation()
click to toggle source
# File lib/lecli/certificate_builder.rb, line 136 def request_challenge_validation puts 'Requesting challenge validation and polling for confirmation...' wait_time = 5 pending = true while pending @challenges.each do |challenge| begin challenge.request_validation rescue Acme::Client::Error::Malformed print '.' end end status = @challenges.map(&:status) pending = status.include?('pending') next unless pending puts "At least one challenge still pending, waiting #{wait_time}s ..." sleep(wait_time) wait_time *= 2 if wait_time < 640 # Gradually increment retry max ~10min end puts 'Challenges are all valid now!' end
request_challenges(options:)
click to toggle source
# File lib/lecli/certificate_builder.rb, line 97 def request_challenges(options:) create_order(email: options['account_email'], domains: options['domains']) setup_challenges_dir(relative_path: options['challenges_relative_path']) persist_challenge_tokens end
setup_challenges_dir(relative_path:)
click to toggle source
# File lib/lecli/certificate_builder.rb, line 130 def setup_challenges_dir(relative_path:) @challenges_dir = File.expand_path(relative_path) FileUtils.mkdir_p(@challenges_dir) FileUtils.rm(Dir[File.join(@challenges_dir, '*')]) end
write_certificate(cert:, relative_path:)
click to toggle source
# File lib/lecli/certificate_builder.rb, line 103 def write_certificate(cert:, relative_path:) full_path = File.expand_path(relative_path) File.write(full_path, cert) end