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.
@@ -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
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
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
@@ -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
@@ -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.
@@ -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.
@@ -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,6 @@
1
+ require 'letsencrypt-rails-heroku/letsencrypt'
2
+ require 'letsencrypt-rails-heroku/middleware'
3
+
4
+ if defined?(Rails)
5
+ require 'letsencrypt-rails-heroku/railtie'
6
+ end
@@ -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,9 @@
1
+ class LetsencryptRailsHerokuRailtie < Rails::Railtie
2
+ config.before_configuration do
3
+ Letsencrypt.configure
4
+ end
5
+
6
+ rake_tasks do
7
+ load 'tasks/letsencrypt.rake'
8
+ end
9
+ 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: []