require 'openssl' require 'letsencrypt/cli/acme_wrapper' require 'colorize'
namespace :lets_encrypt do
desc 'Register Let\' Encrypt account with email' task :register do email = fetch(:lets_encrypt_email) if email.nil? || email == "" wrapper.log "no E-Mail specified!", :fatal exit 1 end if !email[/.*@.*/] wrapper.log "not an email", :fatal exit 1 end registration = client.register(contact: "mailto:" + email) registration.agree_terms wrapper.log "Account created, Terms accepted" end desc 'Check if the certificate are valid' task :check_certificate do on roles(fetch(:lets_encrypt_roles)) do check_certificate end end desc 'Authorize the domain using the ACME challenge' task :authorize do on roles(fetch(:lets_encrypt_roles), select: :primary) do unless check_certificate domains.each do |domain| authorize(domain) end end end end desc "create certificate and private key pair for domains. The first domain is the main CN domain" task :cert do cert(domains) on roles(fetch(:lets_encrypt_roles)) do domains.each do |domain| upload_certs domain end end end # On server methods def check_certificate if test("[ -f #{certificate_path} ]") temp_path = "/tmp/#{primary_domain}.cert.pem" download! certificate_path, temp_path wrapper.check_certificate(temp_path) else wrapper.log "No certificate found" false end end def authorize(domain) as_encrypt_user do wrapper.log "Authorizing #{domain.blue}." authorization = client.authorize(domain: domain) challenge = authorization.http01 challenge_public_path = fetch(:lets_encrypt_challenge_public_path) challenge_path = File.join(challenge_public_path, File.dirname(challenge.filename)) challenge_file_path = File.join(challenge_public_path, challenge.filename) execute :mkdir, '-pv', challenge_path wrapper.log "Writing challenge to #{challenge_file_path}", :debug execute :echo, "\"#{challenge.file_content}\" > #{challenge_file_path}" challenge.request_verification 5.times do wrapper.log "Checking verification...", :debug sleep 1 break if challenge.verify_status != 'pending' end if challenge.verify_status == 'valid' wrapper.log "Authorization successful for #{domain.green}" execute :rm, '-f', challenge_file_path true else wrapper.log "Authorization error for #{domain.red}", :error wrapper.log challenge.error['detail'] false end end end def cert(domains) domains.each do |domain| FileUtils.mkdir_p(local_out_path(domain)) end wrapper.cert(domains) end def upload_certs(domain) as_encrypt_user do execute :mkdir, '-pv', "#{fetch(:lets_encrypt_output_path)}/#{domain}" safe_upload! local_private_key_path, private_key_path safe_upload! local_fullchain_path, fullchain_path safe_upload! local_certificate_path, certificate_path safe_upload! local_chain_path, chain_path end end def as_encrypt_user(&block) if fetch(:lets_encrypt_user) as fetch(:lets_encrypt_user) do yield end else yield end end def safe_upload!(from, to) tempname = "/tmp/#{Time.now.to_f}" upload! from, tempname sudo :mv, tempname, to end # Helpers def certificate_path(domain = primary_domain) File.join(fetch(:lets_encrypt_output_path), domain, "cert.pem") end def chain_path(domain = primary_domain) File.join(fetch(:lets_encrypt_output_path), domain, "chain.pem") end def fullchain_path(domain = primary_domain) File.join(fetch(:lets_encrypt_output_path), domain, "fullchain.pem") end def private_key_path(domain = primary_domain) File.join(fetch(:lets_encrypt_output_path), domain, "key.pem") end def local_certificate_path(domain = primary_domain) File.join(local_out_path(domain), "cert.pem") end def local_chain_path(domain = primary_domain) File.join(local_out_path(domain), "chain.pem") end def local_fullchain_path(domain = primary_domain) File.join(local_out_path(domain), "fullchain.pem") end def local_private_key_path(domain = primary_domain) File.join(local_out_path(domain), "key.pem") end def local_out_path(domain = primary_domain) File.join(File.expand_path(fetch(:lets_encrypt_local_output_path)), domain) end def domains fetch(:lets_encrypt_domains).split(" ") end def primary_domain domains.first end def wrapper @wrapper ||= AcmeWrapper.new(options) end def options @options ||= { account_key: File.expand_path(fetch(:lets_encrypt_account_key)), test: fetch(:lets_encrypt_test), log_level: "info", color: true, days_valid: fetch(:lets_encrypt_days_valid), private_key_path: local_private_key_path, fullchain_path: local_fullchain_path, certificate_path: local_certificate_path, chain_path: local_chain_path, } end def client @client ||= wrapper.client end
end
namespace :load do
task :defaults do set :lets_encrypt_roles, -> { :web } set :lets_encrypt_test, -> { false } set :lets_encrypt_email, -> { nil } set :lets_encrypt_domains, -> { nil } set :lets_encrypt_challenge_public_path, -> { "#{release_path}/public" } set :lets_encrypt_output_path, -> { "#{shared_path}/ssl/certs" } set :lets_encrypt_account_key, -> { "#{fetch(:lets_encrypt_email)}.account_key.pem" } set :lets_encrypt_days_valid, -> { 30 } set :lets_encrypt_local_output_path, -> { "~/certs" } end
end