apartment_acme_client 0.0.1

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.
Files changed (33) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +305 -0
  4. data/Rakefile +29 -0
  5. data/app/assets/config/apartment_acme_client_manifest.js +2 -0
  6. data/app/assets/javascripts/apartment_acme_client/application.js +13 -0
  7. data/app/assets/stylesheets/apartment_acme_client/application.css +15 -0
  8. data/app/controllers/apartment_acme_client/application_controller.rb +5 -0
  9. data/app/controllers/apartment_acme_client/verifications_controller.rb +5 -0
  10. data/app/helpers/apartment_acme_client/application_helper.rb +4 -0
  11. data/app/jobs/apartment_acme_client/application_job.rb +4 -0
  12. data/app/mailers/apartment_acme_client/application_mailer.rb +6 -0
  13. data/app/models/apartment_acme_client/application_record.rb +5 -0
  14. data/app/models/apartment_acme_client/verifier.rb +38 -0
  15. data/app/views/layouts/apartment_acme_client/application.html.erb +14 -0
  16. data/config/routes.rb +3 -0
  17. data/lib/apartment_acme_client.rb +61 -0
  18. data/lib/apartment_acme_client/acme_client/proxy.rb +14 -0
  19. data/lib/apartment_acme_client/acme_client/real_client.rb +56 -0
  20. data/lib/apartment_acme_client/certificate_storage/proxy.rb +15 -0
  21. data/lib/apartment_acme_client/certificate_storage/s3.rb +74 -0
  22. data/lib/apartment_acme_client/domain_checker.rb +21 -0
  23. data/lib/apartment_acme_client/encryption.rb +134 -0
  24. data/lib/apartment_acme_client/engine.rb +5 -0
  25. data/lib/apartment_acme_client/file_manipulation/proxy.rb +13 -0
  26. data/lib/apartment_acme_client/file_manipulation/real.rb +37 -0
  27. data/lib/apartment_acme_client/nginx_configuration/proxy.rb +13 -0
  28. data/lib/apartment_acme_client/nginx_configuration/real.rb +128 -0
  29. data/lib/apartment_acme_client/railtie.rb +8 -0
  30. data/lib/apartment_acme_client/renewal_service.rb +22 -0
  31. data/lib/apartment_acme_client/version.rb +3 -0
  32. data/lib/tasks/encryption.rake +21 -0
  33. metadata +208 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: df3fe0ce816007d23a0fbbdfbdffba71e2239cb5
4
+ data.tar.gz: 03a37c82154522be994c86a27e070231b13d131a
5
+ SHA512:
6
+ metadata.gz: 39d796e4b59aa6e5b2773bac1e0eda6e7bce36b88aaf6ed77c474322b303b8ec4b0c36d8a27a084b60c8f279e8b74818833c59a1331031e99b402cc7c72a91ed
7
+ data.tar.gz: 3ab79be5d12515ecad5f305c6eea795325755c1450ddb34cc7ee2e05ecfc203285ed7b8b1fe2b81765334f615aa53cb9b225a083c735327ac0581395c62dc278
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2017 Robin Dunlop
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,305 @@
1
+ # ApartmentAcmeClient
2
+
3
+ Let's Encrypt interface for Multi-tenancy applications which respond by on many domains/subdomains (like Apartment)
4
+
5
+ If you have a single server which responds to many different domains, getting Let's Encrypt to provide you with a multi-domain Certificate is possible, but a lot of work.
6
+
7
+
8
+ **Note**: This only works up to 100 domains (https://letsencrypt.org/docs/rate-limits/)
9
+ Reference: https://community.letsencrypt.org/t/host-multiple-domains-with-a-single-certificate/20917/2
10
+
11
+ **Note**: Example usage with real server. Apartment gem with subdomains.
12
+ Reference: https://github.com/influitive/apartment#switch-on-subdomain
13
+
14
+ The goal of this gem is to solve the following problems:
15
+
16
+ 1) Make it easy to start using let's encrypt for multiple domains on one server
17
+ 1) Make it easy to periodically refresh a certificate which handles many domains
18
+ 1) Make it possible to add a new DNS entry which refers to the server, and request a cert which now also covers that new domain.
19
+ 1) Make it resilient, if a DNS record is removed, handle that by removing that domain from list requested for the cert.
20
+
21
+ **Example Situation**:
22
+
23
+ - Your application is known as example.com
24
+ - You allow users to create new accounts, and assign each account a separate subdomain,
25
+ - e.g. alice.example.com, bob.example.com, charlie.example.com
26
+ - You allow users to also whitelabel the service by buying their own domains, and setting up CNAME records:
27
+ - e.g. www.alice.com -> CNAME: alice.example.com
28
+ - e.g. bobrocks.com -> CNAME: bob.example.com
29
+
30
+ **What can ApartmentAcmeClient do?**
31
+
32
+ - Create a single Let's Encrypt SSL Certificate which covers all of:
33
+ - example.com
34
+ - alice.example.com
35
+ - bob.example.com
36
+ - charlie.example.com
37
+ - www.alice.com
38
+ - bobrocks.com
39
+
40
+
41
+ SSL Certificates
42
+ ----------------
43
+
44
+ In order to provide a secure connection, we are using [letsencrypt.org](https://letsencrypt.org) to
45
+ automatically create ssl certificates for the various domains which the server will run on. But, we are doing the validation/registration through the `acme-client` gem instead of using the lets-encrypt binary.
46
+
47
+ Periodically, we check all configured domains, and re-configure the nginx server to properly respond to any newly configured domain names. If we have a new domain name, we also request a new SSL certificate, enabling HTTPS for that domain.
48
+
49
+ How the Encryption process works:
50
+ ---------------------------------
51
+
52
+ 1. A list of domains which are served by this server is created.
53
+ 2. The list of all these domains is used to determine which ones are properly configured in DNS.
54
+ 3. We `authorize` each domain with LetsEncrypt
55
+ 4. A new SSL certificate is requested and installed3
56
+ 5. Nginx is restarted to pick up the new certificate.
57
+
58
+ Setting up crypto the first time:
59
+ ---------------------------------
60
+
61
+ 1. `rake encryption:create_crypto_client` - Register an account with LetsEncrypt
62
+ 2. `rake encryption:renew_and_update_certificate` - Authorize/create certificates
63
+ 3. `rake encryption:update_nginx_config` - re-write the nginx file to point at the certificates
64
+
65
+ At this point, the only thing necessary is to run `rake renew_and_update_certificate` on a regular basis, which will find new domains, authorize them, and get new SSL certs for them.
66
+
67
+ See below for a detailed explanation of "First Time Setup"
68
+
69
+ ## Testing things out
70
+
71
+ When setting this up the first time, it is recommended that you enable test-mode:
72
+ ```ruby
73
+ ApartmentAcmeClient.lets_encrypt_test_server_enabled = true
74
+ ```
75
+
76
+ so that all your requests are made against the test Let's Encrypt server.
77
+
78
+ This will also cause your DER and PEM files to be prefixed with "test_" to make it possible to have REAL and FAKE certs in parallel
79
+
80
+ Once you have an SSL Cert installed which is doing everything correctly (except not from the "REAL" server) you can restart the process.
81
+ - Set `ApartmentAcmeClient.lets_encrypt_test_server_enabled = false`
82
+
83
+ start at step 1 (`rake encryption:create_crypto_client`)....
84
+
85
+ -----------------------------------
86
+ ## Pre-requisites
87
+
88
+ In order for the application to function properly, it is assumed that the application is running in the following configuration:
89
+
90
+ - Nginx running as a service with a socket tunnel to the rails application
91
+ - Nginx can be restarted by `sudo service nginx restart`
92
+ - Rails application running, which can serve files from a `/public`-like directory
93
+
94
+ ## Installation
95
+
96
+ Add this line to your application's Gemfile:
97
+
98
+ ```ruby
99
+ gem 'apartment_acme_client'
100
+ ```
101
+
102
+ And then execute:
103
+
104
+ $ bundle
105
+
106
+ Or install it yourself as:
107
+
108
+ $ gem install apartment_acme_client
109
+
110
+ ## Usage
111
+
112
+ ### Mount the engine
113
+
114
+ We do this so that we can verify the site responds to a URL before we ask Lets Encrypt to verify the site.
115
+
116
+ ```ruby
117
+ mount ApartmentAcmeClient::Engine => '/aac' # you can define whatever path you want to mount the engine
118
+ ```
119
+
120
+ ### Configure the client
121
+
122
+ Create an initializer for the client. Usually `config/initializers/apartment_acme_client.rb`
123
+
124
+ Add the following configuration entries
125
+
126
+ #### Specify the domains to be checked
127
+
128
+ Define the code which will list the domains to check.
129
+
130
+ ```ruby
131
+ # Should return an array of domains (without http/https prefixes)
132
+
133
+ # It can be a straight array, or a callable object
134
+ ApartmentAcmeClient.domains_to_check = -> { SomeModel.all.map(&:subdomain) }
135
+
136
+ # e.g.
137
+ # ApartmentAcmeClient.domains_to_check = ["example.com", "alice.example.com", "alice.com"]
138
+ ```
139
+
140
+ #### Specify the common-name domain
141
+
142
+ This is used to identify the certificate requested, and should be the same from week-to-week.
143
+
144
+ This should be a URL which you control the DNS for, ensuring that it will ALWAYS be pointing at your application. (ie: not subject to the whims of your users).
145
+
146
+ Note: The nginx configuration will be configured to respond to `common_name` and `*.common_name` sources.
147
+
148
+ ```ruby
149
+ ApartmentAcmeClient.common_name = "example.com"
150
+ ```
151
+
152
+ #### Specify public folder
153
+
154
+ Specify where to put the "challenge" files which can be fetched by let's encrypt when validating the domains
155
+
156
+ **Note**: this folder should be not be derived from Rails.root, because that is a sym-link, which changes release to release.
157
+
158
+ ```ruby
159
+ ApartmentAcmeClient.public_folder = "/home/ec2-user/app/current/public" # not: Rails.root.join('public')
160
+ ```
161
+
162
+ #### Where to store the certificates on the server
163
+
164
+ Directory where to store certificates locally. This folder must persist between deployments, so that nginx can reference it permanently.
165
+ ```ruby
166
+ ApartmentAcmeClient.certificate_storage_folder = "/home/ec2-user/app/current/public/system" # not: Rails.root.join("public", "system")
167
+ ```
168
+
169
+ If you are using capistrano for deployments, add public/system to your `linked_dirs`
170
+
171
+ ```ruby
172
+ # deploy.rb
173
+ set :linked_dirs, %w[public/system]
174
+ ```
175
+
176
+ #### S3 Backup Storage settings
177
+
178
+ Each time a certificate is requested from Let's Encrypt, we also store it in S3 in case something happens to the server/filesystem.
179
+
180
+ In order for this to work, you must specify the aws_region and aws_bucket
181
+
182
+ ```ruby
183
+ ApartmentAcmeClient.aws_region = Rails.application.secrets.aws_region
184
+ ApartmentAcmeClient.aws_bucket = Rails.application.secrets.aws_bucket
185
+ ```
186
+
187
+ #### Nginx configuration settings
188
+
189
+ It is assumed that the /etc/nginx/nginx.conf file has a line like:
190
+ ```
191
+ http {
192
+ # Many lines....
193
+
194
+ include /etc/nginx/conf.d/*.conf;
195
+ }
196
+ ```
197
+
198
+ Then, the site's configuration is actually stored in /etc/nginx/conf.d/site.conf
199
+
200
+ So, the nginx_config_path would be
201
+ ```ruby
202
+ ApartmentAcmeClient.nginx_config_path = "/etc/nginx/conf.d/site.conf"
203
+ ```
204
+
205
+ Assuming that your application is running unicorn with a socket.
206
+
207
+ Example:
208
+ ```
209
+ # workers
210
+ worker_processes 1
211
+
212
+ # listen
213
+ listen "/tmp/unicorn-application.socket", backlog: 64
214
+
215
+ # Many more lines....
216
+ ```
217
+
218
+ ```ruby
219
+ ApartmentAcmeClient.socket_path = "/tmp/unicorn-application.socket"
220
+ ```
221
+
222
+ #### Force-SSL Options
223
+
224
+ If you ever choose to enable force-ssl on your server, you will need to set
225
+ the `ApartmentAcmeClient.verify_over_https = true` so that verification checks occur
226
+ over https instead of http
227
+
228
+ ------------------------------------------------------
229
+ ## First Time Setup
230
+
231
+ 1) Register with Let's Encrypt
232
+
233
+ Before we can make requests to Let's encrypt, we need to create a private key, which we will use for all future requests to Let's encrypt. To do this, run `rake encryption::create_crypto_client[my_email@example.com]` (replacing the email address with yours)
234
+
235
+ This will create a new private key, store it on S3, and register that key with let's encrypt for your e-mail address.
236
+
237
+ 2) Create your initial certificate
238
+
239
+ Initially, your nginx configuration will not reference any ssl certificate files, because you don't have any.
240
+ So the first thing you must do is request an initial certificate using `rake encryption:renew_and_update_certificate`
241
+
242
+ Once this is done, the newly acquired certificate will be stored on the server, for use by nginx in step 3.
243
+
244
+ 3) Tell Nginx where to get it's SSL certificates
245
+
246
+ The Nginx configuration must be updated to point to the SSL Certificate location.
247
+
248
+ run `rake encryption:update_nginx_config` in order to write the ngnix configuration file, and restart the nginx service.
249
+
250
+ At this point, the only thing necessary is to run `rake renew_and_update_certificate` on a regular basis, which will find new domains, authorize them, and get new SSL certs for them. It will also restart nginx, to have it pick up the new certificate.
251
+
252
+ ------------------------------------------------------
253
+ ### Schedule a weekly task to be run.
254
+
255
+ Each week, the certificates should be renewed. We have provided 2 ways to do this.
256
+
257
+ a rake task:
258
+ ```ruby
259
+ rake "encryption:renew_and_update_certificate"
260
+ ```
261
+
262
+ straight invocation:
263
+
264
+ ```ruby
265
+ ApartmentAcmeClient::RenewalService.run!
266
+ ```
267
+
268
+ Please use whatever scheduling service you wish in order to ensure that this runs periodically.
269
+ e.g. [whenever](https://github.com/javan/whenever)
270
+
271
+ ----------------------------------------------------------------------
272
+ ## Development
273
+
274
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
275
+
276
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
277
+
278
+ ## Contributing
279
+
280
+ Bug reports and pull requests are welcome on GitHub at https://github.com/rdunlop/apartment_acme_client.
281
+
282
+ ## License
283
+
284
+ The gem is available as open source under the terms of the [MIT License](MIT-LICENSE).
285
+
286
+ --------------------------------------------
287
+
288
+ ## Known issues
289
+
290
+ - Depends on the `aws-sdk-s3` S3 gem version "~> 1".
291
+ - It expects the hosting application has configured the AWS credentials.
292
+
293
+ e.g.:
294
+ ```ruby
295
+ Aws.config.update(
296
+ region: Rails.application.secrets.aws_region,
297
+ credentials: Aws::Credentials.new(
298
+ Rails.application.secrets.aws_access_key,
299
+ Rails.application.secrets.aws_secret_access_key
300
+ )
301
+ )
302
+ ```
303
+
304
+ TODO
305
+ - Get CI running
data/Rakefile ADDED
@@ -0,0 +1,29 @@
1
+ begin
2
+ require 'bundler/setup'
3
+ rescue LoadError
4
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5
+ end
6
+
7
+ require 'rdoc/task'
8
+
9
+ RDoc::Task.new(:rdoc) do |rdoc|
10
+ rdoc.rdoc_dir = 'rdoc'
11
+ rdoc.title = 'ApartmentAcmeClien'
12
+ rdoc.options << '--line-numbers'
13
+ rdoc.rdoc_files.include('README.md')
14
+ rdoc.rdoc_files.include('lib/**/*.rb')
15
+ end
16
+
17
+ APP_RAKEFILE = File.expand_path("../spec/dummy/Rakefile", __FILE__)
18
+ load 'rails/tasks/engine.rake'
19
+
20
+
21
+ load 'rails/tasks/statistics.rake'
22
+
23
+ require 'bundler/gem_tasks'
24
+
25
+ require "rspec/core/rake_task"
26
+
27
+ RSpec::Core::RakeTask.new(:spec)
28
+
29
+ task :default => :spec
@@ -0,0 +1,2 @@
1
+ //= link_directory ../javascripts/apartment_acme_client .js
2
+ //= link_directory ../stylesheets/apartment_acme_client .css
@@ -0,0 +1,13 @@
1
+ // This is a manifest file that'll be compiled into application.js, which will include all the files
2
+ // listed below.
3
+ //
4
+ // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
5
+ // or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path.
6
+ //
7
+ // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
8
+ // compiled file. JavaScript code in this file should be added after the last require_* statement.
9
+ //
10
+ // Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details
11
+ // about supported directives.
12
+ //
13
+ //= require_tree .
@@ -0,0 +1,15 @@
1
+ /*
2
+ * This is a manifest file that'll be compiled into application.css, which will include all the files
3
+ * listed below.
4
+ *
5
+ * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
6
+ * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
7
+ *
8
+ * You're free to add application-wide styles to this file and they'll appear at the bottom of the
9
+ * compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS
10
+ * files in this directory. Styles in this file should be added after the last require_* statement.
11
+ * It is generally better to create a new file per style scope.
12
+ *
13
+ *= require_tree .
14
+ *= require_self
15
+ */
@@ -0,0 +1,5 @@
1
+ module ApartmentAcmeClient
2
+ class ApplicationController < ActionController::Base
3
+ protect_from_forgery with: :exception
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ class ApartmentAcmeClient::VerificationsController < ApartmentAcmeClient::ApplicationController
2
+ def verify
3
+ render plain: "TRUE"
4
+ end
5
+ end
@@ -0,0 +1,4 @@
1
+ module ApartmentAcmeClient
2
+ module ApplicationHelper
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module ApartmentAcmeClient
2
+ class ApplicationJob < ActiveJob::Base
3
+ end
4
+ end
@@ -0,0 +1,6 @@
1
+ module ApartmentAcmeClient
2
+ class ApplicationMailer < ActionMailer::Base
3
+ default from: 'from@example.com'
4
+ layout 'mailer'
5
+ end
6
+ end
@@ -0,0 +1,5 @@
1
+ module ApartmentAcmeClient
2
+ class ApplicationRecord < ActiveRecord::Base
3
+ self.abstract_class = true
4
+ end
5
+ end
@@ -0,0 +1,38 @@
1
+ require 'net/http'
2
+
3
+ module ApartmentAcmeClient
4
+ class Verifier
5
+ attr_reader :url
6
+
7
+ def initialize(url)
8
+ @url = url
9
+ @verify_over_https = ApartmentAcmeClient.verify_over_https ? true : false
10
+ end
11
+
12
+ # Determine whether this alias is properly configured
13
+ # Causes makes a request to a remote server (which should be THIS server)
14
+ # and determines whether the request was properly received
15
+ def properly_configured?
16
+ options = {
17
+ open_timeout: 5,
18
+ use_ssl: @verify_over_https,
19
+ verify_mode: OpenSSL::SSL::VERIFY_NONE # because we might not have a valid cert yet
20
+ }
21
+ Net::HTTP.start(url, options) do |http|
22
+ # Because the engine could be mounted anywhere, we need to get the target
23
+ # path from the Engine Routes
24
+ verify_path = ApartmentAcmeClient::Engine.routes.url_helpers.verify_path
25
+ response = http.get(verify_path)
26
+
27
+ return false unless response.is_a?(Net::HTTPSuccess)
28
+
29
+ response.body == "TRUE"
30
+ end
31
+ rescue SocketError, Net::OpenTimeout, Errno::ECONNREFUSED
32
+ # SocketError if the server name doesn't exist in DNS
33
+ # OpenTimeout if no server responds
34
+ # ECONNREFUSED if the server responds with "No"
35
+ false
36
+ end
37
+ end
38
+ end