apartment_acme_client 0.0.3 → 0.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
 - data/README.md +30 -16
 - data/Rakefile +0 -1
 - data/lib/apartment_acme_client.rb +6 -0
 - data/lib/apartment_acme_client/acme_client/real_client.rb +24 -18
 - data/lib/apartment_acme_client/certificate_storage/s3.rb +32 -17
 - data/lib/apartment_acme_client/dns_api/check_dns.rb +65 -0
 - data/lib/apartment_acme_client/dns_api/fake.rb +6 -0
 - data/lib/apartment_acme_client/dns_api/route53.rb +90 -0
 - data/lib/apartment_acme_client/encryption.rb +121 -29
 - data/lib/apartment_acme_client/nginx_configuration/real.rb +38 -40
 - data/lib/apartment_acme_client/renewal_service.rb +14 -9
 - data/lib/apartment_acme_client/version.rb +1 -1
 - metadata +44 -13
 
    
        checksums.yaml
    CHANGED
    
    | 
         @@ -1,7 +1,7 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            ---
         
     | 
| 
       2 
2 
     | 
    
         
             
            SHA256:
         
     | 
| 
       3 
     | 
    
         
            -
              metadata.gz:  
     | 
| 
       4 
     | 
    
         
            -
              data.tar.gz:  
     | 
| 
      
 3 
     | 
    
         
            +
              metadata.gz: 66eaebf9ae761526e149a5bedd237deee499a95107edb9ce3c1e3916e29c058b
         
     | 
| 
      
 4 
     | 
    
         
            +
              data.tar.gz: 4ab90728f136c38b453a24f54b760c84539c4333ae0cb24ba4122dcb54c124b6
         
     | 
| 
       5 
5 
     | 
    
         
             
            SHA512:
         
     | 
| 
       6 
     | 
    
         
            -
              metadata.gz:  
     | 
| 
       7 
     | 
    
         
            -
              data.tar.gz:  
     | 
| 
      
 6 
     | 
    
         
            +
              metadata.gz: ff22698eadfef2b8b2e6d0c25793ad5bb1228ff66262c039ef01b969aabb42458caf4d3e6f0f8485a4eca5273b878328661437fe4591f6d809317a44811f1ae1
         
     | 
| 
      
 7 
     | 
    
         
            +
              data.tar.gz: 870ed58bb9befb87b788fa1976f4fd73b052982cc1b57be889c2783a1ec55372ce24d155b286fd5937c24bb910bbb69ece5cf101b3e7e298d8117adc51d7f100
         
     | 
    
        data/README.md
    CHANGED
    
    | 
         @@ -15,29 +15,27 @@ The goal of this gem is to solve the following problems: 
     | 
|
| 
       15 
15 
     | 
    
         | 
| 
       16 
16 
     | 
    
         
             
            1) Make it easy to start using let's encrypt for multiple domains on one server
         
     | 
| 
       17 
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.
         
     | 
| 
      
 18 
     | 
    
         
            +
            1) Make it possible to add a new custom DNS entry which refers to the server, and request a cert which now also covers that new domain.
         
     | 
| 
      
 19 
     | 
    
         
            +
            1) Make it easy to request a wildcard cert as well as individual domain certs
         
     | 
| 
       19 
20 
     | 
    
         
             
            1) Make it resilient, if a DNS record is removed, handle that by removing that domain from list requested for the cert.
         
     | 
| 
       20 
21 
     | 
    
         | 
| 
       21 
22 
     | 
    
         
             
            **Example Situation**:
         
     | 
| 
       22 
23 
     | 
    
         | 
| 
       23 
     | 
    
         
            -
            - Your application is known as example.com
         
     | 
| 
      
 24 
     | 
    
         
            +
            - Your application is known as site.example.com
         
     | 
| 
       24 
25 
     | 
    
         
             
            - 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 
     | 
    
         
            +
              - e.g. alice.site.example.com, bob.site.example.com, charlie.site.example.com
         
     | 
| 
       26 
27 
     | 
    
         
             
            - 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
         
     | 
| 
      
 28 
     | 
    
         
            +
              - e.g. www.alice.com -> CNAME: alice.site.example.com
         
     | 
| 
      
 29 
     | 
    
         
            +
              - e.g. bobrocks.com -> CNAME: bob.site.example.com
         
     | 
| 
       29 
30 
     | 
    
         | 
| 
       30 
31 
     | 
    
         
             
            **What can ApartmentAcmeClient do?**
         
     | 
| 
       31 
32 
     | 
    
         | 
| 
       32 
33 
     | 
    
         
             
            - 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
         
     | 
| 
      
 34 
     | 
    
         
            +
              - site.example.com
         
     | 
| 
      
 35 
     | 
    
         
            +
              - *.site.example.com (which covers alice.site.example.com, bob.site.example.com, charlie.site.example.com)
         
     | 
| 
       37 
36 
     | 
    
         
             
              - www.alice.com
         
     | 
| 
       38 
37 
     | 
    
         
             
              - bobrocks.com
         
     | 
| 
       39 
38 
     | 
    
         | 
| 
       40 
     | 
    
         
            -
             
     | 
| 
       41 
39 
     | 
    
         
             
            SSL Certificates
         
     | 
| 
       42 
40 
     | 
    
         
             
            ----------------
         
     | 
| 
       43 
41 
     | 
    
         | 
| 
         @@ -70,6 +68,8 @@ See below for a detailed explanation of "First Time Setup" 
     | 
|
| 
       70 
68 
     | 
    
         | 
| 
       71 
69 
     | 
    
         
             
            When setting this up the first time, it is recommended that you enable test-mode:
         
     | 
| 
       72 
70 
     | 
    
         
             
            ```ruby
         
     | 
| 
      
 71 
     | 
    
         
            +
            # in config/initializers/apartment_acme_client.rb
         
     | 
| 
      
 72 
     | 
    
         
            +
             
     | 
| 
       73 
73 
     | 
    
         
             
            ApartmentAcmeClient.lets_encrypt_test_server_enabled = true
         
     | 
| 
       74 
74 
     | 
    
         
             
            ```
         
     | 
| 
       75 
75 
     | 
    
         | 
| 
         @@ -131,12 +131,27 @@ Define the code which will list the domains to check. 
     | 
|
| 
       131 
131 
     | 
    
         
             
            # Should return an array of domains (without http/https prefixes)
         
     | 
| 
       132 
132 
     | 
    
         | 
| 
       133 
133 
     | 
    
         
             
            # It can be a straight array, or a callable object
         
     | 
| 
       134 
     | 
    
         
            -
             
     | 
| 
      
 134 
     | 
    
         
            +
            # These should be all of the domains which are NOT
         
     | 
| 
      
 135 
     | 
    
         
            +
            # covered by the wildcard settings
         
     | 
| 
      
 136 
     | 
    
         
            +
            ApartmentAcmeClient.domains_to_check = -> { SomeModel.all.map(&:custom_domain) }
         
     | 
| 
      
 137 
     | 
    
         
            +
            ApartmentAcmeClient.wildcard_domain = "site.example.com" # optional element
         
     | 
| 
       135 
138 
     | 
    
         | 
| 
       136 
139 
     | 
    
         
             
            # e.g.
         
     | 
| 
       137 
140 
     | 
    
         
             
            # ApartmentAcmeClient.domains_to_check = ["example.com", "alice.example.com", "alice.com"]
         
     | 
| 
       138 
141 
     | 
    
         
             
            ```
         
     | 
| 
       139 
142 
     | 
    
         | 
| 
      
 143 
     | 
    
         
            +
            #### Wildcard domain
         
     | 
| 
      
 144 
     | 
    
         
            +
             
     | 
| 
      
 145 
     | 
    
         
            +
            You can request a wildcard certificate for a domain (or a subdomain). In order to do this, the system must be able to write to the DNS provider.
         
     | 
| 
      
 146 
     | 
    
         
            +
             
     | 
| 
      
 147 
     | 
    
         
            +
            Currently, only Route53 is supported as a DNS provider, and we use an `upsert` to write a TXT record to the system, in order to prove that we control the DNS for the domain.
         
     | 
| 
      
 148 
     | 
    
         
            +
             
     | 
| 
      
 149 
     | 
    
         
            +
            If you specify `wildcard_domain` (the domain on which to request a wildcard cert), we will request a wilcard cert for `*.<wildcard_domain>`, and use AWS Route53 API to perform the domain-authorization.
         
     | 
| 
      
 150 
     | 
    
         
            +
             
     | 
| 
      
 151 
     | 
    
         
            +
            The necessary permissions to be able to update the Route53 records for wildcard-cert update are:
         
     | 
| 
      
 152 
     | 
    
         
            +
            - route53:ListHostedZones
         
     | 
| 
      
 153 
     | 
    
         
            +
            - route53:ChangeResourceRecordSets
         
     | 
| 
      
 154 
     | 
    
         
            +
             
     | 
| 
       140 
155 
     | 
    
         
             
            #### Specify the common-name domain
         
     | 
| 
       141 
156 
     | 
    
         | 
| 
       142 
157 
     | 
    
         
             
            This is used to identify the certificate requested, and should be the same from week-to-week.
         
     | 
| 
         @@ -254,15 +269,14 @@ At this point, the only thing necessary is to run `rake encryption:renew_and_upd 
     | 
|
| 
       254 
269 
     | 
    
         | 
| 
       255 
270 
     | 
    
         
             
            Each week, the certificates should be renewed. We have provided 2 ways to do this.
         
     | 
| 
       256 
271 
     | 
    
         | 
| 
       257 
     | 
    
         
            -
             
     | 
| 
      
 272 
     | 
    
         
            +
            straight invocation:
         
     | 
| 
       258 
273 
     | 
    
         
             
            ```ruby
         
     | 
| 
       259 
     | 
    
         
            -
             
     | 
| 
      
 274 
     | 
    
         
            +
              ApartmentAcmeClient::RenewalService.run!
         
     | 
| 
       260 
275 
     | 
    
         
             
            ```
         
     | 
| 
       261 
276 
     | 
    
         | 
| 
       262 
     | 
    
         
            -
             
     | 
| 
       263 
     | 
    
         
            -
             
     | 
| 
      
 277 
     | 
    
         
            +
            we provide a helper rake task:
         
     | 
| 
       264 
278 
     | 
    
         
             
            ```ruby
         
     | 
| 
       265 
     | 
    
         
            -
             
     | 
| 
      
 279 
     | 
    
         
            +
            rake "encryption:renew_and_update_certificate"
         
     | 
| 
       266 
280 
     | 
    
         
             
            ```
         
     | 
| 
       267 
281 
     | 
    
         | 
| 
       268 
282 
     | 
    
         
             
            Please use whatever scheduling service you wish in order to ensure that this runs periodically.
         
     | 
    
        data/Rakefile
    CHANGED
    
    
| 
         @@ -7,6 +7,9 @@ require "apartment_acme_client/acme_client/proxy" 
     | 
|
| 
       7 
7 
     | 
    
         
             
            require "apartment_acme_client/acme_client/real_client"
         
     | 
| 
       8 
8 
     | 
    
         
             
            require "apartment_acme_client/certificate_storage/proxy"
         
     | 
| 
       9 
9 
     | 
    
         
             
            require "apartment_acme_client/certificate_storage/s3"
         
     | 
| 
      
 10 
     | 
    
         
            +
            require "apartment_acme_client/dns_api/check_dns"
         
     | 
| 
      
 11 
     | 
    
         
            +
            require "apartment_acme_client/dns_api/fake"
         
     | 
| 
      
 12 
     | 
    
         
            +
            require "apartment_acme_client/dns_api/route53"
         
     | 
| 
       10 
13 
     | 
    
         
             
            require "apartment_acme_client/nginx_configuration/proxy"
         
     | 
| 
       11 
14 
     | 
    
         
             
            require "apartment_acme_client/nginx_configuration/real"
         
     | 
| 
       12 
15 
     | 
    
         
             
            require "apartment_acme_client/file_manipulation/proxy"
         
     | 
| 
         @@ -27,6 +30,9 @@ module ApartmentAcmeClient 
     | 
|
| 
       27 
30 
     | 
    
         
             
                end
         
     | 
| 
       28 
31 
     | 
    
         
             
              end
         
     | 
| 
       29 
32 
     | 
    
         | 
| 
      
 33 
     | 
    
         
            +
              # An optional domain which will we request a wildcard certificate for
         
     | 
| 
      
 34 
     | 
    
         
            +
              mattr_accessor :wildcard_domain
         
     | 
| 
      
 35 
     | 
    
         
            +
             
     | 
| 
       30 
36 
     | 
    
         
             
              # The base domain, a domain which is always going to be accessible.
         
     | 
| 
       31 
37 
     | 
    
         
             
              # because we need a common domain to be used on each request.
         
     | 
| 
       32 
38 
     | 
    
         
             
              # if not defined, the first 'domain_to_check' which succeeds will be used
         
     | 
| 
         @@ -1,21 +1,23 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
       1 
3 
     | 
    
         
             
            require 'acme-client'
         
     | 
| 
       2 
4 
     | 
    
         | 
| 
       3 
5 
     | 
    
         
             
            module ApartmentAcmeClient
         
     | 
| 
       4 
6 
     | 
    
         
             
              module AcmeClient
         
     | 
| 
       5 
7 
     | 
    
         
             
                class RealClient
         
     | 
| 
       6 
     | 
    
         
            -
                   
     | 
| 
      
 8 
     | 
    
         
            +
                  attr_reader :csr_private_key
         
     | 
| 
      
 9 
     | 
    
         
            +
                  def initialize(acme_client_private_key:, csr_private_key:)
         
     | 
| 
       7 
10 
     | 
    
         
             
                    @client = Acme::Client.new(
         
     | 
| 
       8 
     | 
    
         
            -
                      private_key:  
     | 
| 
       9 
     | 
    
         
            -
                       
     | 
| 
      
 11 
     | 
    
         
            +
                      private_key: acme_client_private_key,
         
     | 
| 
      
 12 
     | 
    
         
            +
                      directory: server_directory
         
     | 
| 
       10 
13 
     | 
    
         
             
                    )
         
     | 
| 
      
 14 
     | 
    
         
            +
                    @csr_private_key = csr_private_key
         
     | 
| 
       11 
15 
     | 
    
         
             
                  end
         
     | 
| 
       12 
16 
     | 
    
         | 
| 
       13 
17 
     | 
    
         
             
                  def register(email)
         
     | 
| 
       14 
18 
     | 
    
         
             
                    # If the private key is not known to the server, we need to register it for the first time.
         
     | 
| 
       15 
     | 
    
         
            -
                     
     | 
| 
       16 
     | 
    
         
            -
             
     | 
| 
       17 
     | 
    
         
            -
                    # You may need to agree to the terms of service (that's up the to the server to require it or not but boulder does by default)
         
     | 
| 
       18 
     | 
    
         
            -
                    registration.agree_terms
         
     | 
| 
      
 19 
     | 
    
         
            +
                    account = @client.new_account(contact: "mailto:#{email}", terms_of_service_agreed: true)
         
     | 
| 
      
 20 
     | 
    
         
            +
                    Rollbar.info("New Let's Encrypt Account created with KID: #{account.kid}")
         
     | 
| 
       19 
21 
     | 
    
         | 
| 
       20 
22 
     | 
    
         
             
                    true
         
     | 
| 
       21 
23 
     | 
    
         
             
                  end
         
     | 
| 
         @@ -24,31 +26,35 @@ module ApartmentAcmeClient 
     | 
|
| 
       24 
26 
     | 
    
         
             
                    @client.authorize(domain: domain)
         
     | 
| 
       25 
27 
     | 
    
         
             
                  end
         
     | 
| 
       26 
28 
     | 
    
         | 
| 
      
 29 
     | 
    
         
            +
                  def new_order(identifiers:)
         
     | 
| 
      
 30 
     | 
    
         
            +
                    @client.new_order(identifiers: identifiers)
         
     | 
| 
      
 31 
     | 
    
         
            +
                  end
         
     | 
| 
      
 32 
     | 
    
         
            +
             
     | 
| 
       27 
33 
     | 
    
         
             
                  # Create a Certificate for our new set of domain names
         
     | 
| 
       28 
34 
     | 
    
         
             
                  # returns that certificate
         
     | 
| 
       29 
     | 
    
         
            -
                  def request_certificate(common_name:,  
     | 
| 
      
 35 
     | 
    
         
            +
                  def request_certificate(common_name:, names:, order:)
         
     | 
| 
       30 
36 
     | 
    
         
             
                    # We're going to need a certificate signing request. If not explicitly
         
     | 
| 
       31 
37 
     | 
    
         
             
                    # specified, the first name listed becomes the common name.
         
     | 
| 
       32 
     | 
    
         
            -
                    csr = Acme::Client::CertificateRequest.new(common_name: common_name, names:  
     | 
| 
       33 
     | 
    
         
            -
             
     | 
| 
       34 
     | 
    
         
            -
                     
     | 
| 
       35 
     | 
    
         
            -
             
     | 
| 
       36 
     | 
    
         
            -
             
     | 
| 
       37 
     | 
    
         
            -
                     
     | 
| 
      
 38 
     | 
    
         
            +
                    csr = Acme::Client::CertificateRequest.new(private_key: csr_private_key, common_name: common_name, names: names)
         
     | 
| 
      
 39 
     | 
    
         
            +
                    order.finalize(csr: csr)
         
     | 
| 
      
 40 
     | 
    
         
            +
                    while order.status == 'processing'
         
     | 
| 
      
 41 
     | 
    
         
            +
                      sleep(1)
         
     | 
| 
      
 42 
     | 
    
         
            +
                      order.reload
         
     | 
| 
      
 43 
     | 
    
         
            +
                    end
         
     | 
| 
       38 
44 
     | 
    
         | 
| 
       39 
     | 
    
         
            -
                    certificate
         
     | 
| 
      
 45 
     | 
    
         
            +
                    order.certificate
         
     | 
| 
       40 
46 
     | 
    
         
             
                  end
         
     | 
| 
       41 
47 
     | 
    
         | 
| 
       42 
48 
     | 
    
         
             
                  private
         
     | 
| 
       43 
49 
     | 
    
         | 
| 
       44 
     | 
    
         
            -
                  def  
     | 
| 
      
 50 
     | 
    
         
            +
                  def server_directory
         
     | 
| 
       45 
51 
     | 
    
         
             
                    # We need an ACME server to talk to, see github.com/letsencrypt/boulder
         
     | 
| 
       46 
52 
     | 
    
         
             
                    # WARNING: This endpoint is the production endpoint, which is rate limited and will produce valid certificates.
         
     | 
| 
       47 
53 
     | 
    
         
             
                    # You should probably use the staging endpoint for all your experimentation:
         
     | 
| 
       48 
54 
     | 
    
         
             
                    if ApartmentAcmeClient.lets_encrypt_test_server_enabled
         
     | 
| 
       49 
     | 
    
         
            -
                      'https://acme-staging.api.letsencrypt.org/'
         
     | 
| 
      
 55 
     | 
    
         
            +
                      'https://acme-staging-v02.api.letsencrypt.org/directory'
         
     | 
| 
       50 
56 
     | 
    
         
             
                    else
         
     | 
| 
       51 
     | 
    
         
            -
                      'https://acme- 
     | 
| 
      
 57 
     | 
    
         
            +
                      'https://acme-v02.api.letsencrypt.org/directory'
         
     | 
| 
       52 
58 
     | 
    
         
             
                    end
         
     | 
| 
       53 
59 
     | 
    
         
             
                  end
         
     | 
| 
       54 
60 
     | 
    
         
             
                end
         
     | 
| 
         @@ -1,36 +1,36 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
       1 
3 
     | 
    
         
             
            require 'aws-sdk-s3'
         
     | 
| 
       2 
4 
     | 
    
         | 
| 
       3 
5 
     | 
    
         
             
            module ApartmentAcmeClient
         
     | 
| 
       4 
6 
     | 
    
         
             
              module CertificateStorage
         
     | 
| 
       5 
7 
     | 
    
         
             
                class S3
         
     | 
| 
       6 
8 
     | 
    
         
             
                  def initialize
         
     | 
| 
       7 
     | 
    
         
            -
                    @base_prefix = if ApartmentAcmeClient 
     | 
| 
       8 
     | 
    
         
            -
             
     | 
| 
       9 
     | 
    
         
            -
             
     | 
| 
       10 
     | 
    
         
            -
             
     | 
| 
       11 
     | 
    
         
            -
             
     | 
| 
      
 9 
     | 
    
         
            +
                    @base_prefix = if ApartmentAcmeClient.lets_encrypt_test_server_enabled
         
     | 
| 
      
 10 
     | 
    
         
            +
                                     TEST_PREFIX
         
     | 
| 
      
 11 
     | 
    
         
            +
                                   else
         
     | 
| 
      
 12 
     | 
    
         
            +
                                     ''
         
     | 
| 
      
 13 
     | 
    
         
            +
                                   end
         
     | 
| 
       12 
14 
     | 
    
         
             
                  end
         
     | 
| 
       13 
15 
     | 
    
         | 
| 
       14 
     | 
    
         
            -
                  ENCRYPTION_S3_NAME = 'server_encryption_client_private_key.der' 
     | 
| 
      
 16 
     | 
    
         
            +
                  ENCRYPTION_S3_NAME = 'server_encryption_client_private_key.der'
         
     | 
| 
      
 17 
     | 
    
         
            +
                  CSR_ENCRYPTION_S3_NAME = 'csr_server_encryption_client_private_key.der'
         
     | 
| 
       15 
18 
     | 
    
         | 
| 
       16 
     | 
    
         
            -
                  def  
     | 
| 
       17 
     | 
    
         
            -
                     
     | 
| 
       18 
     | 
    
         
            -
                     
     | 
| 
       19 
     | 
    
         
            -
             
     | 
| 
       20 
     | 
    
         
            -
                    File.write(cert_path("chain.pem"), certificate.chain_to_pem)
         
     | 
| 
       21 
     | 
    
         
            -
                    File.write(cert_path("fullchain.pem"), certificate.fullchain_to_pem)
         
     | 
| 
      
 19 
     | 
    
         
            +
                  def store_certificate_string(certificate_string)
         
     | 
| 
      
 20 
     | 
    
         
            +
                    File.write(cert_path('cert.pem'), certificate_string)
         
     | 
| 
      
 21 
     | 
    
         
            +
                    store_s3_file(derived_filename('cert.pem'), certificate_string)
         
     | 
| 
      
 22 
     | 
    
         
            +
                  end
         
     | 
| 
       22 
23 
     | 
    
         | 
| 
       23 
     | 
    
         
            -
             
     | 
| 
       24 
     | 
    
         
            -
                     
     | 
| 
       25 
     | 
    
         
            -
                    store_s3_file(derived_filename( 
     | 
| 
       26 
     | 
    
         
            -
                    store_s3_file(derived_filename("fullchain.pem"), certificate.fullchain_to_pem)
         
     | 
| 
      
 24 
     | 
    
         
            +
                  def store_csr_private_key_string(csr_private_key_string)
         
     | 
| 
      
 25 
     | 
    
         
            +
                    File.write(cert_path('privkey.pem'), csr_private_key_string)
         
     | 
| 
      
 26 
     | 
    
         
            +
                    store_s3_file(derived_filename('privkey.pem'), csr_private_key_string)
         
     | 
| 
       27 
27 
     | 
    
         
             
                  end
         
     | 
| 
       28 
28 
     | 
    
         | 
| 
       29 
29 
     | 
    
         
             
                  # do we have a certificate on this server?
         
     | 
| 
       30 
30 
     | 
    
         
             
                  # We cannot start nginx when it is pointing at a non-existing certificate,
         
     | 
| 
       31 
31 
     | 
    
         
             
                  # so we need to check
         
     | 
| 
       32 
32 
     | 
    
         
             
                  def cert_exists?
         
     | 
| 
       33 
     | 
    
         
            -
                    File.exist?(cert_path( 
     | 
| 
      
 33 
     | 
    
         
            +
                    File.exist?(cert_path('privkey.pem'))
         
     | 
| 
       34 
34 
     | 
    
         
             
                  end
         
     | 
| 
       35 
35 
     | 
    
         | 
| 
       36 
36 
     | 
    
         
             
                  def private_key
         
     | 
| 
         @@ -40,17 +40,32 @@ module ApartmentAcmeClient 
     | 
|
| 
       40 
40 
     | 
    
         
             
                    s3_object.get.body.read
         
     | 
| 
       41 
41 
     | 
    
         
             
                  end
         
     | 
| 
       42 
42 
     | 
    
         | 
| 
      
 43 
     | 
    
         
            +
                  def csr_private_key
         
     | 
| 
      
 44 
     | 
    
         
            +
                    s3_object = s3_file(csr_private_key_s3_filename)
         
     | 
| 
      
 45 
     | 
    
         
            +
                    return nil unless s3_object.exists?
         
     | 
| 
      
 46 
     | 
    
         
            +
             
     | 
| 
      
 47 
     | 
    
         
            +
                    s3_object.get.body.read
         
     | 
| 
      
 48 
     | 
    
         
            +
                  end
         
     | 
| 
      
 49 
     | 
    
         
            +
             
     | 
| 
       43 
50 
     | 
    
         
             
                  # saves a private key to s3
         
     | 
| 
       44 
51 
     | 
    
         
             
                  def save_private_key(private_key)
         
     | 
| 
       45 
52 
     | 
    
         
             
                    store_s3_file(private_key_s3_filename, private_key.to_der)
         
     | 
| 
       46 
53 
     | 
    
         
             
                  end
         
     | 
| 
       47 
54 
     | 
    
         | 
| 
      
 55 
     | 
    
         
            +
                  def save_csr_private_key(private_key)
         
     | 
| 
      
 56 
     | 
    
         
            +
                    store_s3_file(csr_private_key_s3_filename, private_key.to_der)
         
     | 
| 
      
 57 
     | 
    
         
            +
                  end
         
     | 
| 
      
 58 
     | 
    
         
            +
             
     | 
| 
       48 
59 
     | 
    
         
             
                  private
         
     | 
| 
       49 
60 
     | 
    
         | 
| 
       50 
61 
     | 
    
         
             
                  def private_key_s3_filename
         
     | 
| 
       51 
62 
     | 
    
         
             
                    derived_filename(ENCRYPTION_S3_NAME)
         
     | 
| 
       52 
63 
     | 
    
         
             
                  end
         
     | 
| 
       53 
64 
     | 
    
         | 
| 
      
 65 
     | 
    
         
            +
                  def csr_private_key_s3_filename
         
     | 
| 
      
 66 
     | 
    
         
            +
                    derived_filename(CSR_ENCRYPTION_S3_NAME)
         
     | 
| 
      
 67 
     | 
    
         
            +
                  end
         
     | 
| 
      
 68 
     | 
    
         
            +
             
     | 
| 
       54 
69 
     | 
    
         
             
                  def derived_filename(filename)
         
     | 
| 
       55 
70 
     | 
    
         
             
                    "#{@base_prefix}#{filename}"
         
     | 
| 
       56 
71 
     | 
    
         
             
                  end
         
     | 
| 
         @@ -0,0 +1,65 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module ApartmentAcmeClient
         
     | 
| 
      
 2 
     | 
    
         
            +
              module DnsApi
         
     | 
| 
      
 3 
     | 
    
         
            +
                # Check to see if a particular DNS record is
         
     | 
| 
      
 4 
     | 
    
         
            +
                # present.
         
     | 
| 
      
 5 
     | 
    
         
            +
                class CheckDns
         
     | 
| 
      
 6 
     | 
    
         
            +
                  attr_reader :root_domain, :dns_record
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
                  def initialize(root_domain, dns_record)
         
     | 
| 
      
 9 
     | 
    
         
            +
                    # ensure we only have the TLD, not a subdomain
         
     | 
| 
      
 10 
     | 
    
         
            +
                    @root_domain = root_domain.split(".").last(2).join(".")
         
     | 
| 
      
 11 
     | 
    
         
            +
                    @dns_record = dns_record
         
     | 
| 
      
 12 
     | 
    
         
            +
                  end
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
                  # Search DNS recodrs for any entries which are TXT and include
         
     | 
| 
      
 15 
     | 
    
         
            +
                  # the text 'value'
         
     | 
| 
      
 16 
     | 
    
         
            +
                  def check_dns(value)
         
     | 
| 
      
 17 
     | 
    
         
            +
                    valid = true
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
                    nameservers.each do |nameserver|
         
     | 
| 
      
 20 
     | 
    
         
            +
                      begin
         
     | 
| 
      
 21 
     | 
    
         
            +
                        records = Resolv::DNS.open(nameserver: nameserver) do |dns|
         
     | 
| 
      
 22 
     | 
    
         
            +
                          dns.getresources(
         
     | 
| 
      
 23 
     | 
    
         
            +
                            dns_record,
         
     | 
| 
      
 24 
     | 
    
         
            +
                            Resolv::DNS::Resource::IN::TXT
         
     | 
| 
      
 25 
     | 
    
         
            +
                          )
         
     | 
| 
      
 26 
     | 
    
         
            +
                        end
         
     | 
| 
      
 27 
     | 
    
         
            +
                        records = records.map(&:strings).flatten
         
     | 
| 
      
 28 
     | 
    
         
            +
                        valid = records.include?(value)
         
     | 
| 
      
 29 
     | 
    
         
            +
                      rescue Resolv::ResolvError
         
     | 
| 
      
 30 
     | 
    
         
            +
                        return false
         
     | 
| 
      
 31 
     | 
    
         
            +
                      end
         
     | 
| 
      
 32 
     | 
    
         
            +
                      return false unless valid
         
     | 
| 
      
 33 
     | 
    
         
            +
                    end
         
     | 
| 
      
 34 
     | 
    
         
            +
             
     | 
| 
      
 35 
     | 
    
         
            +
                    valid
         
     | 
| 
      
 36 
     | 
    
         
            +
                  end
         
     | 
| 
      
 37 
     | 
    
         
            +
             
     | 
| 
      
 38 
     | 
    
         
            +
                  def nameservers
         
     | 
| 
      
 39 
     | 
    
         
            +
                    return @nameservers if defined?(@nameservers)
         
     | 
| 
      
 40 
     | 
    
         
            +
             
     | 
| 
      
 41 
     | 
    
         
            +
                    @nameservers = []
         
     | 
| 
      
 42 
     | 
    
         
            +
                    Resolv::DNS.open(nameserver: '8.8.8.8') do |dns|
         
     | 
| 
      
 43 
     | 
    
         
            +
                      while nameservers.empty?
         
     | 
| 
      
 44 
     | 
    
         
            +
                        @nameservers = dns.getresources(
         
     | 
| 
      
 45 
     | 
    
         
            +
                          root_domain,
         
     | 
| 
      
 46 
     | 
    
         
            +
                          Resolv::DNS::Resource::IN::NS
         
     | 
| 
      
 47 
     | 
    
         
            +
                        ).map(&:name).map(&:to_s)
         
     | 
| 
      
 48 
     | 
    
         
            +
                      end
         
     | 
| 
      
 49 
     | 
    
         
            +
                    end
         
     | 
| 
      
 50 
     | 
    
         
            +
             
     | 
| 
      
 51 
     | 
    
         
            +
                    @nameservers
         
     | 
| 
      
 52 
     | 
    
         
            +
                  end
         
     | 
| 
      
 53 
     | 
    
         
            +
             
     | 
| 
      
 54 
     | 
    
         
            +
                  def wait_for_present(value, timeout_seconds: 60)
         
     | 
| 
      
 55 
     | 
    
         
            +
                    time = 1
         
     | 
| 
      
 56 
     | 
    
         
            +
                    until check_dns(value)
         
     | 
| 
      
 57 
     | 
    
         
            +
                      puts "Waiting for DNS to update"
         
     | 
| 
      
 58 
     | 
    
         
            +
                      sleep 1
         
     | 
| 
      
 59 
     | 
    
         
            +
                      time += 1
         
     | 
| 
      
 60 
     | 
    
         
            +
                      break if time > timeout_seconds
         
     | 
| 
      
 61 
     | 
    
         
            +
                    end
         
     | 
| 
      
 62 
     | 
    
         
            +
                  end
         
     | 
| 
      
 63 
     | 
    
         
            +
                end
         
     | 
| 
      
 64 
     | 
    
         
            +
              end
         
     | 
| 
      
 65 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,90 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'aws-sdk-route53'
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module ApartmentAcmeClient
         
     | 
| 
      
 4 
     | 
    
         
            +
              module DnsApi
         
     | 
| 
      
 5 
     | 
    
         
            +
                # based on https://www.petekeen.net/lets-encrypt-without-certbot
         
     | 
| 
      
 6 
     | 
    
         
            +
                class Route53
         
     | 
| 
      
 7 
     | 
    
         
            +
                  # The domain being requested for DNS update
         
     | 
| 
      
 8 
     | 
    
         
            +
                  # e.g. "site.example.com"
         
     | 
| 
      
 9 
     | 
    
         
            +
                  attr_reader :requested_domain
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
                  # the DNS TXT record label (full label, including domain)
         
     | 
| 
      
 12 
     | 
    
         
            +
                  attr_reader :label
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
                  # will be TXT
         
     | 
| 
      
 15 
     | 
    
         
            +
                  attr_reader :record_type
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
                  # array of value keys to be written
         
     | 
| 
      
 18 
     | 
    
         
            +
                  # (for wildcard certs, you'll have one for *.example.com, and one for example.com)
         
     | 
| 
      
 19 
     | 
    
         
            +
                  # e.g. ["One", "Two"]
         
     | 
| 
      
 20 
     | 
    
         
            +
                  attr_reader :values
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
                  def initialize(requested_domain:, dns_record_label:, record_type:, values:)
         
     | 
| 
      
 23 
     | 
    
         
            +
                    @requested_domain = requested_domain
         
     | 
| 
      
 24 
     | 
    
         
            +
                    @label = dns_record_label
         
     | 
| 
      
 25 
     | 
    
         
            +
                    @record_type = record_type
         
     | 
| 
      
 26 
     | 
    
         
            +
                    @values = values
         
     | 
| 
      
 27 
     | 
    
         
            +
                  end
         
     | 
| 
      
 28 
     | 
    
         
            +
             
     | 
| 
      
 29 
     | 
    
         
            +
                  # NOTE:
         
     | 
| 
      
 30 
     | 
    
         
            +
                  # if you get error like:
         
     | 
| 
      
 31 
     | 
    
         
            +
                  #
         
     | 
| 
      
 32 
     | 
    
         
            +
                  # "Invalid Resource Record: FATAL problem:
         
     | 
| 
      
 33 
     | 
    
         
            +
                  # InvalidCharacterString
         
     | 
| 
      
 34 
     | 
    
         
            +
                  # (Value should be enclosed in quotation marks) encountered with <value>"
         
     | 
| 
      
 35 
     | 
    
         
            +
                  #
         
     | 
| 
      
 36 
     | 
    
         
            +
                  # this means that the "Value" should include escape quotes.
         
     | 
| 
      
 37 
     | 
    
         
            +
                  # e.g. values: ["\"Something\"", "\"Other Thing\""]
         
     | 
| 
      
 38 
     | 
    
         
            +
                  def write_record
         
     | 
| 
      
 39 
     | 
    
         
            +
                    route53.change_resource_record_sets(options)
         
     | 
| 
      
 40 
     | 
    
         
            +
                  end
         
     | 
| 
      
 41 
     | 
    
         
            +
             
     | 
| 
      
 42 
     | 
    
         
            +
                  private
         
     | 
| 
      
 43 
     | 
    
         
            +
             
     | 
| 
      
 44 
     | 
    
         
            +
                  def options
         
     | 
| 
      
 45 
     | 
    
         
            +
                    change = {
         
     | 
| 
      
 46 
     | 
    
         
            +
                      action: 'UPSERT',
         
     | 
| 
      
 47 
     | 
    
         
            +
                      resource_record_set: {
         
     | 
| 
      
 48 
     | 
    
         
            +
                        name: label,
         
     | 
| 
      
 49 
     | 
    
         
            +
                        type: record_type,
         
     | 
| 
      
 50 
     | 
    
         
            +
                        ttl: 1,
         
     | 
| 
      
 51 
     | 
    
         
            +
                        resource_records: resource_record_values
         
     | 
| 
      
 52 
     | 
    
         
            +
                      }
         
     | 
| 
      
 53 
     | 
    
         
            +
                    }
         
     | 
| 
      
 54 
     | 
    
         
            +
             
     | 
| 
      
 55 
     | 
    
         
            +
                    {
         
     | 
| 
      
 56 
     | 
    
         
            +
                      hosted_zone_id: zone.id,
         
     | 
| 
      
 57 
     | 
    
         
            +
                      change_batch: {
         
     | 
| 
      
 58 
     | 
    
         
            +
                        changes: [change]
         
     | 
| 
      
 59 
     | 
    
         
            +
                      }
         
     | 
| 
      
 60 
     | 
    
         
            +
                    }
         
     | 
| 
      
 61 
     | 
    
         
            +
                  end
         
     | 
| 
      
 62 
     | 
    
         
            +
             
     | 
| 
      
 63 
     | 
    
         
            +
                  def root_domain
         
     | 
| 
      
 64 
     | 
    
         
            +
                    requested_domain.split(".").last(2).join(".")
         
     | 
| 
      
 65 
     | 
    
         
            +
                  end
         
     | 
| 
      
 66 
     | 
    
         
            +
             
     | 
| 
      
 67 
     | 
    
         
            +
                  def zone
         
     | 
| 
      
 68 
     | 
    
         
            +
                    @zone = route53.list_hosted_zones(max_items: 100)
         
     | 
| 
      
 69 
     | 
    
         
            +
                                   .hosted_zones
         
     | 
| 
      
 70 
     | 
    
         
            +
                                   .detect { |z| z.name = "#{root_domain}." }
         
     | 
| 
      
 71 
     | 
    
         
            +
                  end
         
     | 
| 
      
 72 
     | 
    
         
            +
             
     | 
| 
      
 73 
     | 
    
         
            +
                  def route53
         
     | 
| 
      
 74 
     | 
    
         
            +
                    # Note: The `region` doesn't matter, because Route53 is global.
         
     | 
| 
      
 75 
     | 
    
         
            +
                    @route53 ||= Aws::Route53::Client.new(region: 'us-east-1')
         
     | 
| 
      
 76 
     | 
    
         
            +
                  end
         
     | 
| 
      
 77 
     | 
    
         
            +
             
     | 
| 
      
 78 
     | 
    
         
            +
                  # createt an AwsRoute53 upsert with multiple value entries
         
     | 
| 
      
 79 
     | 
    
         
            +
                  def resource_record_values
         
     | 
| 
      
 80 
     | 
    
         
            +
                    values.map do |value|
         
     | 
| 
      
 81 
     | 
    
         
            +
                      if value.include?("\"")
         
     | 
| 
      
 82 
     | 
    
         
            +
                        { value: value }
         
     | 
| 
      
 83 
     | 
    
         
            +
                      else
         
     | 
| 
      
 84 
     | 
    
         
            +
                        { value: "\"#{value}\"" }
         
     | 
| 
      
 85 
     | 
    
         
            +
                      end
         
     | 
| 
      
 86 
     | 
    
         
            +
                    end
         
     | 
| 
      
 87 
     | 
    
         
            +
                  end
         
     | 
| 
      
 88 
     | 
    
         
            +
                end
         
     | 
| 
      
 89 
     | 
    
         
            +
              end
         
     | 
| 
      
 90 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -1,11 +1,11 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
       1 
3 
     | 
    
         
             
            require 'openssl'
         
     | 
| 
       2 
4 
     | 
    
         | 
| 
       3 
5 
     | 
    
         
             
            # Initially, the system is only accessible via subdomain.example.com
         
     | 
| 
       4 
6 
     | 
    
         
             
            # But, as we add more Conventions, we want to be able to access those also,
         
     | 
| 
       5 
7 
     | 
    
         
             
            # thus we will need:
         
     | 
| 
       6 
     | 
    
         
            -
            # -  
     | 
| 
       7 
     | 
    
         
            -
            # - b.subdomain.example.com
         
     | 
| 
       8 
     | 
    
         
            -
            # - c.subdomain.example.com
         
     | 
| 
      
 8 
     | 
    
         
            +
            # - *.subdomain.example.com
         
     | 
| 
       9 
9 
     | 
    
         
             
            #
         
     | 
| 
       10 
10 
     | 
    
         
             
            # Also, each convention may add an "alias" for their convention, like:
         
     | 
| 
       11 
11 
     | 
    
         
             
            # - www.naucc.com
         
     | 
| 
         @@ -21,20 +21,21 @@ require 'openssl' 
     | 
|
| 
       21 
21 
     | 
    
         
             
            #
         
     | 
| 
       22 
22 
     | 
    
         
             
            # Manage the encryption of the website (https).
         
     | 
| 
       23 
23 
     | 
    
         
             
            module ApartmentAcmeClient
         
     | 
| 
       24 
     | 
    
         
            -
              class Encryption
         
     | 
| 
      
 24 
     | 
    
         
            +
              class Encryption # rubocop:disable Metrics/ClassLength
         
     | 
| 
       25 
25 
     | 
    
         
             
                def initialize
         
     | 
| 
       26 
26 
     | 
    
         
             
                  @certificate_storage = ApartmentAcmeClient::CertificateStorage::Proxy.singleton
         
     | 
| 
       27 
27 
     | 
    
         
             
                end
         
     | 
| 
       28 
28 
     | 
    
         | 
| 
       29 
29 
     | 
    
         
             
                # Largely based on https://github.com/unixcharles/acme-client documentation
         
     | 
| 
       30 
30 
     | 
    
         
             
                def register_new(email)
         
     | 
| 
       31 
     | 
    
         
            -
                  raise StandardError.new( 
     | 
| 
      
 31 
     | 
    
         
            +
                  raise StandardError.new('Private key already exists') unless @certificate_storage.private_key.nil?
         
     | 
| 
       32 
32 
     | 
    
         | 
| 
       33 
33 
     | 
    
         
             
                  private_key = create_private_key
         
     | 
| 
       34 
34 
     | 
    
         | 
| 
       35 
35 
     | 
    
         
             
                  # Initialize the client
         
     | 
| 
       36 
36 
     | 
    
         
             
                  new_client = ApartmentAcmeClient::AcmeClient::Proxy.singleton(
         
     | 
| 
       37 
     | 
    
         
            -
                     
     | 
| 
      
 37 
     | 
    
         
            +
                    acme_client_private_key: private_key,
         
     | 
| 
      
 38 
     | 
    
         
            +
                    csr_private_key: nil, # not needed for 'register' call
         
     | 
| 
       38 
39 
     | 
    
         
             
                  )
         
     | 
| 
       39 
40 
     | 
    
         | 
| 
       40 
41 
     | 
    
         
             
                  new_client.register(email)
         
     | 
| 
         @@ -42,22 +43,75 @@ module ApartmentAcmeClient 
     | 
|
| 
       42 
43 
     | 
    
         
             
                  @certificate_storage.save_private_key(private_key)
         
     | 
| 
       43 
44 
     | 
    
         
             
                end
         
     | 
| 
       44 
45 
     | 
    
         | 
| 
       45 
     | 
    
         
            -
                 
     | 
| 
       46 
     | 
    
         
            -
             
     | 
| 
       47 
     | 
    
         
            -
             
     | 
| 
      
 46 
     | 
    
         
            +
                # Authorize a wildcard cert domain.
         
     | 
| 
      
 47 
     | 
    
         
            +
                # to do this, we have to write to the Amazon Route53 DNS entry
         
     | 
| 
      
 48 
     | 
    
         
            +
                # params:
         
     | 
| 
      
 49 
     | 
    
         
            +
                #  - authorizations - a list of authorizations, which may be http or dns based (ignore the non-wildcard ones)
         
     | 
| 
      
 50 
     | 
    
         
            +
                #  - wildcard_domain - the url of the wildcard's base domain (e.g. "site.example.com")
         
     | 
| 
      
 51 
     | 
    
         
            +
                def authorize_domains_with_dns(authorizations, wildcard_domain:) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
         
     | 
| 
      
 52 
     | 
    
         
            +
                  label = nil
         
     | 
| 
      
 53 
     | 
    
         
            +
                  record_type = nil
         
     | 
| 
      
 54 
     | 
    
         
            +
                  values = []
         
     | 
| 
      
 55 
     | 
    
         
            +
             
     | 
| 
      
 56 
     | 
    
         
            +
                  dns_authorizations = []
         
     | 
| 
      
 57 
     | 
    
         
            +
                  authorizations.each do |domain_authorization|
         
     | 
| 
      
 58 
     | 
    
         
            +
                    next unless domain_authorization.wildcard || domain_authorization.http.nil?
         
     | 
| 
      
 59 
     | 
    
         
            +
             
     | 
| 
      
 60 
     | 
    
         
            +
                    dns_authorizations << domain_authorization.dns
         
     | 
| 
      
 61 
     | 
    
         
            +
                  end
         
     | 
| 
      
 62 
     | 
    
         
            +
             
     | 
| 
      
 63 
     | 
    
         
            +
                  dns_authorizations.each do |authorization|
         
     | 
| 
      
 64 
     | 
    
         
            +
                    label         = "#{authorization.record_name}.#{wildcard_domain}"
         
     | 
| 
      
 65 
     | 
    
         
            +
                    record_type   = authorization.record_type
         
     | 
| 
      
 66 
     | 
    
         
            +
                    value         = authorization.record_content
         
     | 
| 
      
 67 
     | 
    
         
            +
                    values << value
         
     | 
| 
      
 68 
     | 
    
         
            +
                  end
         
     | 
| 
      
 69 
     | 
    
         
            +
             
     | 
| 
      
 70 
     | 
    
         
            +
                  return unless values.any?
         
     | 
| 
      
 71 
     | 
    
         
            +
             
     | 
| 
      
 72 
     | 
    
         
            +
                  route53 = ApartmentAcmeClient::DnsApi::Route53.new(
         
     | 
| 
      
 73 
     | 
    
         
            +
                    requested_domain: wildcard_domain,
         
     | 
| 
      
 74 
     | 
    
         
            +
                    dns_record_label: label,
         
     | 
| 
      
 75 
     | 
    
         
            +
                    record_type: record_type,
         
     | 
| 
      
 76 
     | 
    
         
            +
                    values: values
         
     | 
| 
      
 77 
     | 
    
         
            +
                  )
         
     | 
| 
      
 78 
     | 
    
         
            +
             
     | 
| 
      
 79 
     | 
    
         
            +
                  route53.write_record
         
     | 
| 
      
 80 
     | 
    
         
            +
             
     | 
| 
      
 81 
     | 
    
         
            +
                  check_dns = ApartmentAcmeClient::DnsApi::CheckDns.new(wildcard_domain, label)
         
     | 
| 
      
 82 
     | 
    
         
            +
             
     | 
| 
      
 83 
     | 
    
         
            +
                  check_dns.wait_for_present(values.first)
         
     | 
| 
      
 84 
     | 
    
         
            +
             
     | 
| 
      
 85 
     | 
    
         
            +
                  if check_dns.check_dns(values.first)
         
     | 
| 
      
 86 
     | 
    
         
            +
                    # DNS is updated, proceed with cert request
         
     | 
| 
      
 87 
     | 
    
         
            +
                    dns_authorizations.each do |domain_authorization|
         
     | 
| 
      
 88 
     | 
    
         
            +
                      domain_authorization.request_validation
         
     | 
| 
      
 89 
     | 
    
         
            +
             
     | 
| 
      
 90 
     | 
    
         
            +
                      30.times do
         
     | 
| 
      
 91 
     | 
    
         
            +
                        # may be 'pending' initially
         
     | 
| 
      
 92 
     | 
    
         
            +
                        break if domain_authorization.status == 'valid'
         
     | 
| 
      
 93 
     | 
    
         
            +
             
     | 
| 
      
 94 
     | 
    
         
            +
                        puts "Waiting for LetsEncrypt to authorize the domain. Status #{domain_authorization.status}"
         
     | 
| 
      
 95 
     | 
    
         
            +
             
     | 
| 
      
 96 
     | 
    
         
            +
                        # Wait a bit for the server to make the request, or just blink. It should be fast.
         
     | 
| 
      
 97 
     | 
    
         
            +
                        sleep(2)
         
     | 
| 
      
 98 
     | 
    
         
            +
                        domain_authorization.reload
         
     | 
| 
      
 99 
     | 
    
         
            +
                      end
         
     | 
| 
      
 100 
     | 
    
         
            +
                    end
         
     | 
| 
      
 101 
     | 
    
         
            +
                  else
         
     | 
| 
      
 102 
     | 
    
         
            +
                    # ERROR, DNS not updated in time
         
     | 
| 
      
 103 
     | 
    
         
            +
                    Rollbar.error("DNS Entry not found in timeout")
         
     | 
| 
      
 104 
     | 
    
         
            +
                  end
         
     | 
| 
       48 
105 
     | 
    
         
             
                end
         
     | 
| 
       49 
106 
     | 
    
         | 
| 
       50 
     | 
    
         
            -
                # authorizes a domain with letsencrypt server
         
     | 
| 
      
 107 
     | 
    
         
            +
                # authorizes a single domain with letsencrypt server
         
     | 
| 
       51 
108 
     | 
    
         
             
                # returns true on success, false otherwise.
         
     | 
| 
       52 
109 
     | 
    
         
             
                #
         
     | 
| 
       53 
110 
     | 
    
         
             
                # from https://github.com/unixcharles/acme-client/tree/master#authorize-for-domain
         
     | 
| 
       54 
     | 
    
         
            -
                def  
     | 
| 
       55 
     | 
    
         
            -
                   
     | 
| 
      
 111 
     | 
    
         
            +
                def authorize_domain_with_http(domain_authorization)
         
     | 
| 
      
 112 
     | 
    
         
            +
                  challenge = domain_authorization.http
         
     | 
| 
       56 
113 
     | 
    
         | 
| 
       57 
     | 
    
         
            -
                  #  
     | 
| 
       58 
     | 
    
         
            -
                  challenge = authorization.http01
         
     | 
| 
       59 
     | 
    
         
            -
             
     | 
| 
       60 
     | 
    
         
            -
                  # The http-01 method will require you to respond to a HTTP request.
         
     | 
| 
      
 114 
     | 
    
         
            +
                  # The http method will require you to respond to a HTTP request.
         
     | 
| 
       61 
115 
     | 
    
         | 
| 
       62 
116 
     | 
    
         
             
                  # You can retrieve the challenge token
         
     | 
| 
       63 
117 
     | 
    
         
             
                  challenge.token # => "some_token"
         
     | 
| 
         @@ -90,43 +144,81 @@ module ApartmentAcmeClient 
     | 
|
| 
       90 
144 
     | 
    
         
             
                  #  challenge = client.challenge_from_hash(JSON.parse(File.read('challenge')))
         
     | 
| 
       91 
145 
     | 
    
         | 
| 
       92 
146 
     | 
    
         
             
                  # Once you are ready to serve the confirmation request you can proceed.
         
     | 
| 
       93 
     | 
    
         
            -
                  challenge. 
     | 
| 
      
 147 
     | 
    
         
            +
                  challenge.request_validation # => true
         
     | 
| 
       94 
148 
     | 
    
         | 
| 
       95 
     | 
    
         
            -
                   
     | 
| 
       96 
     | 
    
         
            -
             
     | 
| 
       97 
     | 
    
         
            -
                    if challenge. 
     | 
| 
       98 
     | 
    
         
            -
             
     | 
| 
       99 
     | 
    
         
            -
             
     | 
| 
       100 
     | 
    
         
            -
                    end
         
     | 
| 
      
 149 
     | 
    
         
            +
                  30.times do
         
     | 
| 
      
 150 
     | 
    
         
            +
                    # may be 'pending' initially
         
     | 
| 
      
 151 
     | 
    
         
            +
                    break if challenge.status == 'valid'
         
     | 
| 
      
 152 
     | 
    
         
            +
             
     | 
| 
      
 153 
     | 
    
         
            +
                    puts "Waiting for letsencrypt to authorize the single domain. Status: #{challenge.status}"
         
     | 
| 
       101 
154 
     | 
    
         | 
| 
       102 
155 
     | 
    
         
             
                    # Wait a bit for the server to make the request, or just blink. It should be fast.
         
     | 
| 
       103 
     | 
    
         
            -
                    sleep( 
     | 
| 
      
 156 
     | 
    
         
            +
                    sleep(2)
         
     | 
| 
      
 157 
     | 
    
         
            +
                    challenge.reload
         
     | 
| 
       104 
158 
     | 
    
         
             
                  end
         
     | 
| 
       105 
159 
     | 
    
         
             
                  File.delete(full_challenge_filename)
         
     | 
| 
       106 
160 
     | 
    
         | 
| 
       107 
     | 
    
         
            -
                   
     | 
| 
      
 161 
     | 
    
         
            +
                  challenge.status == 'valid'
         
     | 
| 
      
 162 
     | 
    
         
            +
                end
         
     | 
| 
      
 163 
     | 
    
         
            +
             
     | 
| 
      
 164 
     | 
    
         
            +
                # Create an order, perform authorization for each domain, and then
         
     | 
| 
      
 165 
     | 
    
         
            +
                # request the certificate.
         
     | 
| 
      
 166 
     | 
    
         
            +
                # - common name is used so that there is continuity of requests over time
         
     | 
| 
      
 167 
     | 
    
         
            +
                # - domains are the list of individual http-based domains to be authorized
         
     | 
| 
      
 168 
     | 
    
         
            +
                # - wildcard_domain is an optional wildcard domain to be authorized via DNS Record
         
     | 
| 
      
 169 
     | 
    
         
            +
                #
         
     | 
| 
      
 170 
     | 
    
         
            +
                # Returns the certificate
         
     | 
| 
      
 171 
     | 
    
         
            +
                def request_certificate(common_name:, domains:, wildcard_domain: nil)
         
     | 
| 
      
 172 
     | 
    
         
            +
                  domain_names_requested = domains
         
     | 
| 
      
 173 
     | 
    
         
            +
                  domain_names_requested += [wildcard_domain, "*.#{wildcard_domain}"] if wildcard_domain.present?
         
     | 
| 
      
 174 
     | 
    
         
            +
                  order = client.new_order(identifiers: domain_names_requested)
         
     | 
| 
      
 175 
     | 
    
         
            +
             
     | 
| 
      
 176 
     | 
    
         
            +
                  # Do the HTTP authorizations
         
     | 
| 
      
 177 
     | 
    
         
            +
                  order.authorizations.each do |authorization|
         
     | 
| 
      
 178 
     | 
    
         
            +
                    next if authorization.wildcard || authorization.http.nil?
         
     | 
| 
      
 179 
     | 
    
         
            +
             
     | 
| 
      
 180 
     | 
    
         
            +
                    authorize_domain_with_http(authorization)
         
     | 
| 
      
 181 
     | 
    
         
            +
                  end
         
     | 
| 
      
 182 
     | 
    
         
            +
                  # Do the DNS (wildcard) authorizations
         
     | 
| 
      
 183 
     | 
    
         
            +
                  authorize_domains_with_dns(order.authorizations, wildcard_domain: wildcard_domain)
         
     | 
| 
      
 184 
     | 
    
         
            +
             
     | 
| 
      
 185 
     | 
    
         
            +
                  client.request_certificate(common_name: common_name, names: domain_names_requested, order: order)
         
     | 
| 
       108 
186 
     | 
    
         
             
                end
         
     | 
| 
       109 
187 
     | 
    
         | 
| 
       110 
     | 
    
         
            -
                 
     | 
| 
       111 
     | 
    
         
            -
             
     | 
| 
      
 188 
     | 
    
         
            +
                # for use in order to store this on the machine for NGINX use
         
     | 
| 
      
 189 
     | 
    
         
            +
                def csr_private_key_string
         
     | 
| 
      
 190 
     | 
    
         
            +
                  csr_private_key.to_s
         
     | 
| 
       112 
191 
     | 
    
         
             
                end
         
     | 
| 
       113 
192 
     | 
    
         | 
| 
       114 
193 
     | 
    
         
             
                private
         
     | 
| 
       115 
194 
     | 
    
         | 
| 
       116 
195 
     | 
    
         
             
                def client
         
     | 
| 
       117 
196 
     | 
    
         
             
                  @client ||= ApartmentAcmeClient::AcmeClient::Proxy.singleton(
         
     | 
| 
       118 
     | 
    
         
            -
                     
     | 
| 
      
 197 
     | 
    
         
            +
                    acme_client_private_key: acme_client_private_key,
         
     | 
| 
      
 198 
     | 
    
         
            +
                    csr_private_key: csr_private_key,
         
     | 
| 
       119 
199 
     | 
    
         
             
                  )
         
     | 
| 
       120 
200 
     | 
    
         
             
                end
         
     | 
| 
       121 
201 
     | 
    
         | 
| 
       122 
202 
     | 
    
         
             
                # Returns a private key
         
     | 
| 
       123 
     | 
    
         
            -
                def  
     | 
| 
      
 203 
     | 
    
         
            +
                def acme_client_private_key
         
     | 
| 
       124 
204 
     | 
    
         
             
                  private_key = @certificate_storage.private_key
         
     | 
| 
       125 
205 
     | 
    
         
             
                  return nil unless private_key
         
     | 
| 
       126 
206 
     | 
    
         | 
| 
       127 
207 
     | 
    
         
             
                  OpenSSL::PKey::RSA.new(private_key)
         
     | 
| 
       128 
208 
     | 
    
         
             
                end
         
     | 
| 
       129 
209 
     | 
    
         | 
| 
      
 210 
     | 
    
         
            +
                def csr_private_key
         
     | 
| 
      
 211 
     | 
    
         
            +
                  private_key = @certificate_storage.csr_private_key
         
     | 
| 
      
 212 
     | 
    
         
            +
             
     | 
| 
      
 213 
     | 
    
         
            +
                  # create a new private key if one is not found
         
     | 
| 
      
 214 
     | 
    
         
            +
                  if private_key.nil?
         
     | 
| 
      
 215 
     | 
    
         
            +
                    private_key = create_private_key
         
     | 
| 
      
 216 
     | 
    
         
            +
                    @certificate_storage.save_csr_private_key(private_key)
         
     | 
| 
      
 217 
     | 
    
         
            +
                  end
         
     | 
| 
      
 218 
     | 
    
         
            +
             
     | 
| 
      
 219 
     | 
    
         
            +
                  OpenSSL::PKey::RSA.new(private_key)
         
     | 
| 
      
 220 
     | 
    
         
            +
                end
         
     | 
| 
      
 221 
     | 
    
         
            +
             
     | 
| 
       130 
222 
     | 
    
         
             
                def create_private_key
         
     | 
| 
       131 
223 
     | 
    
         
             
                  OpenSSL::PKey::RSA.new(4096)
         
     | 
| 
       132 
224 
     | 
    
         
             
                end
         
     | 
| 
         @@ -30,10 +30,10 @@ module ApartmentAcmeClient 
     | 
|
| 
       30 
30 
     | 
    
         | 
| 
       31 
31 
     | 
    
         
             
                  def filled_template
         
     | 
| 
       32 
32 
     | 
    
         
             
                    return nil unless check_configuration
         
     | 
| 
      
 33 
     | 
    
         
            +
             
     | 
| 
       33 
34 
     | 
    
         
             
                    fill_template(read_template, @options)
         
     | 
| 
       34 
35 
     | 
    
         
             
                  end
         
     | 
| 
       35 
36 
     | 
    
         | 
| 
       36 
     | 
    
         
            -
             
     | 
| 
       37 
37 
     | 
    
         
             
                  def default_options
         
     | 
| 
       38 
38 
     | 
    
         
             
                    result = {}
         
     | 
| 
       39 
39 
     | 
    
         
             
                    result[:public_folder] = ApartmentAcmeClient.public_folder
         
     | 
| 
         @@ -59,62 +59,60 @@ module ApartmentAcmeClient 
     | 
|
| 
       59 
59 
     | 
    
         | 
| 
       60 
60 
     | 
    
         
             
                  def default_template
         
     | 
| 
       61 
61 
     | 
    
         
             
                    <<~THE_END
         
     | 
| 
       62 
     | 
    
         
            -
             
     | 
| 
       63 
     | 
    
         
            -
             
     | 
| 
       64 
     | 
    
         
            -
             
     | 
| 
      
 62 
     | 
    
         
            +
                      #
         
     | 
| 
      
 63 
     | 
    
         
            +
                      # A virtual host using mix of IP-, name-, and port-based configuration
         
     | 
| 
      
 64 
     | 
    
         
            +
                      #
         
     | 
| 
       65 
65 
     | 
    
         | 
| 
       66 
     | 
    
         
            -
             
     | 
| 
       67 
     | 
    
         
            -
             
     | 
| 
       68 
     | 
    
         
            -
             
     | 
| 
       69 
     | 
    
         
            -
             
     | 
| 
      
 66 
     | 
    
         
            +
                      upstream app {
         
     | 
| 
      
 67 
     | 
    
         
            +
                          # Path to Unicorn SOCK file, as defined previously
         
     | 
| 
      
 68 
     | 
    
         
            +
                          server unix:<%= options[:socket_path] %> fail_timeout=0;
         
     | 
| 
      
 69 
     | 
    
         
            +
                      }
         
     | 
| 
       70 
70 
     | 
    
         | 
| 
       71 
     | 
    
         
            -
             
     | 
| 
      
 71 
     | 
    
         
            +
                      server {
         
     | 
| 
       72 
72 
     | 
    
         | 
| 
       73 
     | 
    
         
            -
             
     | 
| 
       74 
     | 
    
         
            -
             
     | 
| 
      
 73 
     | 
    
         
            +
                          # FOR HTTP
         
     | 
| 
      
 74 
     | 
    
         
            +
                          listen 80;
         
     | 
| 
       75 
75 
     | 
    
         | 
| 
       76 
     | 
    
         
            -
             
     | 
| 
      
 76 
     | 
    
         
            +
                          gzip on;
         
     | 
| 
       77 
77 
     | 
    
         | 
| 
       78 
     | 
    
         
            -
             
     | 
| 
       79 
     | 
    
         
            -
             
     | 
| 
       80 
     | 
    
         
            -
             
     | 
| 
      
 78 
     | 
    
         
            +
                          # Application root, as defined previously
         
     | 
| 
      
 79 
     | 
    
         
            +
                          root <%= options[:public_folder] %>;
         
     | 
| 
      
 80 
     | 
    
         
            +
                          server_name  <%= options[:base_domain] %> *.<%= options[:base_domain] %>;
         
     | 
| 
       81 
81 
     | 
    
         | 
| 
       82 
     | 
    
         
            -
             
     | 
| 
      
 82 
     | 
    
         
            +
                          try_files $uri/index.html $uri @app;
         
     | 
| 
       83 
83 
     | 
    
         | 
| 
       84 
     | 
    
         
            -
             
     | 
| 
       85 
     | 
    
         
            -
             
     | 
| 
       86 
     | 
    
         
            -
             
     | 
| 
       87 
     | 
    
         
            -
             
     | 
| 
       88 
     | 
    
         
            -
             
     | 
| 
       89 
     | 
    
         
            -
             
     | 
| 
       90 
     | 
    
         
            -
             
     | 
| 
      
 84 
     | 
    
         
            +
                          location @app {
         
     | 
| 
      
 85 
     | 
    
         
            +
                              proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
         
     | 
| 
      
 86 
     | 
    
         
            +
                              proxy_set_header X-FORWARDED-PROTO $scheme;
         
     | 
| 
      
 87 
     | 
    
         
            +
                              proxy_set_header Host $http_host;
         
     | 
| 
      
 88 
     | 
    
         
            +
                              proxy_redirect off;
         
     | 
| 
      
 89 
     | 
    
         
            +
                              proxy_pass http://app;
         
     | 
| 
      
 90 
     | 
    
         
            +
                          }
         
     | 
| 
       91 
91 
     | 
    
         | 
| 
       92 
     | 
    
         
            -
             
     | 
| 
       93 
     | 
    
         
            -
             
     | 
| 
       94 
     | 
    
         
            -
             
     | 
| 
      
 92 
     | 
    
         
            +
                          error_page 500 502 503 504 /500.html;
         
     | 
| 
      
 93 
     | 
    
         
            +
                          client_max_body_size 4G;
         
     | 
| 
      
 94 
     | 
    
         
            +
                          keepalive_timeout 10;
         
     | 
| 
       95 
95 
     | 
    
         | 
| 
       96 
     | 
    
         
            -
             
     | 
| 
       97 
     | 
    
         
            -
             
     | 
| 
       98 
     | 
    
         
            -
             
     | 
| 
      
 96 
     | 
    
         
            +
                          # BELOW THIS LINE FOR HTTPS
         
     | 
| 
      
 97 
     | 
    
         
            +
                          <% if options[:include_ssl] %>
         
     | 
| 
      
 98 
     | 
    
         
            +
                          listen 443 default_server ssl;
         
     | 
| 
       99 
99 
     | 
    
         | 
| 
       100 
     | 
    
         
            -
             
     | 
| 
       101 
     | 
    
         
            -
             
     | 
| 
      
 100 
     | 
    
         
            +
                          # The following should be enabled once everything is SSL
         
     | 
| 
      
 101 
     | 
    
         
            +
                          # ssl on;
         
     | 
| 
       102 
102 
     | 
    
         | 
| 
       103 
     | 
    
         
            -
             
     | 
| 
       104 
     | 
    
         
            -
             
     | 
| 
      
 103 
     | 
    
         
            +
                          ssl_certificate <%= options[:certificate_storage_folder] %>/<%= options[:cert_prefix] %>cert.pem;
         
     | 
| 
      
 104 
     | 
    
         
            +
                          ssl_certificate_key <%= options[:certificate_storage_folder] %>/<%= options[:cert_prefix] %>privkey.pem;
         
     | 
| 
       105 
105 
     | 
    
         | 
| 
       106 
     | 
    
         
            -
             
     | 
| 
       107 
     | 
    
         
            -
             
     | 
| 
       108 
     | 
    
         
            -
                        ssl_trusted_certificate <%= options[:certificate_storage_folder] %>/<%= options[:cert_prefix] %>fullchain.pem;
         
     | 
| 
      
 106 
     | 
    
         
            +
                          ssl_stapling on;
         
     | 
| 
      
 107 
     | 
    
         
            +
                          ssl_stapling_verify on;
         
     | 
| 
       109 
108 
     | 
    
         | 
| 
       110 
     | 
    
         
            -
             
     | 
| 
       111 
     | 
    
         
            -
             
     | 
| 
       112 
     | 
    
         
            -
             
     | 
| 
      
 109 
     | 
    
         
            +
                          ssl_session_timeout 5m;
         
     | 
| 
      
 110 
     | 
    
         
            +
                          <% end %>
         
     | 
| 
      
 111 
     | 
    
         
            +
                      }
         
     | 
| 
       113 
112 
     | 
    
         
             
                    THE_END
         
     | 
| 
       114 
113 
     | 
    
         
             
                  end
         
     | 
| 
       115 
114 
     | 
    
         | 
| 
       116 
115 
     | 
    
         
             
                  def fill_template(template, options)
         
     | 
| 
       117 
     | 
    
         
            -
             
     | 
| 
       118 
116 
     | 
    
         
             
                    # scope defined for use in binding to ERB
         
     | 
| 
       119 
117 
     | 
    
         
             
                    def opts(options)
         
     | 
| 
       120 
118 
     | 
    
         
             
                      options
         
     | 
| 
         @@ -1,22 +1,27 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
       1 
3 
     | 
    
         
             
            module ApartmentAcmeClient
         
     | 
| 
       2 
4 
     | 
    
         
             
              class RenewalService
         
     | 
| 
       3 
5 
     | 
    
         
             
                def self.run!
         
     | 
| 
       4 
     | 
    
         
            -
             
     | 
| 
       5 
6 
     | 
    
         
             
                  good_domains, rejected_domains = ApartmentAcmeClient::DomainChecker.new.accessible_domains
         
     | 
| 
       6 
7 
     | 
    
         
             
                  puts "All domains to be requested: #{good_domains}, invalid domains: #{rejected_domains}"
         
     | 
| 
       7 
8 
     | 
    
         | 
| 
       8 
     | 
    
         
            -
                   
     | 
| 
       9 
     | 
    
         
            -
                  puts "authorized-domains list: #{domains}"
         
     | 
| 
      
 9 
     | 
    
         
            +
                  common_name = ApartmentAcmeClient.common_name || good_domains.first
         
     | 
| 
       10 
10 
     | 
    
         | 
| 
       11 
     | 
    
         
            -
                   
     | 
| 
       12 
     | 
    
         
            -
                  certificate =  
     | 
| 
      
 11 
     | 
    
         
            +
                  encryptor = ApartmentAcmeClient::Encryption.new
         
     | 
| 
      
 12 
     | 
    
         
            +
                  certificate = encryptor.request_certificate(
         
     | 
| 
      
 13 
     | 
    
         
            +
                    common_name: common_name,
         
     | 
| 
      
 14 
     | 
    
         
            +
                    domains: good_domains,
         
     | 
| 
      
 15 
     | 
    
         
            +
                    wildcard_domain: ApartmentAcmeClient.wildcard_domain
         
     | 
| 
      
 16 
     | 
    
         
            +
                  )
         
     | 
| 
       13 
17 
     | 
    
         | 
| 
       14 
     | 
    
         
            -
                  ApartmentAcmeClient::CertificateStorage::Proxy.singleton. 
     | 
| 
      
 18 
     | 
    
         
            +
                  ApartmentAcmeClient::CertificateStorage::Proxy.singleton.store_certificate_string(certificate)
         
     | 
| 
      
 19 
     | 
    
         
            +
                  ApartmentAcmeClient::CertificateStorage::Proxy.singleton.store_csr_private_key_string(encryptor.csr_private_key_string)
         
     | 
| 
       15 
20 
     | 
    
         | 
| 
       16 
     | 
    
         
            -
                  puts  
     | 
| 
       17 
     | 
    
         
            -
                  ApartmentAcmeClient::FileManipulation::Proxy.singleton.restart_service( 
     | 
| 
      
 21 
     | 
    
         
            +
                  puts 'Restarting nginx with new certificate'
         
     | 
| 
      
 22 
     | 
    
         
            +
                  ApartmentAcmeClient::FileManipulation::Proxy.singleton.restart_service('nginx')
         
     | 
| 
       18 
23 
     | 
    
         | 
| 
       19 
     | 
    
         
            -
                  puts  
     | 
| 
      
 24 
     | 
    
         
            +
                  puts 'done.'
         
     | 
| 
       20 
25 
     | 
    
         
             
                end
         
     | 
| 
       21 
26 
     | 
    
         
             
              end
         
     | 
| 
       22 
27 
     | 
    
         
             
            end
         
     | 
    
        metadata
    CHANGED
    
    | 
         @@ -1,14 +1,14 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            --- !ruby/object:Gem::Specification
         
     | 
| 
       2 
2 
     | 
    
         
             
            name: apartment_acme_client
         
     | 
| 
       3 
3 
     | 
    
         
             
            version: !ruby/object:Gem::Version
         
     | 
| 
       4 
     | 
    
         
            -
              version: 0.0. 
     | 
| 
      
 4 
     | 
    
         
            +
              version: 0.0.5
         
     | 
| 
       5 
5 
     | 
    
         
             
            platform: ruby
         
     | 
| 
       6 
6 
     | 
    
         
             
            authors:
         
     | 
| 
       7 
7 
     | 
    
         
             
            - Robin Dunlop
         
     | 
| 
       8 
8 
     | 
    
         
             
            autorequire: 
         
     | 
| 
       9 
9 
     | 
    
         
             
            bindir: exe
         
     | 
| 
       10 
10 
     | 
    
         
             
            cert_chain: []
         
     | 
| 
       11 
     | 
    
         
            -
            date:  
     | 
| 
      
 11 
     | 
    
         
            +
            date: 2020-01-20 00:00:00.000000000 Z
         
     | 
| 
       12 
12 
     | 
    
         
             
            dependencies:
         
     | 
| 
       13 
13 
     | 
    
         
             
            - !ruby/object:Gem::Dependency
         
     | 
| 
       14 
14 
     | 
    
         
             
              name: rails
         
     | 
| 
         @@ -36,16 +36,16 @@ dependencies: 
     | 
|
| 
       36 
36 
     | 
    
         
             
                requirements:
         
     | 
| 
       37 
37 
     | 
    
         
             
                - - "~>"
         
     | 
| 
       38 
38 
     | 
    
         
             
                  - !ruby/object:Gem::Version
         
     | 
| 
       39 
     | 
    
         
            -
                    version: 0. 
     | 
| 
      
 39 
     | 
    
         
            +
                    version: 2.0.0
         
     | 
| 
       40 
40 
     | 
    
         
             
              type: :runtime
         
     | 
| 
       41 
41 
     | 
    
         
             
              prerelease: false
         
     | 
| 
       42 
42 
     | 
    
         
             
              version_requirements: !ruby/object:Gem::Requirement
         
     | 
| 
       43 
43 
     | 
    
         
             
                requirements:
         
     | 
| 
       44 
44 
     | 
    
         
             
                - - "~>"
         
     | 
| 
       45 
45 
     | 
    
         
             
                  - !ruby/object:Gem::Version
         
     | 
| 
       46 
     | 
    
         
            -
                    version: 0. 
     | 
| 
      
 46 
     | 
    
         
            +
                    version: 2.0.0
         
     | 
| 
       47 
47 
     | 
    
         
             
            - !ruby/object:Gem::Dependency
         
     | 
| 
       48 
     | 
    
         
            -
              name: aws-sdk- 
     | 
| 
      
 48 
     | 
    
         
            +
              name: aws-sdk-route53
         
     | 
| 
       49 
49 
     | 
    
         
             
              requirement: !ruby/object:Gem::Requirement
         
     | 
| 
       50 
50 
     | 
    
         
             
                requirements:
         
     | 
| 
       51 
51 
     | 
    
         
             
                - - "~>"
         
     | 
| 
         @@ -59,19 +59,19 @@ dependencies: 
     | 
|
| 
       59 
59 
     | 
    
         
             
                  - !ruby/object:Gem::Version
         
     | 
| 
       60 
60 
     | 
    
         
             
                    version: '1'
         
     | 
| 
       61 
61 
     | 
    
         
             
            - !ruby/object:Gem::Dependency
         
     | 
| 
       62 
     | 
    
         
            -
              name:  
     | 
| 
      
 62 
     | 
    
         
            +
              name: aws-sdk-s3
         
     | 
| 
       63 
63 
     | 
    
         
             
              requirement: !ruby/object:Gem::Requirement
         
     | 
| 
       64 
64 
     | 
    
         
             
                requirements:
         
     | 
| 
       65 
     | 
    
         
            -
                - - " 
     | 
| 
      
 65 
     | 
    
         
            +
                - - "~>"
         
     | 
| 
       66 
66 
     | 
    
         
             
                  - !ruby/object:Gem::Version
         
     | 
| 
       67 
     | 
    
         
            -
                    version: ' 
     | 
| 
       68 
     | 
    
         
            -
              type: : 
     | 
| 
      
 67 
     | 
    
         
            +
                    version: '1'
         
     | 
| 
      
 68 
     | 
    
         
            +
              type: :runtime
         
     | 
| 
       69 
69 
     | 
    
         
             
              prerelease: false
         
     | 
| 
       70 
70 
     | 
    
         
             
              version_requirements: !ruby/object:Gem::Requirement
         
     | 
| 
       71 
71 
     | 
    
         
             
                requirements:
         
     | 
| 
       72 
     | 
    
         
            -
                - - " 
     | 
| 
      
 72 
     | 
    
         
            +
                - - "~>"
         
     | 
| 
       73 
73 
     | 
    
         
             
                  - !ruby/object:Gem::Version
         
     | 
| 
       74 
     | 
    
         
            -
                    version: ' 
     | 
| 
      
 74 
     | 
    
         
            +
                    version: '1'
         
     | 
| 
       75 
75 
     | 
    
         
             
            - !ruby/object:Gem::Dependency
         
     | 
| 
       76 
76 
     | 
    
         
             
              name: bundler
         
     | 
| 
       77 
77 
     | 
    
         
             
              requirement: !ruby/object:Gem::Requirement
         
     | 
| 
         @@ -86,6 +86,20 @@ dependencies: 
     | 
|
| 
       86 
86 
     | 
    
         
             
                - - "~>"
         
     | 
| 
       87 
87 
     | 
    
         
             
                  - !ruby/object:Gem::Version
         
     | 
| 
       88 
88 
     | 
    
         
             
                    version: '1.15'
         
     | 
| 
      
 89 
     | 
    
         
            +
            - !ruby/object:Gem::Dependency
         
     | 
| 
      
 90 
     | 
    
         
            +
              name: pry
         
     | 
| 
      
 91 
     | 
    
         
            +
              requirement: !ruby/object:Gem::Requirement
         
     | 
| 
      
 92 
     | 
    
         
            +
                requirements:
         
     | 
| 
      
 93 
     | 
    
         
            +
                - - ">="
         
     | 
| 
      
 94 
     | 
    
         
            +
                  - !ruby/object:Gem::Version
         
     | 
| 
      
 95 
     | 
    
         
            +
                    version: '0'
         
     | 
| 
      
 96 
     | 
    
         
            +
              type: :development
         
     | 
| 
      
 97 
     | 
    
         
            +
              prerelease: false
         
     | 
| 
      
 98 
     | 
    
         
            +
              version_requirements: !ruby/object:Gem::Requirement
         
     | 
| 
      
 99 
     | 
    
         
            +
                requirements:
         
     | 
| 
      
 100 
     | 
    
         
            +
                - - ">="
         
     | 
| 
      
 101 
     | 
    
         
            +
                  - !ruby/object:Gem::Version
         
     | 
| 
      
 102 
     | 
    
         
            +
                    version: '0'
         
     | 
| 
       89 
103 
     | 
    
         
             
            - !ruby/object:Gem::Dependency
         
     | 
| 
       90 
104 
     | 
    
         
             
              name: rake
         
     | 
| 
       91 
105 
     | 
    
         
             
              requirement: !ruby/object:Gem::Requirement
         
     | 
| 
         @@ -129,7 +143,21 @@ dependencies: 
     | 
|
| 
       129 
143 
     | 
    
         
             
                  - !ruby/object:Gem::Version
         
     | 
| 
       130 
144 
     | 
    
         
             
                    version: '0'
         
     | 
| 
       131 
145 
     | 
    
         
             
            - !ruby/object:Gem::Dependency
         
     | 
| 
       132 
     | 
    
         
            -
              name:  
     | 
| 
      
 146 
     | 
    
         
            +
              name: rubocop
         
     | 
| 
      
 147 
     | 
    
         
            +
              requirement: !ruby/object:Gem::Requirement
         
     | 
| 
      
 148 
     | 
    
         
            +
                requirements:
         
     | 
| 
      
 149 
     | 
    
         
            +
                - - ">="
         
     | 
| 
      
 150 
     | 
    
         
            +
                  - !ruby/object:Gem::Version
         
     | 
| 
      
 151 
     | 
    
         
            +
                    version: '0'
         
     | 
| 
      
 152 
     | 
    
         
            +
              type: :development
         
     | 
| 
      
 153 
     | 
    
         
            +
              prerelease: false
         
     | 
| 
      
 154 
     | 
    
         
            +
              version_requirements: !ruby/object:Gem::Requirement
         
     | 
| 
      
 155 
     | 
    
         
            +
                requirements:
         
     | 
| 
      
 156 
     | 
    
         
            +
                - - ">="
         
     | 
| 
      
 157 
     | 
    
         
            +
                  - !ruby/object:Gem::Version
         
     | 
| 
      
 158 
     | 
    
         
            +
                    version: '0'
         
     | 
| 
      
 159 
     | 
    
         
            +
            - !ruby/object:Gem::Dependency
         
     | 
| 
      
 160 
     | 
    
         
            +
              name: sqlite3
         
     | 
| 
       133 
161 
     | 
    
         
             
              requirement: !ruby/object:Gem::Requirement
         
     | 
| 
       134 
162 
     | 
    
         
             
                requirements:
         
     | 
| 
       135 
163 
     | 
    
         
             
                - - ">="
         
     | 
| 
         @@ -170,6 +198,9 @@ files: 
     | 
|
| 
       170 
198 
     | 
    
         
             
            - lib/apartment_acme_client/acme_client/real_client.rb
         
     | 
| 
       171 
199 
     | 
    
         
             
            - lib/apartment_acme_client/certificate_storage/proxy.rb
         
     | 
| 
       172 
200 
     | 
    
         
             
            - lib/apartment_acme_client/certificate_storage/s3.rb
         
     | 
| 
      
 201 
     | 
    
         
            +
            - lib/apartment_acme_client/dns_api/check_dns.rb
         
     | 
| 
      
 202 
     | 
    
         
            +
            - lib/apartment_acme_client/dns_api/fake.rb
         
     | 
| 
      
 203 
     | 
    
         
            +
            - lib/apartment_acme_client/dns_api/route53.rb
         
     | 
| 
       173 
204 
     | 
    
         
             
            - lib/apartment_acme_client/domain_checker.rb
         
     | 
| 
       174 
205 
     | 
    
         
             
            - lib/apartment_acme_client/encryption.rb
         
     | 
| 
       175 
206 
     | 
    
         
             
            - lib/apartment_acme_client/engine.rb
         
     | 
| 
         @@ -201,7 +232,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement 
     | 
|
| 
       201 
232 
     | 
    
         
             
                  version: '0'
         
     | 
| 
       202 
233 
     | 
    
         
             
            requirements: []
         
     | 
| 
       203 
234 
     | 
    
         
             
            rubyforge_project: 
         
     | 
| 
       204 
     | 
    
         
            -
            rubygems_version: 2.7. 
     | 
| 
      
 235 
     | 
    
         
            +
            rubygems_version: 2.7.6
         
     | 
| 
       205 
236 
     | 
    
         
             
            signing_key: 
         
     | 
| 
       206 
237 
     | 
    
         
             
            specification_version: 4
         
     | 
| 
       207 
238 
     | 
    
         
             
            summary: Let's Encrypt interface for Multi-tenancy applications (like Apartment)
         
     |