Class: AcmeNsupdate::Client
- Inherits:
-
Object
- Object
- AcmeNsupdate::Client
- Defined in:
- lib/acme_nsupdate/client.rb
Defined Under Namespace
Classes: DebuggableClient, Error
Constant Summary collapse
- RENEWAL_THRESHOLD =
30*24*60*60, 30 days
2_592_000
Instance Attribute Summary collapse
-
#logger ⇒ Object
readonly
Returns the value of attribute logger.
-
#options ⇒ Object
readonly
Returns the value of attribute options.
Instance Method Summary collapse
- #account_key ⇒ Object
- #account_key_path ⇒ Object
- #archive_path ⇒ Object
- #build_nsupdate ⇒ Object
- #client ⇒ Object
- #csr ⇒ Object
- #datadir ⇒ Object
- #fetch_certificate(order) ⇒ Object
-
#initialize(options) ⇒ Client
constructor
A new instance of Client.
- #live_path ⇒ Object
- #outdated_certificates ⇒ Object
- #private_key ⇒ Object
- #private_key_path ⇒ Object
- #publish_tlsa_records(certificate_pem) ⇒ Object
- #read_or_create_key(path) ⇒ Object
- #register_account ⇒ Object
- #renewal_needed? ⇒ Boolean
- #run ⇒ Object
- #write_files(path, certificate, key) ⇒ Object
Constructor Details
#initialize(options) ⇒ Client
Returns a new instance of Client.
33 34 35 36 37 38 39 40 |
# File 'lib/acme_nsupdate/client.rb', line 33 def initialize @options = @logger = Logger.new(STDOUT) @logger.level = Logger::INFO @logger.level = Logger::FATAL if @options[:quiet] @logger.level = Logger::DEBUG if @options[:verbose] @verification_strategy = Strategy.for(@options[:challenge]).new(self) end |
Instance Attribute Details
#logger ⇒ Object (readonly)
Returns the value of attribute logger.
31 32 33 |
# File 'lib/acme_nsupdate/client.rb', line 31 def logger @logger end |
#options ⇒ Object (readonly)
Returns the value of attribute options.
31 32 33 |
# File 'lib/acme_nsupdate/client.rb', line 31 def @options end |
Instance Method Details
#account_key ⇒ Object
81 82 83 |
# File 'lib/acme_nsupdate/client.rb', line 81 def account_key @account_key ||= read_or_create_key account_key_path end |
#account_key_path ⇒ Object
73 74 75 |
# File 'lib/acme_nsupdate/client.rb', line 73 def account_key_path @account_key_path ||= datadir.join ".#{@options[:contact]}.pem" end |
#archive_path ⇒ Object
135 136 137 138 139 140 |
# File 'lib/acme_nsupdate/client.rb', line 135 def archive_path @archive_path ||= datadir.join("archive") .join(Time.now.strftime("%Y%m%d%H%M%S")) .join(@options[:domains].first) .tap(&:mkpath) end |
#build_nsupdate ⇒ Object
101 102 103 104 105 106 |
# File 'lib/acme_nsupdate/client.rb', line 101 def build_nsupdate Nsupdate.new(logger).tap do |nsupdate| nsupdate.server @options[:master] if @options[:master] nsupdate.tsig(*@options[:tsig].split(":")) if @options[:tsig] end end |
#client ⇒ Object
67 68 69 70 71 |
# File 'lib/acme_nsupdate/client.rb', line 67 def client @client ||= DebuggableClient.new(private_key: account_key, directory: @options[:endpoint]).tap do |client| client.logger = @logger if @options[:verbose] end end |
#csr ⇒ Object
118 119 120 121 |
# File 'lib/acme_nsupdate/client.rb', line 118 def csr logger.debug "Generating CSR" Acme::Client::CertificateRequest.new(names: @options[:domains], private_key: private_key) end |
#datadir ⇒ Object
77 78 79 |
# File 'lib/acme_nsupdate/client.rb', line 77 def datadir @datadir ||= Pathname.new(@options[:datadir]).tap(&:mkpath) end |
#fetch_certificate(order) ⇒ Object
108 109 110 111 112 113 114 115 116 |
# File 'lib/acme_nsupdate/client.rb', line 108 def fetch_certificate order order.finalize csr: csr while order.status == 'processing' sleep 3 order.reload end raise "Failed to fetch certificate, order failed." unless order.status == 'valid' order.certificate end |
#live_path ⇒ Object
131 132 133 |
# File 'lib/acme_nsupdate/client.rb', line 131 def live_path @live_path ||= datadir.join("live").join(@options[:domains].first).tap(&:mkpath) end |
#outdated_certificates ⇒ Object
194 195 196 197 198 199 200 201 202 203 204 |
# File 'lib/acme_nsupdate/client.rb', line 194 def outdated_certificates domain = @options[:domains].first @outdated_certificates ||= datadir .join("archive") .children .select {|dir| dir.join(domain, "fullchain.pem").exist? } .sort_by(&:basename) .map {|path| OpenSSL::X509::Certificate.new path.join(domain, "fullchain.pem").read } .tap(&:pop) # keep current .tap(&:pop) # keep previous end |
#private_key ⇒ Object
123 124 125 |
# File 'lib/acme_nsupdate/client.rb', line 123 def private_key @private_key ||= read_or_create_key private_key_path end |
#private_key_path ⇒ Object
127 128 129 |
# File 'lib/acme_nsupdate/client.rb', line 127 def private_key_path @private_key_path ||= live_path.join("privkey.pem") end |
#publish_tlsa_records(certificate_pem) ⇒ Object
151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 |
# File 'lib/acme_nsupdate/client.rb', line 151 def publish_tlsa_records certificate_pem return if @options[:notlsa] certificate = OpenSSL::X509::Certificate.new certificate_pem logger.info "Publishing TLSA records" old_contents = outdated_certificates.map {|certificate| "3 1 1 #{OpenSSL::Digest::SHA256.hexdigest(certificate.public_key.to_der)}" }.uniq content = "3 1 1 #{OpenSSL::Digest::SHA256.hexdigest(certificate.public_key.to_der)}" old_contents.delete(content) @options[:domains].each do |domain| nsupdate = build_nsupdate @options[:tlsaports].each do |port| restriction, port = port.split(":") restriction, port = port, restriction unless port label = "_#{port}._tcp.#{domain}" if restriction restrictions = restriction.delete("[]").split(" ") unless restrictions.include? domain logger.debug "Not publishing TLSA record for #{label}, not one of #{restrictions.join(" ")}" next end end old_contents.each do |old_content| nsupdate.del label, "TLSA", old_content unless @options[:keep] end nsupdate.del label, "TLSA", content nsupdate.add label, "TLSA", content, @options[:tlsa_ttl] end begin nsupdate.send rescue Nsupdate::Error # Continue trying other zones, errors logged in Nsupdate end end end |
#read_or_create_key(path) ⇒ Object
85 86 87 88 89 |
# File 'lib/acme_nsupdate/client.rb', line 85 def read_or_create_key path logger.debug "Creating or reading #{path}" path.write OpenSSL::PKey::RSA.new @options[:keylength] unless path.exist? OpenSSL::PKey::RSA.new path.read end |
#register_account ⇒ Object
60 61 62 63 64 65 |
# File 'lib/acme_nsupdate/client.rb', line 60 def register_account return if account_key_path.exist? logger.debug "No key found at #{account_key_path}, registering" client.new_account contact: "mailto:#{@options[:contact]}", terms_of_service_agreed: true end |
#renewal_needed? ⇒ Boolean
91 92 93 94 95 96 97 98 99 |
# File 'lib/acme_nsupdate/client.rb', line 91 def renewal_needed? return true if @options[:force] cert_path = live_path.join("fullchain.pem") return true unless cert_path.exist? cert = OpenSSL::X509::Certificate.new(cert_path.read) (cert.not_after - Time.now) <= RENEWAL_THRESHOLD end |
#run ⇒ Object
42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
# File 'lib/acme_nsupdate/client.rb', line 42 def run unless renewal_needed? logger.info "Existing certificate is still valid long enough." return end register_account order, challenges = @verification_strategy.verify_domains logger.info "Requesting certificate" certificate = fetch_certificate order write_files live_path, certificate, private_key write_files archive_path, certificate, private_key @verification_strategy.cleanup challenges unless @options[:keep] publish_tlsa_records certificate rescue Nsupdate::Error abort "nsupdate failed." # detail logged in Nsupdate end |
#write_files(path, certificate, key) ⇒ Object
142 143 144 145 146 147 148 149 |
# File 'lib/acme_nsupdate/client.rb', line 142 def write_files path, certificate, key logger.info "Writing files to #{path}" logger.debug "Writing #{path.join("key.pem")}" path.join("privkey.pem").write key.to_pem path.join("privkey.pem").chmod(0600) logger.debug "Writing #{path.join("fullchain.pem")}" path.join("fullchain.pem").write certificate end |