rails-letsencrypt 0.12.0 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3e68d1fe64e1617ce8806c9b5b37ec2262fc0364d6760bdd7102e209064cdc72
4
- data.tar.gz: d0a907c1d281e450c648793373d1b52b4dba5a0cc04fb4e7158335a9d39c6d04
3
+ metadata.gz: 0bcd52d0b1c063971fe7db79ea96162cdb3895bc8992b190de25773a5d6f3a59
4
+ data.tar.gz: 66ef7517093ba680cd0bdffbdf7dfc2e21f4822a29d41c9d3ba171e2f3cb4299
5
5
  SHA512:
6
- metadata.gz: c60b81e40b6ac0fbbf2f984131ee7552f8130a7ecff3da572940b85ffba71068222fcb1f861d45590aeff40519f4afcf44a9e28cd63eff6c7feb178abaaf490a
7
- data.tar.gz: 777b086600489784480ceaa8c96ed86ce45f8560724eb7b0c097071fd5518bd5dea979c429511be83e00b6fb8e897efabbfa301fae5c3829463b48e3579fe706
6
+ metadata.gz: 643bfad8030d1a7962e49ae7a85b354c3c7e13737d3e2e2ce5b9657c0964e20f3faf867cd51b23a2a695d0e1e30e66d7d54a3e125d135b65f2527447fc2d14f9
7
+ data.tar.gz: c37a4c9465a91717846aa952510fd7e37ab006732bb35076c50d3057b7ae28aadd70f0acc12a9c7e9d396a4bafd831762d77e06bb03d74ce22cfccf31fda086b
data/README.md CHANGED
@@ -45,9 +45,13 @@ Add a file to `config/initializers/letsencrypt.rb` and put below config you need
45
45
 
46
46
  ```ruby
47
47
  LetsEncrypt.config do |config|
48
+ # Configure the ACME server
49
+ # Default is Let's Encrypt production server
50
+ # config.acme_server = 'https://acme-v02.api.letsencrypt.org/directory'
51
+
48
52
  # Using Let's Encrypt staging server or not
49
53
  # Default only `Rails.env.production? == true` will use Let's Encrypt production server.
50
- config.use_staging = true
54
+ # config.use_staging = true
51
55
 
52
56
  # Set the private key path
53
57
  # Default is locate at config/letsencrypt.key
@@ -70,13 +74,56 @@ LetsEncrypt.config do |config|
70
74
  # Enable it if you want to customize the model
71
75
  # Default is LetsEncrypt::Certificate
72
76
  # config.certificate_model = 'MyCertificate'
77
+
78
+ # Configure the maximum attempts to re-check status when verifying or issuing
79
+ # config.max_attempts = 30
80
+
81
+ # Configure the interval between attempts
82
+ # config.retry_interval = 1
73
83
  end
74
84
  ```
75
85
 
86
+ > [!WARNING]
87
+ > **Depcrecation Notice**
88
+ > The `use_staging` will be removed in the future, and the `acme_server` will be used to determine the server.
89
+
76
90
  ## Usage
77
91
 
78
92
  The SSL certificate setup depends on the web server, this gem can work with `ngx_mruby` or `kong`.
79
93
 
94
+ ### Service
95
+
96
+ #### Renew Service
97
+
98
+ ```ruby
99
+ certificate = LetsEncrypt::Certificate.find_by(domain: 'example.com')
100
+
101
+ service = LetsEncrypt::RenewService.new
102
+ service.execute(certificate)
103
+ ```
104
+
105
+ #### Verify Service
106
+
107
+ ```ruby
108
+ certificate = LetsEncrypt::Certificate.find_by(domain: 'example.com')
109
+
110
+ order = LetsEncrypt.client.new_order(identifiers: [certificate.domain])
111
+
112
+ service = LetsEncrypt::VerifyService.new
113
+ service.execute(certificate, order)
114
+ ```
115
+
116
+ #### Issue Service
117
+
118
+ ```ruby
119
+ certificate = LetsEncrypt::Certificate.find_by(domain: 'example.com')
120
+
121
+ order = LetsEncrypt.client.new_order(identifiers: [certificate.domain])
122
+
123
+ service = LetsEncrypt::IssueService.new
124
+ service.execute(certificate, order)
125
+ ```
126
+
80
127
  ### Certificate Model
81
128
 
82
129
  #### Create
@@ -88,6 +135,10 @@ cert = LetsEncrypt::Certificate.create(domain: 'example.com')
88
135
  cert.get # alias `verify && issue`
89
136
  ```
90
137
 
138
+ > [!WARNING]
139
+ > **Depcrecation Notice**
140
+ > The `get` will be replaced by `RenewService` in the future.
141
+
91
142
  #### Verify
92
143
 
93
144
  Makes a request to Let's Encrypt and verify domain
@@ -97,6 +148,10 @@ cert = LetsEncrypt::Certificate.find_by(domain: 'example.com')
97
148
  cert.verify
98
149
  ```
99
150
 
151
+ > [!WARNING]
152
+ > **Depcrecation Notice**
153
+ > The `verify` will be replaced by `VerifyService` in the future.
154
+
100
155
  #### Issue
101
156
 
102
157
  Ask Let's Encrypt to issue a new certificate.
@@ -106,6 +161,10 @@ cert = LetsEncrypt::Certificate.find_by(domain: 'example.com')
106
161
  cert.issue
107
162
  ```
108
163
 
164
+ > [!WARNING]
165
+ > **Depcrecation Notice**
166
+ > The `issue` will be replaced by `IssueService` in the future.
167
+
109
168
  #### Renew
110
169
 
111
170
  ```ruby
@@ -113,6 +172,10 @@ cert = LetsEncrypt::Certificate.find_by(domain: 'example.com')
113
172
  cert.renew
114
173
  ```
115
174
 
175
+ > [!WARNING]
176
+ > **Depcrecation Notice**
177
+ > The `renew` will be replaced by `RenewService` in the future.
178
+
116
179
  #### Status
117
180
 
118
181
  Check a certificate is verified and issued.
@@ -141,7 +204,7 @@ rake letsencrypt:renew
141
204
 
142
205
  If you are using Sidekiq or others, you can enqueue renew task daily.
143
206
 
144
- ```
207
+ ```ruby
145
208
  LetsEncrypt::RenewCertificatesJob.perform_later
146
209
  ```
147
210
 
@@ -150,11 +213,17 @@ LetsEncrypt::RenewCertificatesJob.perform_later
150
213
  When the certificate is trying to issue a new one, you can subscribe it for logging or error handling.
151
214
 
152
215
  ```ruby
153
- ActiveSupport::Notifications.subscribe('letsencrypt.issue') do |name, start, finish, id, payload|
154
- Rails.logger.info("Certificate for #{payload[:domain]} is issued")
216
+ ActiveSupport::Notifications.subscribe('letsencrypt.renew') do |name, start, finish, id, payload|
217
+ Rails.logger.info("Certificate for #{payload[:domain]} is renewed")
155
218
  end
156
219
  ```
157
220
 
221
+ The available events are:
222
+
223
+ * `letsencrypt.renew`
224
+ * `letsencrypt.verify`
225
+ * `letsencrypt.issue`
226
+
158
227
  ### ngx_mruby
159
228
 
160
229
  The setup is following this [Article](http://hb.matsumoto-r.jp/entry/2017/03/23/173236)
@@ -6,10 +6,15 @@ module LetsEncrypt
6
6
  queue_as :default
7
7
 
8
8
  def perform
9
- LetsEncrypt.certificate_model.renewable.each do |certificate|
10
- next if certificate.renew
9
+ service = LetsEncrypt::RenewService.new
11
10
 
11
+ LetsEncrypt.certificate_model.renewable.each do |certificate|
12
+ service.execute(certificate)
13
+ rescue LetsEncrypt::MaxCheckExceeded, LetsEncrypt::InvalidStatus
14
+ certificate.update(renew_after: 1.day.from_now)
15
+ rescue Acme::Client::Error => e
12
16
  certificate.update(renew_after: 1.day.from_now)
17
+ Rails.logger.error("LetsEncrypt::RenewCertificatesJob: #{e.message}")
13
18
  end
14
19
  end
15
20
  end
@@ -23,9 +23,6 @@ module LetsEncrypt
23
23
  # index_letsencrypt_certificates_on_renew_after (renew_after)
24
24
  #
25
25
  class Certificate < ApplicationRecord
26
- include CertificateVerifiable
27
- include CertificateIssuable
28
-
29
26
  self.table_name = 'letsencrypt_certificates'
30
27
 
31
28
  validates :domain, presence: true, uniqueness: true
@@ -51,15 +48,6 @@ module LetsEncrypt
51
48
  Time.zone.now >= expires_at
52
49
  end
53
50
 
54
- # Returns true if success get a new certificate
55
- def get
56
- ActiveSupport::Notifications.instrument('letsencrypt.issue', domain:) do
57
- verify && issue
58
- end
59
- end
60
-
61
- alias renew get
62
-
63
51
  # Returns full-chain bundled certificates
64
52
  def bundle
65
53
  (certificate || '') + (intermediaries || '')
@@ -73,6 +61,22 @@ module LetsEncrypt
73
61
  @key_object ||= OpenSSL::PKey::RSA.new(key)
74
62
  end
75
63
 
64
+ def challenge!(filename, file_content)
65
+ update!(
66
+ verification_path: filename,
67
+ verification_string: file_content
68
+ )
69
+ end
70
+
71
+ def refresh!(cert, fullchain)
72
+ update!(
73
+ certificate: cert.to_pem,
74
+ intermediaries: fullchain.join("\n\n"),
75
+ expires_at: cert.not_after,
76
+ renew_after: (cert.not_after - 1.month) + rand(10).days
77
+ )
78
+ end
79
+
76
80
  # Save certificate into redis
77
81
  def save_to_redis
78
82
  LetsEncrypt::Redis.save(self)
@@ -83,12 +87,47 @@ module LetsEncrypt
83
87
  LetsEncrypt::Redis.delete(self)
84
88
  end
85
89
 
86
- protected
90
+ # Returns true if success get a new certificate
91
+ def get
92
+ logger.info "Getting certificate for #{domain}"
93
+ service = LetsEncrypt::RenewService.new
94
+ service.execute(self)
95
+ logger.info "Certificate issued for #{domain} " \
96
+ "(expires on #{expires_at}, will renew after #{renew_after})"
97
+
98
+ true
99
+ rescue LetsEncrypt::MaxCheckExceeded, LetsEncrypt::InvalidStatus => e
100
+ logger.error "#{domain}: #{e.message}"
101
+ false
102
+ end
103
+
104
+ alias renew get
87
105
 
88
- def logger
89
- LetsEncrypt.logger
106
+ def verify
107
+ service = LetsEncrypt::VerifyService.new
108
+ service.execute(self, order)
109
+
110
+ true
111
+ rescue LetsEncrypt::MaxCheckExceeded, LetsEncrypt::InvalidStatus => e
112
+ logger.error "#{domain}: #{e.message}"
113
+ false
114
+ end
115
+
116
+ def issue
117
+ logger.info "Getting certificate for #{domain}"
118
+ service = LetsEncrypt::IssueService.new
119
+ service.execute(self, order)
120
+ logger.info "Certificate issued for #{domain} " \
121
+ "(expires on #{expires_at}, will renew after #{renew_after})"
122
+
123
+ true
124
+ rescue LetsEncrypt::MaxCheckExceeded, LetsEncrypt::InvalidStatus => e
125
+ logger.error "#{domain}: #{e.message}"
126
+ false
90
127
  end
91
128
 
129
+ protected
130
+
92
131
  def order
93
132
  @order ||= LetsEncrypt.client.new_order(identifiers: [domain])
94
133
  end
@@ -1,7 +1,11 @@
1
1
  LetsEncrypt.config do |config|
2
+ # Configure the ACME server
3
+ # Default is Let's Encrypt production server
4
+ # config.acme_server = 'https://acme-v02.api.letsencrypt.org/directory'
5
+
2
6
  # Using Let's Encrypt staging server or not
3
7
  # Default only `Rails.env.production? == true` will use Let's Encrypt production server.
4
- config.use_staging = true
8
+ # config.use_staging = true
5
9
 
6
10
  # Set the private key path
7
11
  # Default is locate at config/letsencrypt.key
@@ -24,5 +28,11 @@ LetsEncrypt.config do |config|
24
28
  # Enable it if you want to customize the model
25
29
  # Default is LetsEncrypt::Certificate
26
30
  # config.certificate_model = 'MyCertificate'
31
+
32
+ # Configure the maximum attempts to re-check status when verifying or issuing
33
+ # config.max_attempts = 30
34
+
35
+ # Configure the interval between attempts
36
+ # config.retry_interval = 1
27
37
  end
28
38
 
@@ -5,6 +5,8 @@ module LetsEncrypt
5
5
  class Configuration
6
6
  include ActiveSupport::Configurable
7
7
 
8
+ config_accessor :acme_directory
9
+
8
10
  config_accessor :use_staging do
9
11
  !Rails.env.production?
10
12
  end
@@ -20,6 +22,14 @@ module LetsEncrypt
20
22
  'LetsEncrypt::Certificate'
21
23
  end
22
24
 
25
+ config_accessor :max_attempts do
26
+ 30
27
+ end
28
+
29
+ config_accessor :retry_interval do
30
+ 1
31
+ end
32
+
23
33
  # Returns true if enabled `save_to_redis` feature
24
34
  def use_redis?
25
35
  save_to_redis == true
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LetsEncrypt
4
+ class Error < StandardError; end
5
+
6
+ class MaxCheckExceeded < Error; end
7
+ class InvalidStatus < Error; end
8
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LetsEncrypt
4
+ # The issue service to download the certificate
5
+ class IssueService
6
+ attr_reader :checker
7
+
8
+ STATUS_PROCESSING = 'processing'
9
+
10
+ def initialize(config: LetsEncrypt.config)
11
+ @checker = StatusChecker.new(
12
+ max_attempts: config.max_attempts,
13
+ interval: config.retry_interval
14
+ )
15
+ end
16
+
17
+ def execute(certificate, order)
18
+ ActiveSupport::Notifications.instrument('letsencrypt.issue', domain: certificate.domain) do
19
+ issue(certificate, order)
20
+ end
21
+ end
22
+
23
+ private
24
+
25
+ def issue(certificate, order)
26
+ csr = build_csr(certificate)
27
+ order.finalize(csr:)
28
+ checker.execute do
29
+ order.reload
30
+ order.status != STATUS_PROCESSING
31
+ end
32
+ fullchain = order.certificate.split("\n\n")
33
+ cert = OpenSSL::X509::Certificate.new(fullchain.shift)
34
+ certificate.refresh!(cert, fullchain)
35
+ end
36
+
37
+ def build_csr(certificate)
38
+ Acme::Client::CertificateRequest.new(
39
+ private_key: OpenSSL::PKey::RSA.new(certificate.key),
40
+ subject: {
41
+ common_name: certificate.domain
42
+ }
43
+ )
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LetsEncrypt
4
+ # The renew service to create or renew the certificate
5
+ class RenewService
6
+ attr_reader :acme_client, :config
7
+
8
+ def initialize(acme_client: LetsEncrypt.client, config: LetsEncrypt.config)
9
+ @acme_client = acme_client
10
+ @config = config
11
+ end
12
+
13
+ def execute(certificate)
14
+ ActiveSupport::Notifications.instrument('letsencrypt.renew', domain: certificate.domain) do
15
+ order = acme_client.new_order(identifiers: [certificate.domain])
16
+
17
+ verify_service = VerifyService.new(config:)
18
+ verify_service.execute(certificate, order)
19
+
20
+ issue_service = IssueService.new(config:)
21
+ issue_service.execute(certificate, order)
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LetsEncrypt
4
+ # The status checker to make a loop until the status is reached
5
+ class StatusChecker
6
+ attr_reader :max_attempts, :interval
7
+
8
+ def initialize(max_attempts:, interval:)
9
+ @max_attempts = max_attempts
10
+ @interval = interval
11
+ end
12
+
13
+ def execute
14
+ attempts = 0
15
+
16
+ loop do
17
+ break if yield
18
+
19
+ attempts += 1
20
+ raise MaxCheckExceeded, "Max attempts exceeded (#{max_attempts})" if attempts >= max_attempts
21
+
22
+ sleep interval
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LetsEncrypt
4
+ # Process the verification of the domain
5
+ class VerifyService
6
+ STATUS_PENDING = 'pending'
7
+ STATUS_VALID = 'valid'
8
+
9
+ attr_reader :checker
10
+
11
+ def initialize(config: LetsEncrypt.config)
12
+ @checker = StatusChecker.new(
13
+ max_attempts: config.max_attempts,
14
+ interval: config.retry_interval
15
+ )
16
+ end
17
+
18
+ def execute(certificate, order)
19
+ ActiveSupport::Notifications.instrument('letsencrypt.verify', domain: certificate.domain) do
20
+ verify(certificate, order)
21
+ end
22
+ end
23
+
24
+ private
25
+
26
+ def verify(certificate, order)
27
+ challenge = order.authorizations.first.http
28
+
29
+ certificate.challenge!(challenge.filename, challenge.file_content)
30
+
31
+ challenge.request_validation
32
+
33
+ checker.execute do
34
+ challenge.reload
35
+ challenge.status != STATUS_PENDING
36
+ end
37
+ assert(challenge)
38
+ end
39
+
40
+ def assert(challenge)
41
+ return if challenge.status == STATUS_VALID
42
+
43
+ raise LetsEncrypt::InvalidStatus, "Status not valid (was: #{challenge.status})"
44
+ end
45
+ end
46
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module LetsEncrypt
4
- VERSION = '0.12.0'
4
+ VERSION = '0.13.0'
5
5
  end
data/lib/letsencrypt.rb CHANGED
@@ -5,9 +5,13 @@ require 'acme-client'
5
5
  require 'redis'
6
6
  require 'letsencrypt/railtie'
7
7
  require 'letsencrypt/engine'
8
+ require 'letsencrypt/errors'
8
9
  require 'letsencrypt/configuration'
9
- require 'letsencrypt/logger_proxy'
10
10
  require 'letsencrypt/redis'
11
+ require 'letsencrypt/status_checker'
12
+ require 'letsencrypt/verify_service'
13
+ require 'letsencrypt/issue_service'
14
+ require 'letsencrypt/renew_service'
11
15
 
12
16
  # :nodoc:
13
17
  module LetsEncrypt
@@ -23,7 +27,8 @@ module LetsEncrypt
23
27
  def client
24
28
  @client ||= ::Acme::Client.new(
25
29
  private_key:,
26
- directory:
30
+ directory:,
31
+ bad_nonce_retry: 5
27
32
  )
28
33
  end
29
34
 
@@ -40,7 +45,8 @@ module LetsEncrypt
40
45
 
41
46
  # Get current using Let's Encrypt endpoint
42
47
  def directory
43
- @directory ||= config.use_staging? ? ENDPOINT_STAGING : ENDPOINT
48
+ @directory ||= config.acme_directory ||
49
+ (config.use_staging? ? ENDPOINT_STAGING : ENDPOINT)
44
50
  end
45
51
 
46
52
  # Register a Let's Encrypt account
@@ -68,7 +74,7 @@ module LetsEncrypt
68
74
  end
69
75
 
70
76
  def logger
71
- @logger ||= LoggerProxy.new(Rails.logger, tags: ['LetsEncrypt'])
77
+ @logger ||= Rails.logger.tagged('LetsEncrypt')
72
78
  end
73
79
 
74
80
  # Config how to Let's Encrypt works for Rails
@@ -3,18 +3,21 @@
3
3
  namespace :letsencrypt do
4
4
  desc 'Renew certificates that already expired or expiring soon'
5
5
  task renew: :environment do
6
- count = 0
6
+ success = 0
7
7
  failed = 0
8
8
 
9
- LetsEncrypt.certificate_model.renewable.each do |certificate|
10
- count += 1
11
-
12
- next if certificate.renew
9
+ service = LetsEncrypt::RenewService.new
13
10
 
11
+ LetsEncrypt.certificate_model.renewable.each do |certificate|
12
+ service.execute(certificate)
13
+ success += 1
14
+ rescue Acme::Client::Error, LetsEncrypt::MaxCheckExceeded, LetsEncrypt::InvalidStatus => e
14
15
  failed += 1
15
- puts "Could not renew domain: #{certificate.domain}"
16
+ puts "Could not renew domain: #{certificate.domain} - #{e.message}"
16
17
  end
17
18
 
18
- puts "Renewed #{count - failed} out of #{count} domains"
19
+ puts "Renewed #{success} certificates successfully."
20
+ puts "Failed to renew #{failed} certificates."
21
+ puts "Total: #{success + failed} certificates."
19
22
  end
20
23
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rails-letsencrypt
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.12.0
4
+ version: 0.13.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - 蒼時弦也
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2025-05-16 00:00:00.000000000 Z
10
+ date: 2025-05-20 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: acme-client
@@ -142,15 +142,12 @@ executables: []
142
142
  extensions: []
143
143
  extra_rdoc_files: []
144
144
  files:
145
- - MIT-LICENSE
146
145
  - README.md
147
146
  - Rakefile
148
147
  - app/controllers/lets_encrypt/application_controller.rb
149
148
  - app/controllers/lets_encrypt/verifications_controller.rb
150
149
  - app/jobs/lets_encrypt/application_job.rb
151
150
  - app/jobs/lets_encrypt/renew_certificates_job.rb
152
- - app/models/concerns/lets_encrypt/certificate_issuable.rb
153
- - app/models/concerns/lets_encrypt/certificate_verifiable.rb
154
151
  - app/models/lets_encrypt/certificate.rb
155
152
  - config/routes.rb
156
153
  - lib/generators/lets_encrypt/install_generator.rb
@@ -160,9 +157,13 @@ files:
160
157
  - lib/letsencrypt.rb
161
158
  - lib/letsencrypt/configuration.rb
162
159
  - lib/letsencrypt/engine.rb
163
- - lib/letsencrypt/logger_proxy.rb
160
+ - lib/letsencrypt/errors.rb
161
+ - lib/letsencrypt/issue_service.rb
164
162
  - lib/letsencrypt/railtie.rb
165
163
  - lib/letsencrypt/redis.rb
164
+ - lib/letsencrypt/renew_service.rb
165
+ - lib/letsencrypt/status_checker.rb
166
+ - lib/letsencrypt/verify_service.rb
166
167
  - lib/letsencrypt/version.rb
167
168
  - lib/rails-letsencrypt.rb
168
169
  - lib/tasks/letsencrypt_tasks.rake
data/MIT-LICENSE DELETED
@@ -1,20 +0,0 @@
1
- Copyright 2017 蒼時弦也
2
-
3
- Permission is hereby granted, free of charge, to any person obtaining
4
- a copy of this software and associated documentation files (the
5
- "Software"), to deal in the Software without restriction, including
6
- without limitation the rights to use, copy, modify, merge, publish,
7
- distribute, sublicense, and/or sell copies of the Software, and to
8
- permit persons to whom the Software is furnished to do so, subject to
9
- the following conditions:
10
-
11
- The above copyright notice and this permission notice shall be
12
- included in all copies or substantial portions of the Software.
13
-
14
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
- NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
- LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
- OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
- WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -1,43 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module LetsEncrypt
4
- # :nodoc:
5
- module CertificateIssuable
6
- extend ActiveSupport::Concern
7
-
8
- # Returns true if issue new certificate succeed.
9
- def issue
10
- logger.info "Getting certificate for #{domain}"
11
- create_certificate
12
- logger.info "Certificate issued for #{domain} (expires on #{expires_at}, will renew after #{renew_after})"
13
- true
14
- end
15
-
16
- private
17
-
18
- def csr
19
- Acme::Client::CertificateRequest.new(
20
- private_key: OpenSSL::PKey::RSA.new(key),
21
- subject: {
22
- common_name: domain
23
- }
24
- )
25
- end
26
-
27
- def create_certificate
28
- order.finalize(csr:)
29
- sleep 1 while order.status == 'processing'
30
- fullchain = order.certificate.split("\n\n")
31
- assign_new_certificate(fullchain)
32
- save!
33
- end
34
-
35
- def assign_new_certificate(fullchain)
36
- cert = OpenSSL::X509::Certificate.new(fullchain.shift)
37
- self.certificate = cert.to_pem
38
- self.intermediaries = fullchain.join("\n\n")
39
- self.expires_at = cert.not_after
40
- self.renew_after = (expires_at - 1.month) + rand(10).days
41
- end
42
- end
43
- end
@@ -1,68 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module LetsEncrypt
4
- # :nodoc:
5
- module CertificateVerifiable
6
- extend ActiveSupport::Concern
7
-
8
- # Returns true if verify domain is succeed.
9
- def verify
10
- create_order
11
- start_challenge
12
- wait_verify_status
13
- check_verify_status
14
- rescue Acme::Client::Error => e
15
- retry_on_verify_error(e)
16
- end
17
-
18
- private
19
-
20
- def create_order
21
- # TODO: Support multiple domain
22
- @challenge = order.authorizations.first.http
23
- self.verification_path = @challenge.filename
24
- self.verification_string = @challenge.file_content
25
- save!
26
- end
27
-
28
- def start_challenge
29
- logger.info "Attempting verification of #{domain}"
30
- @challenge.request_validation
31
- end
32
-
33
- def wait_verify_status
34
- checks = 0
35
- until @challenge.status != 'pending'
36
- checks += 1
37
- if checks > 30
38
- logger.info "#{domain}: Status remained at pending for 30 checks"
39
- return false
40
- end
41
- sleep 1
42
- @challenge.reload
43
- end
44
- end
45
-
46
- def check_verify_status
47
- unless @challenge.status == 'valid'
48
- logger.info "#{domain}: Status was not valid (was: #{@challenge.status})"
49
- return false
50
- end
51
-
52
- true
53
- end
54
-
55
- def retry_on_verify_error(error)
56
- @retries ||= 0
57
- if error.is_a?(Acme::Client::Error::BadNonce) && @retries < 5
58
- @retries += 1
59
- logger.info "#{domain}: Bad nounce encountered. Retrying (#{@retries} of 5 attempts)"
60
- sleep 1
61
- verify
62
- else
63
- logger.info "#{domain}: Error: #{error.class} (#{error.message})"
64
- false
65
- end
66
- end
67
- end
68
- end
@@ -1,39 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module LetsEncrypt
4
- # :nodoc:
5
- class LoggerProxy
6
- attr_reader :tags
7
-
8
- def initialize(logger, tags:)
9
- @logger = logger
10
- @tags = tags.flatten
11
- end
12
-
13
- def add_tags(*tags)
14
- @tags += tags.flatten
15
- @tags = @tags.uniq
16
- end
17
-
18
- def tag(logger, &)
19
- if logger.respond_to?(:tagged)
20
- current_tags = tags - logger.formatter.current_tags
21
- logger.tagged(*current_tags, &)
22
- else
23
- yield
24
- end
25
- end
26
-
27
- %i[debug info warn error fatal unknown].each do |severity|
28
- define_method(severity) do |message|
29
- log severity, message
30
- end
31
- end
32
-
33
- private
34
-
35
- def log(type, message)
36
- tag(@logger) { @logger.send type, message }
37
- end
38
- end
39
- end