mirror of
https://github.com/neocities/neocities.git
synced 2025-04-25 01:32:36 +02:00
Let's Encrypt script is getting ugly, but covers the edge cases now.
Needs refactor eventually.
This commit is contained in:
parent
713d057395
commit
54ff80ecd2
1 changed files with 101 additions and 26 deletions
|
@ -1,6 +1,7 @@
|
||||||
class LetsEncryptWorker
|
class LetsEncryptWorker
|
||||||
class NotAuthorizedYetError < StandardError; end
|
class NotAuthorizedYetError < StandardError; end
|
||||||
class VerificationTimeoutError < StandardError; end
|
class VerificationTimeoutError < StandardError; end
|
||||||
|
class InvalidAuthError < StandardError; end
|
||||||
class VerifyNotFoundWithDomain < StandardError; end
|
class VerifyNotFoundWithDomain < StandardError; end
|
||||||
include Sidekiq::Worker
|
include Sidekiq::Worker
|
||||||
sidekiq_options queue: :lets_encrypt_worker, retry: 5, backtrace: true
|
sidekiq_options queue: :lets_encrypt_worker, retry: 5, backtrace: true
|
||||||
|
@ -9,6 +10,9 @@ class LetsEncryptWorker
|
||||||
1.hour.to_i
|
1.hour.to_i
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# If you need to clear scheduled jobs:
|
||||||
|
# Sidekiq::ScheduledSet.new.select {|s| JSON.parse(s.value)['class'] == 'LetsEncryptWorker'}.length
|
||||||
|
|
||||||
def letsencrypt
|
def letsencrypt
|
||||||
Acme::Client.new(
|
Acme::Client.new(
|
||||||
private_key: OpenSSL::PKey::RSA.new(File.read($config['letsencrypt_key'])),
|
private_key: OpenSSL::PKey::RSA.new(File.read($config['letsencrypt_key'])),
|
||||||
|
@ -28,53 +32,124 @@ class LetsEncryptWorker
|
||||||
site = Site[site_id]
|
site = Site[site_id]
|
||||||
return if site.domain.blank? || site.is_deleted || site.is_banned
|
return if site.domain.blank? || site.is_deleted || site.is_banned
|
||||||
|
|
||||||
domains = [site.domain, "www.#{site.domain}"]
|
return if site.values[:domain].match /\.neocities\.org$/i
|
||||||
|
|
||||||
|
domain_raw = site.values[:domain].gsub(/^www\./, '')
|
||||||
|
|
||||||
|
domains = [domain_raw, "www.#{domain_raw}"]
|
||||||
|
|
||||||
|
verified_domains = []
|
||||||
|
|
||||||
domains.each_with_index do |domain, index|
|
domains.each_with_index do |domain, index|
|
||||||
auth = letsencrypt.authorize domain: domain
|
puts "verifying accessability of test file on #{domain}"
|
||||||
challenge = auth.http01
|
challenge_base_path = File.join '.well-known', 'acme-challenge'
|
||||||
|
testfile_name, testfile_key = "test#{UUIDTools::UUID.random_create}", SecureRandom.hex
|
||||||
|
testfile_fs_path = File.join site.base_files_path, challenge_base_path
|
||||||
|
|
||||||
FileUtils.mkdir_p File.join(site.base_files_path, File.dirname(challenge.filename)) if index == 0
|
begin
|
||||||
File.write File.join(site.base_files_path, challenge.filename), challenge.file_content
|
FileUtils.mkdir_p File.join(site.base_files_path, challenge_base_path)
|
||||||
|
File.write File.join(testfile_fs_path, testfile_name), testfile_key
|
||||||
|
rescue => e
|
||||||
|
puts "ERROR WRITING TO WELLKNOWN FILE, SKIPPING #{domain}: #{e.inspect}"
|
||||||
|
next
|
||||||
|
end
|
||||||
|
|
||||||
# Ensure that both domains work before sending request. Let's Encrypt has a low
|
# Ensure that both domains work before sending request. Let's Encrypt has a low
|
||||||
# pending request limit, and it takes one week (!) to flush out.
|
# pending request limit, and it takes one week (!) to flush out.
|
||||||
sleep 2
|
|
||||||
challenge_url = "#{domain}/#{challenge.filename}"
|
challenge_url = "http://#{domain}/#{challenge_base_path}/#{testfile_name}"
|
||||||
["http://#{challenge_url}", "http://www.#{challenge_url}"].each do |url|
|
|
||||||
res = HTTP.follow.get(url)
|
puts "testing #{challenge_url}"
|
||||||
raise VerifyNotFoundWithDomain unless res.status == 200
|
|
||||||
|
begin
|
||||||
|
res = HTTP.timeout(:global, write: 5, connect: 10, read: 10).follow.get(challenge_url)
|
||||||
|
rescue
|
||||||
|
puts "error with #{challenge_url}"
|
||||||
|
next
|
||||||
|
end
|
||||||
|
|
||||||
|
if res.status != 200 && res.body != testfile_key
|
||||||
|
puts "CONTENT DOWNLOADED DID NOT MATCH #{challenge_url}"
|
||||||
|
next
|
||||||
|
end
|
||||||
|
|
||||||
|
puts "test succeeded, sending challenge request verification"
|
||||||
|
|
||||||
|
begin
|
||||||
|
auth = letsencrypt.authorize domain: domain
|
||||||
|
challenge = auth.http01
|
||||||
|
rescue Acme::Client::Error::Malformed
|
||||||
|
puts "international domains not supported yet, quitting"
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
begin
|
||||||
|
FileUtils.mkdir_p File.join(site.base_files_path, File.dirname(challenge.filename))
|
||||||
|
File.write File.join(site.base_files_path, challenge.filename), challenge.file_content
|
||||||
|
rescue => e
|
||||||
|
put "FAILED TO WRITE CHALLENGE: #{site.domain} #{challenge.filename}"
|
||||||
|
# A verification needs to be made anyways, otherwise 300 of them will jam up the system for a week
|
||||||
end
|
end
|
||||||
|
|
||||||
challenge.request_verification
|
challenge.request_verification
|
||||||
|
|
||||||
sleep 60
|
sleep 1
|
||||||
attempts = 0
|
attempts = 0
|
||||||
|
|
||||||
begin
|
while true
|
||||||
puts "WAITING FOR #{domain} VALIDATION"
|
result = challenge.verify_status
|
||||||
|
puts "#{domain} : #{result}"
|
||||||
|
|
||||||
|
if result == 'valid'
|
||||||
|
puts "VALIDATED: #{domain}"
|
||||||
|
clean_wellknown_turds site
|
||||||
|
verified_domains.push domain
|
||||||
|
break
|
||||||
|
end
|
||||||
|
|
||||||
raise VerificationTimeoutError if attempts == 60
|
raise VerificationTimeoutError if attempts == 60
|
||||||
raise NotAuthorizedYetError if challenge.verify_status != 'valid'
|
|
||||||
rescue NotAuthorizedYetError
|
if result == 'invalid'
|
||||||
sleep 20
|
puts "returned invalid, walking away"
|
||||||
attempts += 1
|
|
||||||
retry
|
|
||||||
ensure
|
|
||||||
clean_wellknown_turds site
|
clean_wellknown_turds site
|
||||||
end
|
break
|
||||||
puts "DONE!"
|
|
||||||
end
|
end
|
||||||
|
|
||||||
csr = Acme::Client::CertificateRequest.new names: domains
|
attempts += 1
|
||||||
|
sleep 2
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if verified_domains.empty?
|
||||||
|
puts "no verified domains, skipping"
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
puts "verified domains: #{verified_domains.inspect}"
|
||||||
|
|
||||||
|
clean_wellknown_turds site
|
||||||
|
|
||||||
|
retries = 0
|
||||||
|
begin
|
||||||
|
csr = Acme::Client::CertificateRequest.new names: verified_domains
|
||||||
certificate = letsencrypt.new_certificate csr
|
certificate = letsencrypt.new_certificate csr
|
||||||
|
rescue Acme::Client::Error => e
|
||||||
|
if retries == 2
|
||||||
|
puts "Failed to create cert, returning: #{e.message}"
|
||||||
|
return
|
||||||
|
end
|
||||||
|
retries += 1
|
||||||
|
retry
|
||||||
|
end
|
||||||
|
|
||||||
site.ssl_key = certificate.request.private_key.to_pem
|
site.ssl_key = certificate.request.private_key.to_pem
|
||||||
site.ssl_cert = certificate.fullchain_to_pem
|
site.ssl_cert = certificate.fullchain_to_pem
|
||||||
|
site.cert_updated_at = Time.now
|
||||||
site.save_changes validate: false
|
site.save_changes validate: false
|
||||||
clean_wellknown_turds site
|
|
||||||
|
|
||||||
# Refresh the cert periodically, current expire time is 90 days
|
# Refresh the cert periodically, current expire time is 90 days
|
||||||
LetsEncryptWorker.perform_in 60.days, site.id
|
# We're going for a cron task for this now, so this is commented out.
|
||||||
|
#LetsEncryptWorker.perform_in 60.days, site.id
|
||||||
end
|
end
|
||||||
|
|
||||||
def clean_wellknown_turds(site)
|
def clean_wellknown_turds(site)
|
||||||
|
|
Loading…
Add table
Reference in a new issue