letsencrypt-rails-heroku 0.2.3
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
[![Gem Version](https://badge.fury.io/rb/letsencrypt-rails-heroku.svg)](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: []
|