letsencrypt-rails-heroku 0.2.3
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 +7 -0
- data/.document +5 -0
- data/Gemfile +14 -0
- data/Gemfile.lock +117 -0
- data/LICENSE.txt +20 -0
- data/README.md +174 -0
- data/Rakefile +52 -0
- data/VERSION +1 -0
- data/letsencrypt-rails-heroku.gemspec +71 -0
- data/lib/letsencrypt-rails-heroku.rb +6 -0
- data/lib/letsencrypt-rails-heroku/letsencrypt.rb +38 -0
- data/lib/letsencrypt-rails-heroku/middleware.rb +17 -0
- data/lib/letsencrypt-rails-heroku/railtie.rb +9 -0
- data/lib/tasks/letsencrypt.rake +106 -0
- metadata +157 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA1:
|
|
3
|
+
metadata.gz: 1a4a463e9fae1296ef19b8fc1213e38fd046e438
|
|
4
|
+
data.tar.gz: 854e97723f2eaf1c7aa3f90ac941ecc8af4a2423
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: f161f92843c8a7c4af64acea6b2d606b0ce3e62fccbc9bbdbaeda40d7bf92d6f69ce3615e356382519a9c07b0c696231f2fb2007336cd0bf10dc75ed1af709c6
|
|
7
|
+
data.tar.gz: 36a5bd7ee0404c167a649c95b624c5ce518993ad41335e41db878b05e7544487b8f0466bb02f18ed0b00d50dfacebc3a4f5fc555673ace3c35ceb0e88b0396b2
|
data/.document
ADDED
data/Gemfile
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
source "https://rubygems.org"
|
|
2
|
+
|
|
3
|
+
gem 'acme-client', '~> 0.3.7'
|
|
4
|
+
# SNI endpoints not supported yet:
|
|
5
|
+
# <https://github.com/heroku/platform-api/issues/49>
|
|
6
|
+
gem 'platform-api', github: 'jalada/platform-api', branch: 'master'
|
|
7
|
+
|
|
8
|
+
group :development do
|
|
9
|
+
gem "shoulda", ">= 0"
|
|
10
|
+
gem "rdoc", "~> 3.12"
|
|
11
|
+
gem "bundler", "~> 1.0"
|
|
12
|
+
gem "juwelier", "~> 2.1.0"
|
|
13
|
+
gem "simplecov", ">= 0"
|
|
14
|
+
end
|
data/Gemfile.lock
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
GIT
|
|
2
|
+
remote: git://github.com/jalada/platform-api.git
|
|
3
|
+
revision: 45ddb3c1a7e2c7f85d979c0791db18e99affb237
|
|
4
|
+
branch: master
|
|
5
|
+
specs:
|
|
6
|
+
platform-api (0.8.0)
|
|
7
|
+
heroics (~> 0.0.17)
|
|
8
|
+
|
|
9
|
+
GEM
|
|
10
|
+
remote: https://rubygems.org/
|
|
11
|
+
specs:
|
|
12
|
+
acme-client (0.3.7)
|
|
13
|
+
faraday (~> 0.9, >= 0.9.1)
|
|
14
|
+
json-jwt (~> 1.2, >= 1.2.3)
|
|
15
|
+
activesupport (5.0.0)
|
|
16
|
+
concurrent-ruby (~> 1.0, >= 1.0.2)
|
|
17
|
+
i18n (~> 0.7)
|
|
18
|
+
minitest (~> 5.1)
|
|
19
|
+
tzinfo (~> 1.1)
|
|
20
|
+
addressable (2.4.0)
|
|
21
|
+
bindata (2.3.1)
|
|
22
|
+
builder (3.2.2)
|
|
23
|
+
concurrent-ruby (1.0.2)
|
|
24
|
+
descendants_tracker (0.0.4)
|
|
25
|
+
thread_safe (~> 0.3, >= 0.3.1)
|
|
26
|
+
docile (1.1.5)
|
|
27
|
+
erubis (2.7.0)
|
|
28
|
+
excon (0.51.0)
|
|
29
|
+
faraday (0.9.2)
|
|
30
|
+
multipart-post (>= 1.2, < 3)
|
|
31
|
+
git (1.3.0)
|
|
32
|
+
github_api (0.14.4)
|
|
33
|
+
addressable (~> 2.4.0)
|
|
34
|
+
descendants_tracker (~> 0.0.4)
|
|
35
|
+
faraday (~> 0.8, < 0.10)
|
|
36
|
+
hashie (>= 3.4)
|
|
37
|
+
oauth2 (~> 1.0.0)
|
|
38
|
+
hashie (3.4.4)
|
|
39
|
+
heroics (0.0.17)
|
|
40
|
+
erubis (~> 2.0)
|
|
41
|
+
excon
|
|
42
|
+
moneta
|
|
43
|
+
multi_json (>= 1.9.2)
|
|
44
|
+
netrc
|
|
45
|
+
highline (1.7.8)
|
|
46
|
+
i18n (0.7.0)
|
|
47
|
+
json (1.8.3)
|
|
48
|
+
json-jwt (1.6.3)
|
|
49
|
+
activesupport
|
|
50
|
+
bindata
|
|
51
|
+
multi_json (>= 1.3)
|
|
52
|
+
securecompare
|
|
53
|
+
url_safe_base64
|
|
54
|
+
juwelier (2.1.2)
|
|
55
|
+
builder
|
|
56
|
+
bundler (>= 1.0)
|
|
57
|
+
git (>= 1.2.5)
|
|
58
|
+
github_api
|
|
59
|
+
highline (>= 1.6.15)
|
|
60
|
+
nokogiri (>= 1.5.10)
|
|
61
|
+
rake
|
|
62
|
+
rdoc
|
|
63
|
+
semver
|
|
64
|
+
jwt (1.5.4)
|
|
65
|
+
mini_portile2 (2.1.0)
|
|
66
|
+
minitest (5.9.0)
|
|
67
|
+
moneta (0.8.0)
|
|
68
|
+
multi_json (1.12.1)
|
|
69
|
+
multi_xml (0.5.5)
|
|
70
|
+
multipart-post (2.0.0)
|
|
71
|
+
netrc (0.11.0)
|
|
72
|
+
nokogiri (1.6.8)
|
|
73
|
+
mini_portile2 (~> 2.1.0)
|
|
74
|
+
pkg-config (~> 1.1.7)
|
|
75
|
+
oauth2 (1.0.0)
|
|
76
|
+
faraday (>= 0.8, < 0.10)
|
|
77
|
+
jwt (~> 1.0)
|
|
78
|
+
multi_json (~> 1.3)
|
|
79
|
+
multi_xml (~> 0.5)
|
|
80
|
+
rack (~> 1.2)
|
|
81
|
+
pkg-config (1.1.7)
|
|
82
|
+
rack (1.6.4)
|
|
83
|
+
rake (11.2.2)
|
|
84
|
+
rdoc (3.12.2)
|
|
85
|
+
json (~> 1.4)
|
|
86
|
+
securecompare (1.0.0)
|
|
87
|
+
semver (1.0.1)
|
|
88
|
+
shoulda (3.5.0)
|
|
89
|
+
shoulda-context (~> 1.0, >= 1.0.1)
|
|
90
|
+
shoulda-matchers (>= 1.4.1, < 3.0)
|
|
91
|
+
shoulda-context (1.2.1)
|
|
92
|
+
shoulda-matchers (2.8.0)
|
|
93
|
+
activesupport (>= 3.0.0)
|
|
94
|
+
simplecov (0.12.0)
|
|
95
|
+
docile (~> 1.1.0)
|
|
96
|
+
json (>= 1.8, < 3)
|
|
97
|
+
simplecov-html (~> 0.10.0)
|
|
98
|
+
simplecov-html (0.10.0)
|
|
99
|
+
thread_safe (0.3.5)
|
|
100
|
+
tzinfo (1.2.2)
|
|
101
|
+
thread_safe (~> 0.1)
|
|
102
|
+
url_safe_base64 (0.2.2)
|
|
103
|
+
|
|
104
|
+
PLATFORMS
|
|
105
|
+
ruby
|
|
106
|
+
|
|
107
|
+
DEPENDENCIES
|
|
108
|
+
acme-client (~> 0.3.7)
|
|
109
|
+
bundler (~> 1.0)
|
|
110
|
+
juwelier (~> 2.1.0)
|
|
111
|
+
platform-api!
|
|
112
|
+
rdoc (~> 3.12)
|
|
113
|
+
shoulda
|
|
114
|
+
simplecov
|
|
115
|
+
|
|
116
|
+
BUNDLED WITH
|
|
117
|
+
1.12.5
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
Copyright (c) 2016 Pixie Labs
|
|
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.
|
data/README.md
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
# LetsEncrypt & Rails & Heroku
|
|
2
|
+
|
|
3
|
+
[](https://badge.fury.io/rb/letsencrypt-rails-heroku)
|
|
4
|
+
|
|
5
|
+
This gem is a complete solution for securing your Ruby on Rails application
|
|
6
|
+
on Heroku using their free SNI-based SSL and LetsEncrypt. It will automatically
|
|
7
|
+
handle renewals and keeping your certificate up to date.
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
## Pre-requestives
|
|
11
|
+
|
|
12
|
+
- Whilst it is in beta, you must use the labs feature to enable Heroku's free
|
|
13
|
+
SSL offering:
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
heroku labs:enable http-sni
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
- You must be using hobby or professional dynos to use free SNI-based SSL.
|
|
20
|
+
|
|
21
|
+
- You should have already configured your app DNS as per [Heroku's
|
|
22
|
+
documentation](https://devcenter.heroku.com/articles/custom-domains).
|
|
23
|
+
|
|
24
|
+
## Installation
|
|
25
|
+
|
|
26
|
+
Add the gem to your Gemfile:
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
# Until the API calls are out of beta, you must manually specify my fork
|
|
30
|
+
# of the Heroku API gem:
|
|
31
|
+
gem 'platform-api', github: 'jalada/platform-api', branch: 'master'
|
|
32
|
+
|
|
33
|
+
gem 'letsencrypt-rails-heroku', group: 'production'
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
And add it as middleware in your `config/environments/production.rb`:
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
Rails.application.configure do
|
|
40
|
+
<...>
|
|
41
|
+
|
|
42
|
+
config.middleware.use Letsencrypt::Middleware
|
|
43
|
+
|
|
44
|
+
<...>
|
|
45
|
+
end
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Configuring
|
|
49
|
+
|
|
50
|
+
By default the gem will try to use the following set of configuration variables,
|
|
51
|
+
which you should set.
|
|
52
|
+
|
|
53
|
+
* `ACME_DOMAIN`: Comma separated list of domains for which you want
|
|
54
|
+
certificates, e.g. `example.com,www.example.com`. Your Heroku app should be
|
|
55
|
+
configured to answer to all these domains, because LetsEncrypt will make a
|
|
56
|
+
request to verify ownership.
|
|
57
|
+
* `ACME_EMAIL`: Your email address, should be valid.
|
|
58
|
+
* `HEROKU_TOKEN`: An API token for this app. See below
|
|
59
|
+
* `HEROKU_APP`: Name of Heroku app e.g. bottomless-cavern-7173
|
|
60
|
+
|
|
61
|
+
The gem itself will temporarily create additional environment variables during
|
|
62
|
+
the challenge / validation process:
|
|
63
|
+
|
|
64
|
+
* `ACME_CHALLENGE_FILENAME`: The path of the file LetsEncrypt will request.
|
|
65
|
+
* `ACME_CHALLENGE_FILE_CONTENT`: The content of that challenge file.
|
|
66
|
+
|
|
67
|
+
## Creating a Heroku token
|
|
68
|
+
|
|
69
|
+
Use the `heroku-oauth` toolbelt plugin to generate an access token suitable
|
|
70
|
+
for accessing the Heroku API to update the certificates. From within your
|
|
71
|
+
project directory:
|
|
72
|
+
|
|
73
|
+
```
|
|
74
|
+
> heroku plugins:install heroku-cli-oauth
|
|
75
|
+
> heroku authorizations:create -d "LetsEncrypt"
|
|
76
|
+
Created OAuth authorization.
|
|
77
|
+
ID: <heroku-client-id>
|
|
78
|
+
Description: LetsEncrypt
|
|
79
|
+
Scope: global
|
|
80
|
+
Token: <heroku-token>
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Use the output of that to set the token (`HEROKU_TOKEN`).
|
|
84
|
+
|
|
85
|
+
## Using for the first time
|
|
86
|
+
|
|
87
|
+
After deploying, run `heroku run rake letsencrypt:renew`. Ensure that the
|
|
88
|
+
output looks good:
|
|
89
|
+
|
|
90
|
+
```
|
|
91
|
+
$ heroku run rake letsencrypt-renew
|
|
92
|
+
Running rake letsencrypt:renew on ⬢ yourapp... ⣷ connecting, run.1234
|
|
93
|
+
Creating account key...Done!
|
|
94
|
+
Registering with LetsEncrypt...Done!
|
|
95
|
+
Setting config vars on Heroku...Done!
|
|
96
|
+
Giving config vars time to change...Done!
|
|
97
|
+
Testing filename works (to bring up app)...done!
|
|
98
|
+
Adding new certificate...Done!
|
|
99
|
+
$
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
If this is the first time you have used an SNI-based SSL certificate on your
|
|
103
|
+
app, you may need to alter your DNS configuration as per
|
|
104
|
+
[Heroku's instructions](https://devcenter.heroku.com/articles/ssl-beta#change-your-dns-for-all-domains-on-your-app).
|
|
105
|
+
|
|
106
|
+
You can see these details by typing `heroku domains`.
|
|
107
|
+
|
|
108
|
+
## Adding a scheduled task
|
|
109
|
+
|
|
110
|
+
You should add a scheduled task on Heroku to renew the certificate. The
|
|
111
|
+
scheduled task should be configured to run `rake letsencrypt:renew` as often
|
|
112
|
+
as you want to renew your certificate. Letsencrypt certificates are valid for
|
|
113
|
+
90 days, but there's no harm renewing them more frequently than that.
|
|
114
|
+
|
|
115
|
+
Heroku Scheduler only lets you run a task as infrequently as once a day, but
|
|
116
|
+
you don't want to renew your SSL certificate every day (you will hit
|
|
117
|
+
[the rate limit](https://letsencrypt.org/docs/rate-limits/)). You can make it
|
|
118
|
+
run less frequently using a shell control statement. For example to renew your
|
|
119
|
+
certificate on the 1st day of every month:
|
|
120
|
+
|
|
121
|
+
```
|
|
122
|
+
if [ "$(date +%d)" = 01 ]; then rake letsencrypt:renew; fi
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Source: [blog.dbrgn.ch](https://blog.dbrgn.ch/2013/10/4/heroku-schedule-weekly-monthly-tasks/)
|
|
126
|
+
|
|
127
|
+
## Security considerations
|
|
128
|
+
|
|
129
|
+
Suggestions and pull requests are welcome in improving the situation with the
|
|
130
|
+
following security considerations:
|
|
131
|
+
|
|
132
|
+
- When configuring this gem you are baking a non-expiring Heroku API token
|
|
133
|
+
into your applications environment. Your collaborators could use this
|
|
134
|
+
token to impersonate the account it was created with when accessing
|
|
135
|
+
the Heroku API. This is important if your account has access to other apps
|
|
136
|
+
that your collaborators don’t. Additionally, if your application’s environment was
|
|
137
|
+
leaked this would give access to the Heroku API as your user account.
|
|
138
|
+
[More information about Heroku’s API and oAuth](https://devcenter.heroku.com/articles/oauth#direct-authorization).
|
|
139
|
+
|
|
140
|
+
You should create the API token from a suitably locked-down account.
|
|
141
|
+
|
|
142
|
+
- This gem uses two environment variables (`ACME_CHALLENGE_FILENAME` and
|
|
143
|
+
`ACME_CHALLENGE_FILE_CONTENT`) to construct routes and responses in your
|
|
144
|
+
app. These environment variables could be manipulated to spoof URLs on your
|
|
145
|
+
application.
|
|
146
|
+
|
|
147
|
+
The gem performs some cursory checks to make sure the filename is roughly
|
|
148
|
+
what is expected to try and mitigate this.
|
|
149
|
+
|
|
150
|
+
## To-do list
|
|
151
|
+
|
|
152
|
+
- Persist account key, or at least give the option of using an existing one, so
|
|
153
|
+
we don’t register with LetsEncrypt over and over.
|
|
154
|
+
|
|
155
|
+
- Stop using a fork of the `platform-api` gem once it supports the SNI endpoint
|
|
156
|
+
API calls.
|
|
157
|
+
|
|
158
|
+
- Provide instructions for running the gem decoupled from the app it is
|
|
159
|
+
securing, for the paranoid.
|
|
160
|
+
|
|
161
|
+
## Contributing
|
|
162
|
+
|
|
163
|
+
- Check out the latest master to make sure the feature hasn't been implemented
|
|
164
|
+
or the bug hasn't been fixed yet.
|
|
165
|
+
- Check out the issue tracker to make sure someone already hasn't requested it
|
|
166
|
+
and/or contributed it.
|
|
167
|
+
- Fork the project.
|
|
168
|
+
- Start a feature/bugfix branch.
|
|
169
|
+
- Commit and push until you are happy with your contribution.
|
|
170
|
+
- Make sure to add tests for it. This is important so I don't break it in a
|
|
171
|
+
future version unintentionally.
|
|
172
|
+
- Please try not to mess with the Rakefile, version, or history. If you want to
|
|
173
|
+
have your own version, or is otherwise necessary, that is fine, but please
|
|
174
|
+
isolate to its own commit so I can cherry-pick around it.
|
data/Rakefile
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
|
|
3
|
+
require 'rubygems'
|
|
4
|
+
require 'bundler'
|
|
5
|
+
begin
|
|
6
|
+
Bundler.setup(:default, :development)
|
|
7
|
+
rescue Bundler::BundlerError => e
|
|
8
|
+
$stderr.puts e.message
|
|
9
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
|
10
|
+
exit e.status_code
|
|
11
|
+
end
|
|
12
|
+
require 'rake'
|
|
13
|
+
|
|
14
|
+
require 'juwelier'
|
|
15
|
+
Juwelier::Tasks.new do |gem|
|
|
16
|
+
# gem is a Gem::Specification... see http://guides.rubygems.org/specification-reference/ for more options
|
|
17
|
+
gem.name = "letsencrypt-rails-heroku"
|
|
18
|
+
gem.homepage = "http://github.com/jalada/letsencrypt-rails-heroku"
|
|
19
|
+
gem.license = "MIT"
|
|
20
|
+
gem.summary = %Q{Automatic LetsEncrypt certs in your Rails app on Heroku}
|
|
21
|
+
gem.description = %Q{This gem automatically handles creation, renewal, and applying SSL certificates from LetsEncrypt to your Heroku account.}
|
|
22
|
+
gem.email = "david@jalada.co.uk"
|
|
23
|
+
gem.authors = ["David Somers"]
|
|
24
|
+
|
|
25
|
+
# dependencies defined in Gemfile
|
|
26
|
+
end
|
|
27
|
+
Juwelier::RubygemsDotOrgTasks.new
|
|
28
|
+
|
|
29
|
+
require 'rake/testtask'
|
|
30
|
+
Rake::TestTask.new(:test) do |test|
|
|
31
|
+
test.libs << 'lib' << 'test'
|
|
32
|
+
test.pattern = 'test/**/test_*.rb'
|
|
33
|
+
test.verbose = true
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
desc "Code coverage detail"
|
|
37
|
+
task :simplecov do
|
|
38
|
+
ENV['COVERAGE'] = "true"
|
|
39
|
+
Rake::Task['test'].execute
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
task :default => :test
|
|
43
|
+
|
|
44
|
+
require 'rdoc/task'
|
|
45
|
+
Rake::RDocTask.new do |rdoc|
|
|
46
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
|
47
|
+
|
|
48
|
+
rdoc.rdoc_dir = 'rdoc'
|
|
49
|
+
rdoc.title = "letsencrypt-rails-heroku #{version}"
|
|
50
|
+
rdoc.rdoc_files.include('README*')
|
|
51
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
|
52
|
+
end
|
data/VERSION
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
0.2.3
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# Generated by juwelier
|
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
|
3
|
+
# Instead, edit Juwelier::Tasks in Rakefile, and run 'rake gemspec'
|
|
4
|
+
# -*- encoding: utf-8 -*-
|
|
5
|
+
# stub: letsencrypt-rails-heroku 0.2.3 ruby lib
|
|
6
|
+
|
|
7
|
+
Gem::Specification.new do |s|
|
|
8
|
+
s.name = "letsencrypt-rails-heroku"
|
|
9
|
+
s.version = "0.2.3"
|
|
10
|
+
|
|
11
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
|
12
|
+
s.require_paths = ["lib"]
|
|
13
|
+
s.authors = ["David Somers"]
|
|
14
|
+
s.date = "2016-08-01"
|
|
15
|
+
s.description = "This gem automatically handles creation, renewal, and applying SSL certificates from LetsEncrypt to your Heroku account."
|
|
16
|
+
s.email = "david@jalada.co.uk"
|
|
17
|
+
s.extra_rdoc_files = [
|
|
18
|
+
"LICENSE.txt",
|
|
19
|
+
"README.md"
|
|
20
|
+
]
|
|
21
|
+
s.files = [
|
|
22
|
+
".document",
|
|
23
|
+
"Gemfile",
|
|
24
|
+
"Gemfile.lock",
|
|
25
|
+
"LICENSE.txt",
|
|
26
|
+
"README.md",
|
|
27
|
+
"Rakefile",
|
|
28
|
+
"VERSION",
|
|
29
|
+
"letsencrypt-rails-heroku.gemspec",
|
|
30
|
+
"lib/letsencrypt-rails-heroku.rb",
|
|
31
|
+
"lib/letsencrypt-rails-heroku/letsencrypt.rb",
|
|
32
|
+
"lib/letsencrypt-rails-heroku/middleware.rb",
|
|
33
|
+
"lib/letsencrypt-rails-heroku/railtie.rb",
|
|
34
|
+
"lib/tasks/letsencrypt.rake"
|
|
35
|
+
]
|
|
36
|
+
s.homepage = "http://github.com/jalada/letsencrypt-rails-heroku"
|
|
37
|
+
s.licenses = ["MIT"]
|
|
38
|
+
s.rubygems_version = "2.5.1"
|
|
39
|
+
s.summary = "Automatic LetsEncrypt certs in your Rails app on Heroku"
|
|
40
|
+
|
|
41
|
+
if s.respond_to? :specification_version then
|
|
42
|
+
s.specification_version = 4
|
|
43
|
+
|
|
44
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
|
45
|
+
s.add_runtime_dependency(%q<acme-client>, ["~> 0.3.7"])
|
|
46
|
+
s.add_runtime_dependency(%q<platform-api>, [">= 0"])
|
|
47
|
+
s.add_development_dependency(%q<shoulda>, [">= 0"])
|
|
48
|
+
s.add_development_dependency(%q<rdoc>, ["~> 3.12"])
|
|
49
|
+
s.add_development_dependency(%q<bundler>, ["~> 1.0"])
|
|
50
|
+
s.add_development_dependency(%q<juwelier>, ["~> 2.1.0"])
|
|
51
|
+
s.add_development_dependency(%q<simplecov>, [">= 0"])
|
|
52
|
+
else
|
|
53
|
+
s.add_dependency(%q<acme-client>, ["~> 0.3.7"])
|
|
54
|
+
s.add_dependency(%q<platform-api>, [">= 0"])
|
|
55
|
+
s.add_dependency(%q<shoulda>, [">= 0"])
|
|
56
|
+
s.add_dependency(%q<rdoc>, ["~> 3.12"])
|
|
57
|
+
s.add_dependency(%q<bundler>, ["~> 1.0"])
|
|
58
|
+
s.add_dependency(%q<juwelier>, ["~> 2.1.0"])
|
|
59
|
+
s.add_dependency(%q<simplecov>, [">= 0"])
|
|
60
|
+
end
|
|
61
|
+
else
|
|
62
|
+
s.add_dependency(%q<acme-client>, ["~> 0.3.7"])
|
|
63
|
+
s.add_dependency(%q<platform-api>, [">= 0"])
|
|
64
|
+
s.add_dependency(%q<shoulda>, [">= 0"])
|
|
65
|
+
s.add_dependency(%q<rdoc>, ["~> 3.12"])
|
|
66
|
+
s.add_dependency(%q<bundler>, ["~> 1.0"])
|
|
67
|
+
s.add_dependency(%q<juwelier>, ["~> 2.1.0"])
|
|
68
|
+
s.add_dependency(%q<simplecov>, [">= 0"])
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
module Letsencrypt
|
|
2
|
+
class << self
|
|
3
|
+
attr_accessor :configuration
|
|
4
|
+
end
|
|
5
|
+
|
|
6
|
+
def self.configure
|
|
7
|
+
self.configuration ||= Configuration.new
|
|
8
|
+
yield(configuration) if block_given?
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def self.challenge_configured?
|
|
12
|
+
configuration.acme_challenge_filename.present? &&
|
|
13
|
+
configuration.acme_challenge_filename.starts_with?(".well-known/") &&
|
|
14
|
+
configuration.acme_challenge_file_content.present?
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
class Configuration
|
|
18
|
+
attr_accessor :heroku_token, :heroku_app, :acme_email, :acme_domain, :acme_endpoint
|
|
19
|
+
|
|
20
|
+
# Not settable by user; part of the gem's behaviour.
|
|
21
|
+
attr_reader :acme_challenge_filename, :acme_challenge_file_content
|
|
22
|
+
|
|
23
|
+
def initialize
|
|
24
|
+
@heroku_token = ENV["HEROKU_TOKEN"]
|
|
25
|
+
@heroku_app = ENV["HEROKU_APP"]
|
|
26
|
+
@acme_email = ENV["ACME_EMAIL"]
|
|
27
|
+
@acme_domain = ENV["ACME_DOMAIN"]
|
|
28
|
+
@acme_endpoint = ENV["ACME_ENDPOINT"].presence || 'https://acme-v01.api.letsencrypt.org/'
|
|
29
|
+
@acme_challenge_filename = ENV["ACME_CHALLENGE_FILENAME"]
|
|
30
|
+
@acme_challenge_file_content = ENV["ACME_CHALLENGE_FILE_CONTENT"]
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def valid?
|
|
34
|
+
heroku_token.present? && heroku_app.present? && acme_email.present? &&
|
|
35
|
+
acme_domain.present?
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
module Letsencrypt
|
|
2
|
+
class Middleware
|
|
3
|
+
|
|
4
|
+
def initialize(app)
|
|
5
|
+
@app = app
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def call(env)
|
|
9
|
+
if Letsencrypt.challenge_configured? && env["PATH_INFO"] == "/#{Letsencrypt.configuration.acme_challenge_filename}"
|
|
10
|
+
return [200, {"Content-Type" => "text/plain"}, [Letsencrypt.configuration.acme_challenge_file_content]]
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
@app.call(env)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
require 'open-uri'
|
|
2
|
+
require 'openssl'
|
|
3
|
+
require 'acme-client'
|
|
4
|
+
require 'platform-api'
|
|
5
|
+
|
|
6
|
+
namespace :letsencrypt do
|
|
7
|
+
|
|
8
|
+
desc 'Renew your LetsEncrypt certificate'
|
|
9
|
+
task :renew => :environment do
|
|
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 a heroku_token, heroku_app, acmp_email and acme_domain configured either via a `Letsencrypt.configure` block in an initializer or as environment variables." unless Letsencrypt.configuration.valid?
|
|
12
|
+
|
|
13
|
+
# Set up Heroku client
|
|
14
|
+
heroku = PlatformAPI.connect_oauth Letsencrypt.configuration.heroku_token
|
|
15
|
+
heroku_app = Letsencrypt.configuration.heroku_app
|
|
16
|
+
|
|
17
|
+
# Create a private key
|
|
18
|
+
print "Creating account key..."
|
|
19
|
+
private_key = OpenSSL::PKey::RSA.new(4096)
|
|
20
|
+
puts "Done!"
|
|
21
|
+
|
|
22
|
+
client = Acme::Client.new(private_key: private_key, endpoint: Letsencrypt.configuration.acme_endpoint, connection_options: { request: { open_timeout: 5, timeout: 5 } })
|
|
23
|
+
|
|
24
|
+
print "Registering with LetsEncrypt..."
|
|
25
|
+
registration = client.register(contact: "mailto:#{Letsencrypt.configuration.acme_email}")
|
|
26
|
+
|
|
27
|
+
registration.agree_terms
|
|
28
|
+
puts "Done!"
|
|
29
|
+
|
|
30
|
+
authorization = client.authorize(domain: Letsencrypt.configuration.acme_domain)
|
|
31
|
+
challenge = authorization.http01
|
|
32
|
+
|
|
33
|
+
print "Setting config vars on Heroku..."
|
|
34
|
+
heroku.config_var.update(heroku_app, {
|
|
35
|
+
'ACME_CHALLENGE_FILENAME' => challenge.filename,
|
|
36
|
+
'ACME_CHALLENGE_FILE_CONTENT' => challenge.file_content
|
|
37
|
+
})
|
|
38
|
+
puts "Done!"
|
|
39
|
+
|
|
40
|
+
# Wait for request to go through
|
|
41
|
+
print "Giving config vars time to change..."
|
|
42
|
+
sleep(5)
|
|
43
|
+
puts "Done!"
|
|
44
|
+
|
|
45
|
+
# Wait for app to come up
|
|
46
|
+
print "Testing filename works (to bring up app)..."
|
|
47
|
+
|
|
48
|
+
# Get the domain name from Heroku
|
|
49
|
+
hostname = heroku.domain.list(heroku_app).first['hostname']
|
|
50
|
+
open("http://#{hostname}/#{challenge.filename}").read
|
|
51
|
+
puts "done!"
|
|
52
|
+
|
|
53
|
+
# Once you are ready to serve the confirmation request you can proceed.
|
|
54
|
+
challenge.request_verification # => true
|
|
55
|
+
challenge.verify_status # => 'pending'
|
|
56
|
+
|
|
57
|
+
# Wait a bit for the server to make the request, or just blink. It should be fast.
|
|
58
|
+
sleep(5)
|
|
59
|
+
|
|
60
|
+
unless challenge.verify_status == 'valid'
|
|
61
|
+
abort "Problem with verifying challenge."
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Unset temporary config vars. We don't care about waiting for this to
|
|
65
|
+
# restart
|
|
66
|
+
heroku.config_var.update(heroku_app, {
|
|
67
|
+
'ACME_CHALLENGE_FILENAME' => nil,
|
|
68
|
+
'ACME_CHALLENGE_FILE_CONTENT' => nil
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
# Create CSR
|
|
72
|
+
names = Letsencrypt.configuration.acme_domain.split(',').map(&:strip)
|
|
73
|
+
csr = Acme::Client::CertificateRequest.new(names: names)
|
|
74
|
+
|
|
75
|
+
# Get certificate
|
|
76
|
+
certificate = client.new_certificate(csr) # => #<Acme::Client::Certificate ....>
|
|
77
|
+
|
|
78
|
+
# Send certificates to Heroku via API
|
|
79
|
+
|
|
80
|
+
# First check for existing certificates:
|
|
81
|
+
certificates = heroku.sni_endpoint.list(heroku_app)
|
|
82
|
+
|
|
83
|
+
begin
|
|
84
|
+
if certificates.any?
|
|
85
|
+
print "Updating existing certificate #{certificates[0]['name']}..."
|
|
86
|
+
heroku.sni_endpoint.update(heroku_app, certificates[0]['name'], {
|
|
87
|
+
certificate_chain: certificate.fullchain_to_pem,
|
|
88
|
+
private_key: certificate.request.private_key.to_pem
|
|
89
|
+
})
|
|
90
|
+
puts "Done!"
|
|
91
|
+
else
|
|
92
|
+
print "Adding new certificate..."
|
|
93
|
+
heroku.sni_endpoint.create(heroku_app, {
|
|
94
|
+
certificate_chain: certificate.fullchain_to_pem,
|
|
95
|
+
private_key: certificate.request.private_key.to_pem
|
|
96
|
+
})
|
|
97
|
+
puts "Done!"
|
|
98
|
+
end
|
|
99
|
+
rescue Excon::Error::UnprocessableEntity => e
|
|
100
|
+
warn "Error adding certificate to Heroku. Response from Heroku’s API follows:"
|
|
101
|
+
abort e.response.body
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: letsencrypt-rails-heroku
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.2.3
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- David Somers
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2016-08-01 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: acme-client
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - "~>"
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: 0.3.7
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - "~>"
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: 0.3.7
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: platform-api
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - ">="
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '0'
|
|
34
|
+
type: :runtime
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - ">="
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '0'
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: shoulda
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - ">="
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '0'
|
|
48
|
+
type: :development
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - ">="
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '0'
|
|
55
|
+
- !ruby/object:Gem::Dependency
|
|
56
|
+
name: rdoc
|
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - "~>"
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '3.12'
|
|
62
|
+
type: :development
|
|
63
|
+
prerelease: false
|
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
+
requirements:
|
|
66
|
+
- - "~>"
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: '3.12'
|
|
69
|
+
- !ruby/object:Gem::Dependency
|
|
70
|
+
name: bundler
|
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
|
72
|
+
requirements:
|
|
73
|
+
- - "~>"
|
|
74
|
+
- !ruby/object:Gem::Version
|
|
75
|
+
version: '1.0'
|
|
76
|
+
type: :development
|
|
77
|
+
prerelease: false
|
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
79
|
+
requirements:
|
|
80
|
+
- - "~>"
|
|
81
|
+
- !ruby/object:Gem::Version
|
|
82
|
+
version: '1.0'
|
|
83
|
+
- !ruby/object:Gem::Dependency
|
|
84
|
+
name: juwelier
|
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
|
86
|
+
requirements:
|
|
87
|
+
- - "~>"
|
|
88
|
+
- !ruby/object:Gem::Version
|
|
89
|
+
version: 2.1.0
|
|
90
|
+
type: :development
|
|
91
|
+
prerelease: false
|
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
93
|
+
requirements:
|
|
94
|
+
- - "~>"
|
|
95
|
+
- !ruby/object:Gem::Version
|
|
96
|
+
version: 2.1.0
|
|
97
|
+
- !ruby/object:Gem::Dependency
|
|
98
|
+
name: simplecov
|
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
|
100
|
+
requirements:
|
|
101
|
+
- - ">="
|
|
102
|
+
- !ruby/object:Gem::Version
|
|
103
|
+
version: '0'
|
|
104
|
+
type: :development
|
|
105
|
+
prerelease: false
|
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
107
|
+
requirements:
|
|
108
|
+
- - ">="
|
|
109
|
+
- !ruby/object:Gem::Version
|
|
110
|
+
version: '0'
|
|
111
|
+
description: This gem automatically handles creation, renewal, and applying SSL certificates
|
|
112
|
+
from LetsEncrypt to your Heroku account.
|
|
113
|
+
email: david@jalada.co.uk
|
|
114
|
+
executables: []
|
|
115
|
+
extensions: []
|
|
116
|
+
extra_rdoc_files:
|
|
117
|
+
- LICENSE.txt
|
|
118
|
+
- README.md
|
|
119
|
+
files:
|
|
120
|
+
- ".document"
|
|
121
|
+
- Gemfile
|
|
122
|
+
- Gemfile.lock
|
|
123
|
+
- LICENSE.txt
|
|
124
|
+
- README.md
|
|
125
|
+
- Rakefile
|
|
126
|
+
- VERSION
|
|
127
|
+
- letsencrypt-rails-heroku.gemspec
|
|
128
|
+
- lib/letsencrypt-rails-heroku.rb
|
|
129
|
+
- lib/letsencrypt-rails-heroku/letsencrypt.rb
|
|
130
|
+
- lib/letsencrypt-rails-heroku/middleware.rb
|
|
131
|
+
- lib/letsencrypt-rails-heroku/railtie.rb
|
|
132
|
+
- lib/tasks/letsencrypt.rake
|
|
133
|
+
homepage: http://github.com/jalada/letsencrypt-rails-heroku
|
|
134
|
+
licenses:
|
|
135
|
+
- MIT
|
|
136
|
+
metadata: {}
|
|
137
|
+
post_install_message:
|
|
138
|
+
rdoc_options: []
|
|
139
|
+
require_paths:
|
|
140
|
+
- lib
|
|
141
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
142
|
+
requirements:
|
|
143
|
+
- - ">="
|
|
144
|
+
- !ruby/object:Gem::Version
|
|
145
|
+
version: '0'
|
|
146
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
147
|
+
requirements:
|
|
148
|
+
- - ">="
|
|
149
|
+
- !ruby/object:Gem::Version
|
|
150
|
+
version: '0'
|
|
151
|
+
requirements: []
|
|
152
|
+
rubyforge_project:
|
|
153
|
+
rubygems_version: 2.5.1
|
|
154
|
+
signing_key:
|
|
155
|
+
specification_version: 4
|
|
156
|
+
summary: Automatic LetsEncrypt certs in your Rails app on Heroku
|
|
157
|
+
test_files: []
|