letsencrypt-rails-heroku 1.2.1 → 2.0.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 +4 -4
- data/CHANGELOG.md +18 -0
- data/Gemfile +1 -1
- data/Gemfile.lock +2 -2
- data/README.md +19 -6
- data/VERSION +1 -1
- data/letsencrypt-rails-heroku.gemspec +6 -6
- data/lib/letsencrypt-rails-heroku/exceptions.rb +8 -4
- data/lib/letsencrypt-rails-heroku/letsencrypt.rb +14 -4
- data/lib/tasks/letsencrypt.rake +83 -35
- metadata +4 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8d58e3d5c2daf94175a089ec08d9f4833752ec89acc6b208597b7a40ee7c6b2f
|
4
|
+
data.tar.gz: e6822c34e00a731d7cf1fbd3116e50274c6b4d1a04e5c35478254eadf91b8006
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 51ef3ec5bfaa44fe033ee54ab089a1d3d3db39efd6b7be44fe1032b8534d4dbed6569fabaf8513055981004477e069f8a06bcf5f9479fed419feea6155188d00
|
7
|
+
data.tar.gz: f2ac8a3044287f5c635b32693c02d9558a669db406adb8e1d0025e6d02736ce44fae4f2e0c13da801eb6c86ede526e83d1e477c2115ea6a3f92765c25aae34c8
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,21 @@
|
|
1
|
+
# 2.0.0
|
2
|
+
|
3
|
+
Thanks to [@mashedkeyboard](https://github.com/mashedkeyboard) for their
|
4
|
+
work on ACME v2, saving registration, and DNS-based validation.
|
5
|
+
|
6
|
+
- *BREAKING* You must indicate your acceptance of Let's Encrypt's terms
|
7
|
+
and conditions by setting the `ACME_TERMS_AGREED` configuration variable.
|
8
|
+
- *BREAKING* Removed `ACME_ENDPOINT` environment variable reference. We never
|
9
|
+
documented that we support alternative endpoints, and we never tested it,
|
10
|
+
and the gem is called *letsencrypt*-rails-heroku, so let's not pretend.
|
11
|
+
Please get in touch if you were using this configuration variable, we'd
|
12
|
+
like to hear from you! Psst; you can still set `acme_directory` when
|
13
|
+
configuring the gem in an initializer.
|
14
|
+
- Use version 2 of the ACME API, paving the way for DNS validation.
|
15
|
+
- Save private key & key ID variables after registering with Let's Encrypt.
|
16
|
+
This will create two new permanent environment variables, `ACME_PRIVATE_KEY`
|
17
|
+
and `ACME_KEY_ID`.
|
18
|
+
|
1
19
|
# 1.2.1
|
2
20
|
|
3
21
|
- Update `rack` and `nokogiri` dependencies due to reported vulnerabilities
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
GEM
|
2
2
|
remote: https://rubygems.org/
|
3
3
|
specs:
|
4
|
-
acme-client (0.
|
4
|
+
acme-client (2.0.3)
|
5
5
|
faraday (~> 0.9, >= 0.9.1)
|
6
6
|
activesupport (5.0.0)
|
7
7
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
@@ -86,7 +86,7 @@ PLATFORMS
|
|
86
86
|
ruby
|
87
87
|
|
88
88
|
DEPENDENCIES
|
89
|
-
acme-client (~>
|
89
|
+
acme-client (~> 2.0)
|
90
90
|
bundler (~> 1.0)
|
91
91
|
juwelier (~> 2.1.0)
|
92
92
|
platform-api (~> 2.2)
|
data/README.md
CHANGED
@@ -61,19 +61,24 @@ end
|
|
61
61
|
|
62
62
|
## Configuring
|
63
63
|
|
64
|
-
By default the gem will try to use the following set of configuration
|
65
|
-
|
64
|
+
By default the gem will try to use the following set of configuration
|
65
|
+
variables. You must set:
|
66
|
+
|
67
|
+
* `ACME_EMAIL`: Your email address, should be valid.
|
68
|
+
* `ACME_TERMS_AGREED`: Existence of this environment variable represents your
|
69
|
+
agreement to [Let's Encrypt's terms of service](https://letsencrypt.org/repository/).
|
70
|
+
* `HEROKU_TOKEN`: An API token for this app. See below
|
71
|
+
* `HEROKU_APP`: Name of Heroku app e.g. bottomless-cavern-7173
|
72
|
+
|
73
|
+
You can also set:
|
66
74
|
|
67
75
|
* `ACME_DOMAIN`: Comma separated list of domains for which you want
|
68
76
|
certificates, e.g. `example.com,www.example.com`. Your Heroku app should be
|
69
|
-
configured to answer to all these domains, because
|
77
|
+
configured to answer to all these domains, because Let's Encrypt will make a
|
70
78
|
request to verify ownership.
|
71
79
|
|
72
80
|
If you leave this blank, the gem will try and use the Heroku API to get a
|
73
81
|
list of configured domains for your app, and verify all of them.
|
74
|
-
* `ACME_EMAIL`: Your email address, should be valid.
|
75
|
-
* `HEROKU_TOKEN`: An API token for this app. See below
|
76
|
-
* `HEROKU_APP`: Name of Heroku app e.g. bottomless-cavern-7173
|
77
82
|
* `SSL_TYPE`: Optional: One of `sni` or `endpoint`, defaults to `sni`.
|
78
83
|
`endpoint` requires your app to have an
|
79
84
|
[SSL endpoint addon](https://elements.heroku.com/addons/ssl) configured.
|
@@ -84,6 +89,14 @@ the challenge / validation process:
|
|
84
89
|
* `ACME_CHALLENGE_FILENAME`: The path of the file LetsEncrypt will request.
|
85
90
|
* `ACME_CHALLENGE_FILE_CONTENT`: The content of that challenge file.
|
86
91
|
|
92
|
+
It will also create two permanent environment variables after the first run:
|
93
|
+
|
94
|
+
* `ACME_PRIVATE_KEY`: Private key used to create requests for certificates.
|
95
|
+
* `ACME_KEY_ID`: Key ID assigned to your private key by Let's Encrypt.
|
96
|
+
|
97
|
+
If you remove these, a new account will be created and new environment
|
98
|
+
variables will be set.
|
99
|
+
|
87
100
|
## Creating a Heroku token
|
88
101
|
|
89
102
|
Use the `heroku-oauth` toolbelt plugin to generate an access token suitable
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
|
1
|
+
2.0.0
|
@@ -2,16 +2,16 @@
|
|
2
2
|
# DO NOT EDIT THIS FILE DIRECTLY
|
3
3
|
# Instead, edit Juwelier::Tasks in Rakefile, and run 'rake gemspec'
|
4
4
|
# -*- encoding: utf-8 -*-
|
5
|
-
# stub: letsencrypt-rails-heroku
|
5
|
+
# stub: letsencrypt-rails-heroku 2.0.0 ruby lib
|
6
6
|
|
7
7
|
Gem::Specification.new do |s|
|
8
8
|
s.name = "letsencrypt-rails-heroku".freeze
|
9
|
-
s.version = "
|
9
|
+
s.version = "2.0.0"
|
10
10
|
|
11
11
|
s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version=
|
12
12
|
s.require_paths = ["lib".freeze]
|
13
13
|
s.authors = ["Pixie Labs".freeze, "David Somers".freeze, "Abigail McPhillips".freeze]
|
14
|
-
s.date = "2019-
|
14
|
+
s.date = "2019-05-17"
|
15
15
|
s.description = "This gem automatically handles creation, renewal, and applying SSL certificates from LetsEncrypt to your Heroku account.".freeze
|
16
16
|
s.email = "team@pixielabs.io".freeze
|
17
17
|
s.extra_rdoc_files = [
|
@@ -44,7 +44,7 @@ Gem::Specification.new do |s|
|
|
44
44
|
s.specification_version = 4
|
45
45
|
|
46
46
|
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
47
|
-
s.add_runtime_dependency(%q<acme-client>.freeze, ["~>
|
47
|
+
s.add_runtime_dependency(%q<acme-client>.freeze, ["~> 2.0"])
|
48
48
|
s.add_runtime_dependency(%q<platform-api>.freeze, ["~> 2.2"])
|
49
49
|
s.add_development_dependency(%q<shoulda>.freeze, [">= 0"])
|
50
50
|
s.add_development_dependency(%q<rdoc>.freeze, ["~> 3.12"])
|
@@ -52,7 +52,7 @@ Gem::Specification.new do |s|
|
|
52
52
|
s.add_development_dependency(%q<juwelier>.freeze, ["~> 2.1.0"])
|
53
53
|
s.add_development_dependency(%q<simplecov>.freeze, [">= 0"])
|
54
54
|
else
|
55
|
-
s.add_dependency(%q<acme-client>.freeze, ["~>
|
55
|
+
s.add_dependency(%q<acme-client>.freeze, ["~> 2.0"])
|
56
56
|
s.add_dependency(%q<platform-api>.freeze, ["~> 2.2"])
|
57
57
|
s.add_dependency(%q<shoulda>.freeze, [">= 0"])
|
58
58
|
s.add_dependency(%q<rdoc>.freeze, ["~> 3.12"])
|
@@ -61,7 +61,7 @@ Gem::Specification.new do |s|
|
|
61
61
|
s.add_dependency(%q<simplecov>.freeze, [">= 0"])
|
62
62
|
end
|
63
63
|
else
|
64
|
-
s.add_dependency(%q<acme-client>.freeze, ["~>
|
64
|
+
s.add_dependency(%q<acme-client>.freeze, ["~> 2.0"])
|
65
65
|
s.add_dependency(%q<platform-api>.freeze, ["~> 2.2"])
|
66
66
|
s.add_dependency(%q<shoulda>.freeze, [">= 0"])
|
67
67
|
s.add_dependency(%q<rdoc>.freeze, ["~> 3.12"])
|
@@ -1,12 +1,16 @@
|
|
1
1
|
module Letsencrypt
|
2
2
|
module Error
|
3
|
-
#
|
3
|
+
# LetsEncrypt encountered an issue verifying the challenge.
|
4
4
|
class VerificationError < StandardError; end
|
5
|
-
#
|
5
|
+
# LetsEncrypt encountered an issue finalizing the order.
|
6
|
+
class FinalizationError < StandardError; end
|
7
|
+
# Challenge URL is not available.
|
6
8
|
class ChallengeUrlError < StandardError; end
|
7
|
-
#
|
9
|
+
# Domain verification took longer than we'd like.
|
8
10
|
class VerificationTimeoutError < StandardError; end
|
9
|
-
#
|
11
|
+
# Order finalization took longer than we'd like.
|
12
|
+
class FinalizationTimeoutError < StandardError; end
|
13
|
+
# Error adding the certificate to Heroku.
|
10
14
|
class HerokuCertificateError < StandardError; end
|
11
15
|
end
|
12
16
|
end
|
@@ -14,26 +14,36 @@ module Letsencrypt
|
|
14
14
|
configuration.acme_challenge_file_content
|
15
15
|
end
|
16
16
|
|
17
|
+
def self.registered?
|
18
|
+
configuration.acme_private_key && configuration.acme_key_id
|
19
|
+
end
|
20
|
+
|
17
21
|
class Configuration
|
18
22
|
attr_accessor :heroku_token, :heroku_app, :acme_email, :acme_domain,
|
19
|
-
:
|
23
|
+
:acme_directory, :ssl_type, :acme_terms_agreed
|
20
24
|
|
21
25
|
# Not settable by user; part of the gem's behaviour.
|
22
|
-
attr_reader :acme_challenge_filename, :acme_challenge_file_content
|
26
|
+
attr_reader :acme_challenge_filename, :acme_challenge_file_content,
|
27
|
+
:acme_private_key, :acme_key_id
|
23
28
|
|
24
29
|
def initialize
|
25
30
|
@heroku_token = ENV["HEROKU_TOKEN"]
|
26
31
|
@heroku_app = ENV["HEROKU_APP"]
|
27
32
|
@acme_email = ENV["ACME_EMAIL"]
|
28
33
|
@acme_domain = ENV["ACME_DOMAIN"]
|
29
|
-
@
|
34
|
+
@acme_directory = 'https://acme-v02.api.letsencrypt.org/directory'
|
35
|
+
@acme_terms_agreed = ENV["ACME_TERMS_AGREED"]
|
30
36
|
@ssl_type = ENV["SSL_TYPE"] || 'sni'
|
37
|
+
|
31
38
|
@acme_challenge_filename = ENV["ACME_CHALLENGE_FILENAME"]
|
32
39
|
@acme_challenge_file_content = ENV["ACME_CHALLENGE_FILE_CONTENT"]
|
40
|
+
|
41
|
+
@acme_private_key = ENV["ACME_PRIVATE_KEY"]
|
42
|
+
@acme_key_id = ENV["ACME_KEY_ID"]
|
33
43
|
end
|
34
44
|
|
35
45
|
def valid?
|
36
|
-
heroku_token && heroku_app && acme_email
|
46
|
+
heroku_token && heroku_app && acme_email && acme_terms_agreed
|
37
47
|
end
|
38
48
|
end
|
39
49
|
end
|
data/lib/tasks/letsencrypt.rake
CHANGED
@@ -8,24 +8,48 @@ namespace :letsencrypt do
|
|
8
8
|
desc 'Renew your LetsEncrypt certificate'
|
9
9
|
task :renew do
|
10
10
|
# Check configuration looks OK
|
11
|
-
abort "letsencrypt-rails-heroku is configured incorrectly. Are you missing an environment variable or other configuration? You should have
|
11
|
+
abort "letsencrypt-rails-heroku is configured incorrectly. Are you missing an environment variable or other configuration? You should have heroku_token, heroku_app, acme_email and acme_terms_agreed configured either via a `Letsencrypt.configure` block in an initializer or as environment variables." unless Letsencrypt.configuration.valid?
|
12
12
|
|
13
13
|
# Set up Heroku client
|
14
14
|
heroku = PlatformAPI.connect_oauth Letsencrypt.configuration.heroku_token
|
15
15
|
heroku_app = Letsencrypt.configuration.heroku_app
|
16
16
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
17
|
+
if Letsencrypt.registered?
|
18
|
+
puts "Using existing registration details"
|
19
|
+
private_key = OpenSSL::PKey::RSA.new(Letsencrypt.configuration.acme_private_key)
|
20
|
+
key_id = Letsencrypt.configuration.acme_key_id
|
21
|
+
else
|
22
|
+
# Create a private key
|
23
|
+
print "Creating account key..."
|
24
|
+
private_key = OpenSSL::PKey::RSA.new(4096)
|
25
|
+
puts "Done!"
|
21
26
|
|
22
|
-
|
27
|
+
client = Acme::Client.new(private_key: private_key,
|
28
|
+
directory: Letsencrypt.configuration.acme_directory,
|
29
|
+
connection_options: {
|
30
|
+
request: {
|
31
|
+
open_timeout: 5,
|
32
|
+
timeout: 5
|
33
|
+
}
|
34
|
+
})
|
23
35
|
|
24
|
-
|
25
|
-
|
36
|
+
print "Registering with LetsEncrypt..."
|
37
|
+
account = client.new_account(contact: "mailto:#{Letsencrypt.configuration.acme_email}",
|
38
|
+
terms_of_service_agreed: true)
|
26
39
|
|
27
|
-
|
28
|
-
|
40
|
+
key_id = account.kid
|
41
|
+
puts "Done!"
|
42
|
+
print "Saving account details as configuration variables..."
|
43
|
+
heroku.config_var.update(heroku_app,
|
44
|
+
'ACME_PRIVATE_KEY' => private_key.to_pem,
|
45
|
+
'ACME_KEY_ID' => account.kid)
|
46
|
+
puts "Done!"
|
47
|
+
end
|
48
|
+
|
49
|
+
# Make a new Acme::Client with whichever private_key & key_id we ended up with.
|
50
|
+
client = Acme::Client.new(private_key: private_key,
|
51
|
+
directory: Letsencrypt.configuration.acme_directory,
|
52
|
+
kid: key_id)
|
29
53
|
|
30
54
|
domains = []
|
31
55
|
if Letsencrypt.configuration.acme_domain
|
@@ -36,11 +60,12 @@ namespace :letsencrypt do
|
|
36
60
|
puts "Using #{domains.length} configured Heroku domain(s) for this app..."
|
37
61
|
end
|
38
62
|
|
39
|
-
|
40
|
-
puts "Performing verification for #{domain}:"
|
63
|
+
order = client.new_order(identifiers: domains)
|
41
64
|
|
42
|
-
|
43
|
-
|
65
|
+
order.authorizations.each do |authorization|
|
66
|
+
puts "Performing verification for #{authorization.domain}:"
|
67
|
+
|
68
|
+
challenge = authorization.http
|
44
69
|
|
45
70
|
print "Setting config vars on Heroku..."
|
46
71
|
heroku.config_var.update(heroku_app, {
|
@@ -78,24 +103,23 @@ namespace :letsencrypt do
|
|
78
103
|
|
79
104
|
print "Giving LetsEncrypt some time to verify..."
|
80
105
|
# Once you are ready to serve the confirmation request you can proceed.
|
81
|
-
challenge.
|
82
|
-
challenge.verify_status # => 'pending'
|
106
|
+
challenge.request_validation
|
83
107
|
|
84
108
|
start_time = Time.now
|
85
|
-
|
86
|
-
while challenge.verify_status == 'pending'
|
109
|
+
while challenge.status == 'pending'
|
87
110
|
if Time.now - start_time >= 30
|
88
111
|
failure_message = "Failed - timed out waiting for challenge verification."
|
89
112
|
raise Letsencrypt::Error::VerificationTimeoutError, failure_message
|
90
113
|
end
|
91
|
-
sleep(
|
114
|
+
sleep(2)
|
115
|
+
challenge.reload
|
92
116
|
end
|
93
117
|
|
94
118
|
puts "Done!"
|
95
119
|
|
96
|
-
unless challenge.
|
120
|
+
unless challenge.status == 'valid'
|
97
121
|
puts "Problem verifying challenge."
|
98
|
-
failure_message = "Status: #{challenge.
|
122
|
+
failure_message = "Status: #{challenge.status}, Error: #{challenge.error}"
|
99
123
|
raise Letsencrypt::Error::VerificationError, failure_message
|
100
124
|
end
|
101
125
|
|
@@ -110,10 +134,33 @@ namespace :letsencrypt do
|
|
110
134
|
})
|
111
135
|
|
112
136
|
# Create CSR
|
113
|
-
|
137
|
+
csr_private_key = OpenSSL::PKey::RSA.new 4096
|
138
|
+
csr = Acme::Client::CertificateRequest.new(names: domains,
|
139
|
+
private_key: csr_private_key)
|
114
140
|
|
141
|
+
print "Asking LetsEncrypt to finalize our certificate order..."
|
115
142
|
# Get certificate
|
116
|
-
|
143
|
+
order.finalize(csr: csr)
|
144
|
+
|
145
|
+
# Wait for order to process
|
146
|
+
start_time = Time.now
|
147
|
+
while order.status == 'processing'
|
148
|
+
if Time.now - start_time >= 30
|
149
|
+
failure_message = "Failed - timed out waiting for order finalization"
|
150
|
+
raise Letsencrypt::Error::FinalizationTimeoutError, failure_message
|
151
|
+
end
|
152
|
+
sleep(2)
|
153
|
+
order.reload
|
154
|
+
end
|
155
|
+
|
156
|
+
puts "Done!"
|
157
|
+
|
158
|
+
unless order.status == 'valid'
|
159
|
+
failure_message = "Problem finalizing order - status: #{order.status}"
|
160
|
+
raise Letsencrypt::Error::FinalizationError, failure_message
|
161
|
+
end
|
162
|
+
|
163
|
+
certificate = order.certificate # => PEM-formatted certificate
|
117
164
|
|
118
165
|
# Send certificates to Heroku via API
|
119
166
|
|
@@ -124,23 +171,24 @@ namespace :letsencrypt do
|
|
124
171
|
heroku.ssl_endpoint
|
125
172
|
end
|
126
173
|
|
127
|
-
|
128
|
-
|
174
|
+
certificate_info = {
|
175
|
+
certificate_chain: certificate,
|
176
|
+
private_key: csr_private_key.to_pem
|
177
|
+
}
|
178
|
+
|
179
|
+
# Fetch existing certificate from Heroku (if any). We just use the first
|
180
|
+
# one; if someone has more than one, they're probably not actually using
|
181
|
+
# this gem. Could also be an error?
|
182
|
+
existing_certificate = endpoint.list(heroku_app)[0]
|
129
183
|
|
130
184
|
begin
|
131
|
-
if
|
132
|
-
print "Updating existing certificate #{
|
133
|
-
endpoint.update(heroku_app,
|
134
|
-
certificate_chain: certificate.fullchain_to_pem,
|
135
|
-
private_key: certificate.request.private_key.to_pem
|
136
|
-
})
|
185
|
+
if existing_certificate
|
186
|
+
print "Updating existing certificate #{existing_certificate['name']}..."
|
187
|
+
endpoint.update(heroku_app, existing_certificates['name'], certificate_info)
|
137
188
|
puts "Done!"
|
138
189
|
else
|
139
190
|
print "Adding new certificate..."
|
140
|
-
endpoint.create(heroku_app,
|
141
|
-
certificate_chain: certificate.fullchain_to_pem,
|
142
|
-
private_key: certificate.request.private_key.to_pem
|
143
|
-
})
|
191
|
+
endpoint.create(heroku_app, certificate_info)
|
144
192
|
puts "Done!"
|
145
193
|
end
|
146
194
|
rescue Excon::Error::UnprocessableEntity => e
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: letsencrypt-rails-heroku
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 2.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Pixie Labs
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date: 2019-
|
13
|
+
date: 2019-05-17 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: acme-client
|
@@ -18,14 +18,14 @@ dependencies:
|
|
18
18
|
requirements:
|
19
19
|
- - "~>"
|
20
20
|
- !ruby/object:Gem::Version
|
21
|
-
version:
|
21
|
+
version: '2.0'
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
24
|
version_requirements: !ruby/object:Gem::Requirement
|
25
25
|
requirements:
|
26
26
|
- - "~>"
|
27
27
|
- !ruby/object:Gem::Version
|
28
|
-
version:
|
28
|
+
version: '2.0'
|
29
29
|
- !ruby/object:Gem::Dependency
|
30
30
|
name: platform-api
|
31
31
|
requirement: !ruby/object:Gem::Requirement
|