anchor-pki 0.5.0 → 0.6.0
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/Gemfile.lock +15 -3
 - data/README.md +2 -1
 - data/lib/anchor/auto_cert/configuration.rb +102 -62
 - data/lib/anchor/auto_cert/identifier_policy.rb +4 -1
 - data/lib/anchor/auto_cert/managed_certificate.rb +39 -29
 - data/lib/anchor/auto_cert/manager.rb +85 -37
 - data/lib/anchor/auto_cert/railtie.rb +1 -1
 - data/lib/anchor/auto_cert/renewal_busy_wait.rb +5 -4
 - data/lib/anchor/disk_store.rb +31 -0
 - data/lib/anchor/pem_bundle.rb +79 -0
 - data/lib/anchor/version.rb +1 -1
 - data/lib/anchor.rb +2 -0
 - data/lib/puma/plugin/auto_cert.rb +9 -7
 - metadata +32 -2
 
    
        checksums.yaml
    CHANGED
    
    | 
         @@ -1,7 +1,7 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            ---
         
     | 
| 
       2 
2 
     | 
    
         
             
            SHA256:
         
     | 
| 
       3 
     | 
    
         
            -
              metadata.gz:  
     | 
| 
       4 
     | 
    
         
            -
              data.tar.gz:  
     | 
| 
      
 3 
     | 
    
         
            +
              metadata.gz: 2fe888d3160e743df15731de957658f18c7a31a1805d29d6d95d1364c03ceb73
         
     | 
| 
      
 4 
     | 
    
         
            +
              data.tar.gz: d264ec5e53c951ceb0c8a5214479a9dd36933f220e05671f88b2029c418f00a3
         
     | 
| 
       5 
5 
     | 
    
         
             
            SHA512:
         
     | 
| 
       6 
     | 
    
         
            -
              metadata.gz:  
     | 
| 
       7 
     | 
    
         
            -
              data.tar.gz:  
     | 
| 
      
 6 
     | 
    
         
            +
              metadata.gz: 7dafa50e3a537fe79c29fedb256e9a4986feceb78cf81cd0afb4c30b14eacee84fdd5c952ed7e34866b96711d4d4f390b00ba91ef3ff0e7c8abab3b4018b1858
         
     | 
| 
      
 7 
     | 
    
         
            +
              data.tar.gz: d9315cdc04dd7dd45dbdb223a31e392113c909f56a42d94981222196c68b90adea48d142847fa2b246655527a9c3f6cf18a53e26f0f7b9e5f6ec930d8dbd8363
         
     | 
    
        data/Gemfile.lock
    CHANGED
    
    | 
         @@ -1,22 +1,26 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            PATH
         
     | 
| 
       2 
2 
     | 
    
         
             
              remote: .
         
     | 
| 
       3 
3 
     | 
    
         
             
              specs:
         
     | 
| 
       4 
     | 
    
         
            -
                anchor-pki (0. 
     | 
| 
      
 4 
     | 
    
         
            +
                anchor-pki (0.5.0)
         
     | 
| 
       5 
5 
     | 
    
         
             
                  acme-client (~> 2.0.13)
         
     | 
| 
      
 6 
     | 
    
         
            +
                  pstore (~> 0.1)
         
     | 
| 
       6 
7 
     | 
    
         | 
| 
       7 
8 
     | 
    
         
             
            GEM
         
     | 
| 
       8 
9 
     | 
    
         
             
              remote: https://rubygems.org/
         
     | 
| 
       9 
10 
     | 
    
         
             
              specs:
         
     | 
| 
       10 
     | 
    
         
            -
                acme-client (2.0. 
     | 
| 
      
 11 
     | 
    
         
            +
                acme-client (2.0.15)
         
     | 
| 
       11 
12 
     | 
    
         
             
                  faraday (>= 1.0, < 3.0.0)
         
     | 
| 
       12 
13 
     | 
    
         
             
                  faraday-retry (>= 1.0, < 3.0.0)
         
     | 
| 
       13 
14 
     | 
    
         
             
                addressable (2.8.4)
         
     | 
| 
       14 
15 
     | 
    
         
             
                  public_suffix (>= 2.0.2, < 6.0)
         
     | 
| 
       15 
16 
     | 
    
         
             
                ast (2.4.2)
         
     | 
| 
      
 17 
     | 
    
         
            +
                base64 (0.2.0)
         
     | 
| 
       16 
18 
     | 
    
         
             
                crack (0.4.5)
         
     | 
| 
       17 
19 
     | 
    
         
             
                  rexml
         
     | 
| 
       18 
20 
     | 
    
         
             
                diff-lcs (1.5.0)
         
     | 
| 
       19 
     | 
    
         
            -
                 
     | 
| 
      
 21 
     | 
    
         
            +
                docile (1.4.0)
         
     | 
| 
      
 22 
     | 
    
         
            +
                faraday (2.7.11)
         
     | 
| 
      
 23 
     | 
    
         
            +
                  base64
         
     | 
| 
       20 
24 
     | 
    
         
             
                  faraday-net_http (>= 2.0, < 3.1)
         
     | 
| 
       21 
25 
     | 
    
         
             
                  ruby2_keywords (>= 0.0.4)
         
     | 
| 
       22 
26 
     | 
    
         
             
                faraday-net_http (3.0.2)
         
     | 
| 
         @@ -28,6 +32,7 @@ GEM 
     | 
|
| 
       28 
32 
     | 
    
         
             
                parallel (1.23.0)
         
     | 
| 
       29 
33 
     | 
    
         
             
                parser (3.2.2.1)
         
     | 
| 
       30 
34 
     | 
    
         
             
                  ast (~> 2.4.1)
         
     | 
| 
      
 35 
     | 
    
         
            +
                pstore (0.1.3)
         
     | 
| 
       31 
36 
     | 
    
         
             
                public_suffix (5.0.1)
         
     | 
| 
       32 
37 
     | 
    
         
             
                rainbow (3.1.1)
         
     | 
| 
       33 
38 
     | 
    
         
             
                rake (13.0.6)
         
     | 
| 
         @@ -68,6 +73,12 @@ GEM 
     | 
|
| 
       68 
73 
     | 
    
         
             
                  rubocop-factory_bot (~> 2.22)
         
     | 
| 
       69 
74 
     | 
    
         
             
                ruby-progressbar (1.13.0)
         
     | 
| 
       70 
75 
     | 
    
         
             
                ruby2_keywords (0.0.5)
         
     | 
| 
      
 76 
     | 
    
         
            +
                simplecov (0.22.0)
         
     | 
| 
      
 77 
     | 
    
         
            +
                  docile (~> 1.1)
         
     | 
| 
      
 78 
     | 
    
         
            +
                  simplecov-html (~> 0.11)
         
     | 
| 
      
 79 
     | 
    
         
            +
                  simplecov_json_formatter (~> 0.1)
         
     | 
| 
      
 80 
     | 
    
         
            +
                simplecov-html (0.12.3)
         
     | 
| 
      
 81 
     | 
    
         
            +
                simplecov_json_formatter (0.1.4)
         
     | 
| 
       71 
82 
     | 
    
         
             
                unicode-display_width (2.4.2)
         
     | 
| 
       72 
83 
     | 
    
         
             
                vcr (6.1.0)
         
     | 
| 
       73 
84 
     | 
    
         
             
                webmock (3.18.1)
         
     | 
| 
         @@ -87,6 +98,7 @@ DEPENDENCIES 
     | 
|
| 
       87 
98 
     | 
    
         
             
              rspec (~> 3.9)
         
     | 
| 
       88 
99 
     | 
    
         
             
              rubocop (~> 1.50)
         
     | 
| 
       89 
100 
     | 
    
         
             
              rubocop-rspec (~> 2.22)
         
     | 
| 
      
 101 
     | 
    
         
            +
              simplecov (~> 0.22)
         
     | 
| 
       90 
102 
     | 
    
         
             
              vcr (~> 6.1)
         
     | 
| 
       91 
103 
     | 
    
         
             
              webmock (~> 3.8)
         
     | 
| 
       92 
104 
     | 
    
         | 
    
        data/README.md
    CHANGED
    
    | 
         @@ -58,7 +58,8 @@ regenerated periodically. 
     | 
|
| 
       58 
58 
     | 
    
         
             
                    export ACME_DIRECTORY_URL='https://anchor.dev/autocert-cab3bc/development/x509/ca/acme'
         
     | 
| 
       59 
59 
     | 
    
         
             
                    export ACME_KID=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
         
     | 
| 
       60 
60 
     | 
    
         
             
                    export ACME_HMAC_KEY=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
         
     | 
| 
       61 
     | 
    
         
            -
             
     | 
| 
      
 61 
     | 
    
         
            +
            1. Update the [./spec/spec_helper.rb](spec/spec_helper.rb) file with these
         
     | 
| 
      
 62 
     | 
    
         
            +
               values as the respective `VCR_KID` and `VCR_HMAC_KEY`.
         
     | 
| 
       62 
63 
     | 
    
         
             
            1. on the command line execute:
         
     | 
| 
       63 
64 
     | 
    
         | 
| 
       64 
65 
     | 
    
         
             
                    $ . .env
         
     | 
| 
         @@ -5,8 +5,9 @@ module Anchor 
     | 
|
| 
       5 
5 
     | 
    
         
             
                # AutoCert Configuration provides a way to configure the AutoCert Manager.
         
     | 
| 
       6 
6 
     | 
    
         
             
                #
         
     | 
| 
       7 
7 
     | 
    
         
             
                class Configuration
         
     | 
| 
       8 
     | 
    
         
            -
                   
     | 
| 
       9 
     | 
    
         
            -
                   
     | 
| 
      
 8 
     | 
    
         
            +
                  DEFAULT_RENEW_BEFORE_SECONDS  = 60 * 60 * 24 * 30 # 30 days in seconds
         
     | 
| 
      
 9 
     | 
    
         
            +
                  DEFAULT_RENEW_BEFORE_FRACTION = 0.5 # when in the last 50% of the validity window, renew
         
     | 
| 
      
 10 
     | 
    
         
            +
                  DEFAULT_CHECK_EVERY_SECONDS   = 60 * 60 # 1 day in seconds
         
     | 
| 
       10 
11 
     | 
    
         | 
| 
       11 
12 
     | 
    
         
             
                  # Note - although it is possible to set change the name of a config, it is
         
     | 
| 
       12 
13 
     | 
    
         
             
                  # not recommended. The name is used as the key in the Registry, and if a
         
     | 
| 
         @@ -14,7 +15,8 @@ module Anchor 
     | 
|
| 
       14 
15 
     | 
    
         
             
                  # change its registry key.
         
     | 
| 
       15 
16 
     | 
    
         
             
                  attr_accessor :name,
         
     | 
| 
       16 
17 
     | 
    
         
             
                                :allow_identifiers,
         
     | 
| 
       17 
     | 
    
         
            -
                                : 
     | 
| 
      
 18 
     | 
    
         
            +
                                :cache_dir,
         
     | 
| 
      
 19 
     | 
    
         
            +
                                :check_every_seconds,
         
     | 
| 
       18 
20 
     | 
    
         
             
                                :contact,
         
     | 
| 
       19 
21 
     | 
    
         
             
                                :directory_url,
         
     | 
| 
       20 
22 
     | 
    
         
             
                                :external_account_binding,
         
     | 
| 
         @@ -30,19 +32,21 @@ module Anchor 
     | 
|
| 
       30 
32 
     | 
    
         
             
                  #
         
     | 
| 
       31 
33 
     | 
    
         
             
                  def initialize(name:,
         
     | 
| 
       32 
34 
     | 
    
         
             
                                 allow_identifiers: nil,
         
     | 
| 
       33 
     | 
    
         
            -
                                  
     | 
| 
      
 35 
     | 
    
         
            +
                                 cache_dir: nil,
         
     | 
| 
      
 36 
     | 
    
         
            +
                                 check_every_seconds: nil,
         
     | 
| 
       34 
37 
     | 
    
         
             
                                 contact: nil,
         
     | 
| 
       35 
38 
     | 
    
         
             
                                 directory_url: nil,
         
     | 
| 
       36 
39 
     | 
    
         
             
                                 external_account_binding: nil,
         
     | 
| 
       37 
     | 
    
         
            -
                                 renew_before_fraction:  
     | 
| 
       38 
     | 
    
         
            -
                                 renew_before_seconds:  
     | 
| 
      
 40 
     | 
    
         
            +
                                 renew_before_fraction: nil,
         
     | 
| 
      
 41 
     | 
    
         
            +
                                 renew_before_seconds: nil,
         
     | 
| 
       39 
42 
     | 
    
         
             
                                 tos_acceptors: nil,
         
     | 
| 
       40 
43 
     | 
    
         
             
                                 work_dir: nil)
         
     | 
| 
       41 
44 
     | 
    
         | 
| 
       42 
45 
     | 
    
         
             
                    @name                     = name
         
     | 
| 
       43 
46 
     | 
    
         | 
| 
       44 
47 
     | 
    
         
             
                    @allow_identifiers        = allow_identifiers
         
     | 
| 
       45 
     | 
    
         
            -
                    @ 
     | 
| 
      
 48 
     | 
    
         
            +
                    @cache_dir                = cache_dir
         
     | 
| 
      
 49 
     | 
    
         
            +
                    @check_every_seconds      = check_every_seconds
         
     | 
| 
       46 
50 
     | 
    
         
             
                    @contact                  = contact
         
     | 
| 
       47 
51 
     | 
    
         
             
                    @directory_url            = directory_url
         
     | 
| 
       48 
52 
     | 
    
         
             
                    @external_account_binding = external_account_binding
         
     | 
| 
         @@ -70,17 +74,31 @@ module Anchor 
     | 
|
| 
       70 
74 
     | 
    
         | 
| 
       71 
75 
     | 
    
         
             
                  def validate!
         
     | 
| 
       72 
76 
     | 
    
         
             
                    @allow_identifiers        = prepare_allow_identifiers(@allow_identifiers)
         
     | 
| 
       73 
     | 
    
         
            -
                    @ 
     | 
| 
      
 77 
     | 
    
         
            +
                    @cache_dir                = prepare_directory(dir: @cache_dir, property: 'cache_dir')
         
     | 
| 
      
 78 
     | 
    
         
            +
                    @check_every_seconds      = prepare_check_every_seconds(@check_every_seconds)
         
     | 
| 
       74 
79 
     | 
    
         
             
                    @directory_url            = prepare_directory_url(@directory_url)
         
     | 
| 
       75 
80 
     | 
    
         
             
                    @external_account_binding = prepare_external_account_binding(@external_account_binding)
         
     | 
| 
       76 
81 
     | 
    
         
             
                    @renew_before_fraction    = prepare_renew_before_fraction(@renew_before_fraction)
         
     | 
| 
       77 
82 
     | 
    
         
             
                    @renew_before_seconds     = prepare_renew_before_seconds(@renew_before_seconds)
         
     | 
| 
       78 
83 
     | 
    
         
             
                    @tos_acceptors            = prepare_tos_acceptors(@tos_acceptors)
         
     | 
| 
       79 
     | 
    
         
            -
                    @work_dir                 =  
     | 
| 
       80 
     | 
    
         
            -
             
     | 
| 
      
 84 
     | 
    
         
            +
                    @work_dir                 = prepare_directory(dir: @work_dir, property: 'work_dir')
         
     | 
| 
       81 
85 
     | 
    
         
             
                    self
         
     | 
| 
       82 
86 
     | 
    
         
             
                  end
         
     | 
| 
       83 
87 
     | 
    
         | 
| 
      
 88 
     | 
    
         
            +
                  # Return the fallback identifer for this configuration
         
     | 
| 
      
 89 
     | 
    
         
            +
             
     | 
| 
      
 90 
     | 
    
         
            +
                  # look at all the identifiers, strip a leading wildcard off of all of
         
     | 
| 
      
 91 
     | 
    
         
            +
                  # them and then pick the one that has the fewest '.' in it, if there are
         
     | 
| 
      
 92 
     | 
    
         
            +
                  # ties for fewest, pick the first one in the list of ties. A minimum of
         
     | 
| 
      
 93 
     | 
    
         
            +
                  # 2 '.' is required.
         
     | 
| 
      
 94 
     | 
    
         
            +
                  #
         
     | 
| 
      
 95 
     | 
    
         
            +
                  def fallback_identifier
         
     | 
| 
      
 96 
     | 
    
         
            +
                    de_wildcarded = allow_identifiers.map { |i| i.sub(/^\*\./, '') }
         
     | 
| 
      
 97 
     | 
    
         
            +
                    not_tld = de_wildcarded.select { |i| i.count('.') >= 2 }
         
     | 
| 
      
 98 
     | 
    
         
            +
                    ordered = not_tld.sort_by { |i| i.count('.') }
         
     | 
| 
      
 99 
     | 
    
         
            +
                    ordered[0]
         
     | 
| 
      
 100 
     | 
    
         
            +
                  end
         
     | 
| 
      
 101 
     | 
    
         
            +
             
     | 
| 
       84 
102 
     | 
    
         
             
                  private
         
     | 
| 
       85 
103 
     | 
    
         | 
| 
       86 
104 
     | 
    
         
             
                  def prepare_allow_identifiers(allow_identifiers)
         
     | 
| 
         @@ -95,33 +113,36 @@ module Anchor 
     | 
|
| 
       95 
113 
     | 
    
         | 
| 
       96 
114 
     | 
    
         
             
                    if prepared.nil? || prepared.empty?
         
     | 
| 
       97 
115 
     | 
    
         
             
                      raise ConfigurationError,
         
     | 
| 
       98 
     | 
    
         
            -
                            "The '#{name}' #{self.class} instance has a misconfigured  
     | 
| 
       99 
     | 
    
         
            -
                            'Set it to a string, or an array of strings, ' \
         
     | 
| 
       100 
     | 
    
         
            -
                            'or set the ACME_ALLOW_IDENTIFIERS environment variable  
     | 
| 
      
 116 
     | 
    
         
            +
                            "The '#{name}' #{self.class} instance has a misconfigured " \
         
     | 
| 
      
 117 
     | 
    
         
            +
                            '`allow_identifiers` value. Set it to a string, or an array of strings, ' \
         
     | 
| 
      
 118 
     | 
    
         
            +
                            'or set the ACME_ALLOW_IDENTIFIERS environment variable ' \
         
     | 
| 
      
 119 
     | 
    
         
            +
                            'to a comma separated list of identifiers.'
         
     | 
| 
       101 
120 
     | 
    
         
             
                    end
         
     | 
| 
       102 
121 
     | 
    
         | 
| 
       103 
122 
     | 
    
         
             
                    prepared
         
     | 
| 
       104 
123 
     | 
    
         
             
                  end
         
     | 
| 
       105 
124 
     | 
    
         | 
| 
       106 
     | 
    
         
            -
                  def  
     | 
| 
       107 
     | 
    
         
            -
                     
     | 
| 
      
 125 
     | 
    
         
            +
                  def prepare_check_every_seconds(check_every_seconds)
         
     | 
| 
      
 126 
     | 
    
         
            +
                    message = "The '#{name}' #{self.class} instance has a misconfigured " \
         
     | 
| 
      
 127 
     | 
    
         
            +
                              '`check_every_seconds` value. It must be set to an integer > 0, ' \
         
     | 
| 
      
 128 
     | 
    
         
            +
                              'or set the AUTO_CERT_CHECK_EVERY environment variable.'
         
     | 
| 
       108 
129 
     | 
    
         | 
| 
       109 
     | 
    
         
            -
                     
     | 
| 
       110 
     | 
    
         
            -
                       
     | 
| 
       111 
     | 
    
         
            -
             
     | 
| 
       112 
     | 
    
         
            -
             
     | 
| 
       113 
     | 
    
         
            -
                     
     | 
| 
      
 130 
     | 
    
         
            +
                    candidates = [
         
     | 
| 
      
 131 
     | 
    
         
            +
                      check_every_seconds,
         
     | 
| 
      
 132 
     | 
    
         
            +
                      ENV.fetch('AUTO_CERT_CHECK_EVERY', nil),
         
     | 
| 
      
 133 
     | 
    
         
            +
                      DEFAULT_CHECK_EVERY_SECONDS
         
     | 
| 
      
 134 
     | 
    
         
            +
                    ]
         
     | 
| 
       114 
135 
     | 
    
         | 
| 
       115 
     | 
    
         
            -
                     
     | 
| 
      
 136 
     | 
    
         
            +
                    ensure_positive_integer(candidates, message)
         
     | 
| 
       116 
137 
     | 
    
         
             
                  end
         
     | 
| 
       117 
138 
     | 
    
         | 
| 
       118 
139 
     | 
    
         
             
                  def prepare_directory_url(directory_url)
         
     | 
| 
      
 140 
     | 
    
         
            +
                    message = "The '#{name}' #{self.class} instance has a misconfigured `directory_url` value. " \
         
     | 
| 
      
 141 
     | 
    
         
            +
                              'It must be set to a string, or set the ACME_DIRECTORY_URL environment variable.'
         
     | 
| 
      
 142 
     | 
    
         
            +
             
     | 
| 
       119 
143 
     | 
    
         
             
                    directory_url ||= ENV.fetch('ACME_DIRECTORY_URL', nil)
         
     | 
| 
       120 
     | 
    
         
            -
             
     | 
| 
       121 
     | 
    
         
            -
             
     | 
| 
       122 
     | 
    
         
            -
                            "The '#{name}' #{self.class} instance has a misconfigured `directory_url` value. " \
         
     | 
| 
       123 
     | 
    
         
            -
                            'It must be set to a string, or set the ACME_DIRECTORY_URL environment variable.'
         
     | 
| 
       124 
     | 
    
         
            -
                    end
         
     | 
| 
      
 144 
     | 
    
         
            +
             
     | 
| 
      
 145 
     | 
    
         
            +
                    raise ConfigurationError, message if directory_url.nil?
         
     | 
| 
       125 
146 
     | 
    
         | 
| 
       126 
147 
     | 
    
         
             
                    directory_url
         
     | 
| 
       127 
148 
     | 
    
         
             
                  end
         
     | 
| 
         @@ -130,39 +151,47 @@ module Anchor 
     | 
|
| 
       130 
151 
     | 
    
         
             
                    kid = ENV.fetch('ACME_KID', nil)
         
     | 
| 
       131 
152 
     | 
    
         
             
                    hmac_key = ENV.fetch('ACME_HMAC_KEY', nil)
         
     | 
| 
       132 
153 
     | 
    
         | 
| 
       133 
     | 
    
         
            -
                    if kid && hmac_key
         
     | 
| 
       134 
     | 
    
         
            -
                      external_account_binding 
     | 
| 
       135 
     | 
    
         
            -
                        kid: kid,
         
     | 
| 
       136 
     | 
    
         
            -
                        hmac_key: hmac_key
         
     | 
| 
       137 
     | 
    
         
            -
                      }
         
     | 
| 
      
 154 
     | 
    
         
            +
                    if external_account_binding && external_account_binding[:kid] && external_account_binding[:hmac_key]
         
     | 
| 
      
 155 
     | 
    
         
            +
                      return external_account_binding
         
     | 
| 
       138 
156 
     | 
    
         
             
                    end
         
     | 
| 
       139 
     | 
    
         
            -
             
     | 
| 
      
 157 
     | 
    
         
            +
             
     | 
| 
      
 158 
     | 
    
         
            +
                    { kid: kid, hmac_key: hmac_key }
         
     | 
| 
       140 
159 
     | 
    
         
             
                  end
         
     | 
| 
       141 
160 
     | 
    
         | 
| 
       142 
161 
     | 
    
         
             
                  def prepare_renew_before_seconds(renew_before_seconds)
         
     | 
| 
       143 
     | 
    
         
            -
                     
     | 
| 
       144 
     | 
    
         
            -
             
     | 
| 
       145 
     | 
    
         
            -
             
     | 
| 
       146 
     | 
    
         
            -
             
     | 
| 
       147 
     | 
    
         
            -
             
     | 
| 
       148 
     | 
    
         
            -
             
     | 
| 
       149 
     | 
    
         
            -
             
     | 
| 
       150 
     | 
    
         
            -
                       
     | 
| 
       151 
     | 
    
         
            -
                     
     | 
| 
       152 
     | 
    
         
            -
                     
     | 
| 
      
 162 
     | 
    
         
            +
                    message = "The '#{name}' #{self.class} instance has a misconfigured " \
         
     | 
| 
      
 163 
     | 
    
         
            +
                              '`before_seconds` value. It must be set to an integer > 0, ' \
         
     | 
| 
      
 164 
     | 
    
         
            +
                              'or set the ACME_RENEW_BEFORE_SECONDS environment variable.'
         
     | 
| 
      
 165 
     | 
    
         
            +
             
     | 
| 
      
 166 
     | 
    
         
            +
                    candidates = [
         
     | 
| 
      
 167 
     | 
    
         
            +
                      renew_before_seconds,
         
     | 
| 
      
 168 
     | 
    
         
            +
                      ENV.fetch('ACME_RENEW_BEFORE_SECONDS', nil),
         
     | 
| 
      
 169 
     | 
    
         
            +
                      DEFAULT_RENEW_BEFORE_SECONDS
         
     | 
| 
      
 170 
     | 
    
         
            +
                    ]
         
     | 
| 
      
 171 
     | 
    
         
            +
                    ensure_positive_integer(candidates, message)
         
     | 
| 
       153 
172 
     | 
    
         
             
                  end
         
     | 
| 
       154 
173 
     | 
    
         | 
| 
       155 
174 
     | 
    
         
             
                  def prepare_renew_before_fraction(renew_before_fraction)
         
     | 
| 
       156 
     | 
    
         
            -
                     
     | 
| 
       157 
     | 
    
         
            -
             
     | 
| 
       158 
     | 
    
         
            -
             
     | 
| 
       159 
     | 
    
         
            -
             
     | 
| 
       160 
     | 
    
         
            -
             
     | 
| 
       161 
     | 
    
         
            -
             
     | 
| 
       162 
     | 
    
         
            -
             
     | 
| 
       163 
     | 
    
         
            -
                       
     | 
| 
      
 175 
     | 
    
         
            +
                    message = "The '#{name}' #{self.class} instance has a misconfigured " \
         
     | 
| 
      
 176 
     | 
    
         
            +
                              '`before_fraction` value. It must be set to a float > 0 and < 1, ' \
         
     | 
| 
      
 177 
     | 
    
         
            +
                              'or set the ACME_RENEW_BEFORE_FRACTION environment variable.'
         
     | 
| 
      
 178 
     | 
    
         
            +
             
     | 
| 
      
 179 
     | 
    
         
            +
                    candidates = [
         
     | 
| 
      
 180 
     | 
    
         
            +
                      renew_before_fraction,
         
     | 
| 
      
 181 
     | 
    
         
            +
                      ENV.fetch('ACME_RENEW_BEFORE_FRACTION', nil),
         
     | 
| 
      
 182 
     | 
    
         
            +
                      DEFAULT_RENEW_BEFORE_FRACTION
         
     | 
| 
      
 183 
     | 
    
         
            +
                    ]
         
     | 
| 
      
 184 
     | 
    
         
            +
             
     | 
| 
      
 185 
     | 
    
         
            +
                    candidates.each do |candidate|
         
     | 
| 
      
 186 
     | 
    
         
            +
                      next if candidate.nil?
         
     | 
| 
      
 187 
     | 
    
         
            +
             
     | 
| 
      
 188 
     | 
    
         
            +
                      as_float = candidate.to_f
         
     | 
| 
      
 189 
     | 
    
         
            +
                      return as_float if (0..1).cover?(as_float)
         
     | 
| 
       164 
190 
     | 
    
         
             
                    end
         
     | 
| 
       165 
     | 
    
         
            -
             
     | 
| 
      
 191 
     | 
    
         
            +
             
     | 
| 
      
 192 
     | 
    
         
            +
                    # this should really never happen as DEFAULT_RENEW_BEFORE_FRACTION is
         
     | 
| 
      
 193 
     | 
    
         
            +
                    # valid
         
     | 
| 
      
 194 
     | 
    
         
            +
                    raise ConfigurationError, message
         
     | 
| 
       166 
195 
     | 
    
         
             
                  end
         
     | 
| 
       167 
196 
     | 
    
         | 
| 
       168 
197 
     | 
    
         
             
                  def prepare_tos_acceptors(tos_acceptors)
         
     | 
| 
         @@ -170,29 +199,40 @@ module Anchor 
     | 
|
| 
       170 
199 
     | 
    
         | 
| 
       171 
200 
     | 
    
         
             
                    if tos_acceptors.empty? || tos_acceptors.any? { |tos| !tos.respond_to?(:accept?) }
         
     | 
| 
       172 
201 
     | 
    
         
             
                      raise ConfigurationError,
         
     | 
| 
       173 
     | 
    
         
            -
                            "The '#{name}' #{self.class} instance has a misconfigured  
     | 
| 
       174 
     | 
    
         
            -
                            'It must be set to an object  
     | 
| 
      
 202 
     | 
    
         
            +
                            "The '#{name}' #{self.class} instance has a misconfigured " \
         
     | 
| 
      
 203 
     | 
    
         
            +
                            '`tos_acceptors` value. It must be set to an object ' \
         
     | 
| 
      
 204 
     | 
    
         
            +
                            'or an array of objects that respond to `accept?`.'
         
     | 
| 
       175 
205 
     | 
    
         
             
                    end
         
     | 
| 
       176 
206 
     | 
    
         | 
| 
       177 
207 
     | 
    
         
             
                    tos_acceptors
         
     | 
| 
       178 
208 
     | 
    
         
             
                  end
         
     | 
| 
       179 
209 
     | 
    
         | 
| 
       180 
     | 
    
         
            -
                  def  
     | 
| 
       181 
     | 
    
         
            -
                    return nil if  
     | 
| 
      
 210 
     | 
    
         
            +
                  def prepare_directory(dir:, property:)
         
     | 
| 
      
 211 
     | 
    
         
            +
                    return nil if dir.nil?
         
     | 
| 
       182 
212 
     | 
    
         | 
| 
       183 
     | 
    
         
            -
                     
     | 
| 
      
 213 
     | 
    
         
            +
                    dir = Pathname.new(dir) unless dir.is_a?(Pathname)
         
     | 
| 
      
 214 
     | 
    
         
            +
                    message = "The '#{name}' #{self.class} instance has a misconfigured " \
         
     | 
| 
      
 215 
     | 
    
         
            +
                              "`#{property}` value, it resolves to (#{dir}). " \
         
     | 
| 
      
 216 
     | 
    
         
            +
                              'It must be set to a directory, or a path that can be created.'
         
     | 
| 
       184 
217 
     | 
    
         | 
| 
       185 
218 
     | 
    
         
             
                    begin
         
     | 
| 
       186 
     | 
    
         
            -
                       
     | 
| 
       187 
     | 
    
         
            -
                    rescue StandardError =>  
     | 
| 
       188 
     | 
    
         
            -
                      raise ConfigurationError,  
     | 
| 
      
 219 
     | 
    
         
            +
                      dir.mkpath
         
     | 
| 
      
 220 
     | 
    
         
            +
                    rescue StandardError => _e
         
     | 
| 
      
 221 
     | 
    
         
            +
                      raise ConfigurationError, message
         
     | 
| 
       189 
222 
     | 
    
         
             
                    end
         
     | 
| 
       190 
223 
     | 
    
         | 
| 
       191 
     | 
    
         
            -
                     
     | 
| 
       192 
     | 
    
         
            -
             
     | 
| 
      
 224 
     | 
    
         
            +
                    dir
         
     | 
| 
      
 225 
     | 
    
         
            +
                  end
         
     | 
| 
      
 226 
     | 
    
         
            +
             
     | 
| 
      
 227 
     | 
    
         
            +
                  def ensure_positive_integer(candidates, message)
         
     | 
| 
      
 228 
     | 
    
         
            +
                    candidates.each do |candidate|
         
     | 
| 
      
 229 
     | 
    
         
            +
                      next if candidate.nil?
         
     | 
| 
      
 230 
     | 
    
         
            +
             
     | 
| 
      
 231 
     | 
    
         
            +
                      as_int = candidate.to_i
         
     | 
| 
      
 232 
     | 
    
         
            +
                      return as_int if as_int.positive?
         
     | 
| 
       193 
233 
     | 
    
         
             
                    end
         
     | 
| 
       194 
234 
     | 
    
         | 
| 
       195 
     | 
    
         
            -
                     
     | 
| 
      
 235 
     | 
    
         
            +
                    raise ConfigurationError, message
         
     | 
| 
       196 
236 
     | 
    
         
             
                  end
         
     | 
| 
       197 
237 
     | 
    
         
             
                end
         
     | 
| 
       198 
238 
     | 
    
         
             
              end
         
     | 
| 
         @@ -47,7 +47,10 @@ module Anchor 
     | 
|
| 
       47 
47 
     | 
    
         
             
                    check_klass = self.class.policy_checks.find do |klass|
         
     | 
| 
       48 
48 
     | 
    
         
             
                      klass.handles?(description)
         
     | 
| 
       49 
49 
     | 
    
         
             
                    end
         
     | 
| 
       50 
     | 
    
         
            -
                     
     | 
| 
      
 50 
     | 
    
         
            +
                    if check_klass.nil?
         
     | 
| 
      
 51 
     | 
    
         
            +
                      raise UnknownPolicyCheckError,
         
     | 
| 
      
 52 
     | 
    
         
            +
                            "Unable to create a policy check based upon '#{description}'"
         
     | 
| 
      
 53 
     | 
    
         
            +
                    end
         
     | 
| 
       51 
54 
     | 
    
         | 
| 
       52 
55 
     | 
    
         
             
                    @description = description
         
     | 
| 
       53 
56 
     | 
    
         
             
                    @check = check_klass.new(description)
         
     | 
| 
         @@ -4,32 +4,26 @@ require 'forwardable' 
     | 
|
| 
       4 
4 
     | 
    
         | 
| 
       5 
5 
     | 
    
         
             
            module Anchor
         
     | 
| 
       6 
6 
     | 
    
         
             
              module AutoCert
         
     | 
| 
       7 
     | 
    
         
            -
                # ManagedCertificate is a class that represents a certificate 
     | 
| 
      
 7 
     | 
    
         
            +
                # ManagedCertificate is a class that represents a certificate
         
     | 
| 
       8 
8 
     | 
    
         
             
                # for renewal
         
     | 
| 
       9 
9 
     | 
    
         
             
                class ManagedCertificate
         
     | 
| 
       10 
     | 
    
         
            -
                  attr_reader :cert_pem, : 
     | 
| 
      
 10 
     | 
    
         
            +
                  attr_reader :cert_pem, :key_pem, :x509, :persist_dir, :cert_path, :private_key_path
         
     | 
| 
       11 
11 
     | 
    
         | 
| 
       12 
12 
     | 
    
         
             
                  extend Forwardable
         
     | 
| 
       13 
     | 
    
         
            -
                  def_delegators :@ 
     | 
| 
       14 
     | 
    
         
            -
                  def_delegators :@x509, :not_after, :not_before, :serial
         
     | 
| 
      
 13 
     | 
    
         
            +
                  def_delegators :@x509, :not_after, :not_before
         
     | 
| 
       15 
14 
     | 
    
         | 
| 
       16 
     | 
    
         
            -
                  def  
     | 
| 
       17 
     | 
    
         
            -
                    cert_pem 
     | 
| 
       18 
     | 
    
         
            -
                    key_pem 
     | 
| 
       19 
     | 
    
         
            -
                    new( 
     | 
| 
      
 15 
     | 
    
         
            +
                  def initialize(cert_pem:, key_pem:, persist_dir: nil)
         
     | 
| 
      
 16 
     | 
    
         
            +
                    @cert_pem         = cert_pem
         
     | 
| 
      
 17 
     | 
    
         
            +
                    @key_pem          = key_pem
         
     | 
| 
      
 18 
     | 
    
         
            +
                    @persist_dir      = Pathname.new(persist_dir) if persist_dir
         
     | 
| 
      
 19 
     | 
    
         
            +
                    @x509             = OpenSSL::X509::Certificate.new(cert_pem)
         
     | 
| 
      
 20 
     | 
    
         
            +
                    @cert_path        = nil
         
     | 
| 
      
 21 
     | 
    
         
            +
                    @private_key_path = nil
         
     | 
| 
      
 22 
     | 
    
         
            +
                    persist_pems
         
     | 
| 
       20 
23 
     | 
    
         
             
                  end
         
     | 
| 
       21 
24 
     | 
    
         | 
| 
       22 
     | 
    
         
            -
                  def  
     | 
| 
       23 
     | 
    
         
            -
                     
     | 
| 
       24 
     | 
    
         
            -
                    @cert_pem  = cert_pem
         
     | 
| 
       25 
     | 
    
         
            -
                    @key_pem   = key_pem
         
     | 
| 
       26 
     | 
    
         
            -
                    @x509      = OpenSSL::X509::Certificate.new(cert_pem)
         
     | 
| 
       27 
     | 
    
         
            -
             
     | 
| 
       28 
     | 
    
         
            -
                    hex_serial_basename = hex_serial('-')
         
     | 
| 
       29 
     | 
    
         
            -
                    @cert_path = manager.work_dir / "#{hex_serial_basename}.crt"
         
     | 
| 
       30 
     | 
    
         
            -
                    @key_path  = manager.work_dir / "#{hex_serial_basename}.key"
         
     | 
| 
       31 
     | 
    
         
            -
             
     | 
| 
       32 
     | 
    
         
            -
                    write_working_files
         
     | 
| 
      
 25 
     | 
    
         
            +
                  def serial
         
     | 
| 
      
 26 
     | 
    
         
            +
                    x509.serial.to_i
         
     | 
| 
       33 
27 
     | 
    
         
             
                  end
         
     | 
| 
       34 
28 
     | 
    
         | 
| 
       35 
29 
     | 
    
         
             
                  def hex_serial(joiner = ':')
         
     | 
| 
         @@ -40,27 +34,43 @@ module Anchor 
     | 
|
| 
       40 
34 
     | 
    
         
             
                    not_after <= now
         
     | 
| 
       41 
35 
     | 
    
         
             
                  end
         
     | 
| 
       42 
36 
     | 
    
         | 
| 
       43 
     | 
    
         
            -
                   
     | 
| 
       44 
     | 
    
         
            -
             
     | 
| 
       45 
     | 
    
         
            -
                  end
         
     | 
| 
       46 
     | 
    
         
            -
             
     | 
| 
      
 37 
     | 
    
         
            +
                  # For the moment, the only items in subjectAltName we care about are DNS:
         
     | 
| 
      
 38 
     | 
    
         
            +
                  # entries.
         
     | 
| 
       47 
39 
     | 
    
         
             
                  def identifiers
         
     | 
| 
       48 
40 
     | 
    
         
             
                    alt_names = x509&.extensions&.find { |ext| ext.oid == 'subjectAltName' }&.value&.split(', ') || []
         
     | 
| 
       49 
     | 
    
         
            -
                    alt_names. 
     | 
| 
      
 41 
     | 
    
         
            +
                    alt_names.select { |name| name.start_with?('DNS:') }
         
     | 
| 
      
 42 
     | 
    
         
            +
                             .map { |name| name.sub(/^DNS:/, '') }
         
     | 
| 
       50 
43 
     | 
    
         
             
                  end
         
     | 
| 
       51 
44 
     | 
    
         | 
| 
       52 
45 
     | 
    
         
             
                  def common_name
         
     | 
| 
       53 
46 
     | 
    
         
             
                    x509.subject.to_a.find { |name, _, _| name == 'CN' }[1]
         
     | 
| 
       54 
47 
     | 
    
         
             
                  end
         
     | 
| 
       55 
48 
     | 
    
         | 
| 
       56 
     | 
    
         
            -
                  def  
     | 
| 
       57 
     | 
    
         
            -
                     
     | 
| 
       58 
     | 
    
         
            -
                     
     | 
| 
      
 49 
     | 
    
         
            +
                  def all_names
         
     | 
| 
      
 50 
     | 
    
         
            +
                    non_common_identifiers = identifiers.reject { |name| name == common_name }
         
     | 
| 
      
 51 
     | 
    
         
            +
                    [common_name, *non_common_identifiers.sort]
         
     | 
| 
      
 52 
     | 
    
         
            +
                  end
         
     | 
| 
      
 53 
     | 
    
         
            +
             
     | 
| 
      
 54 
     | 
    
         
            +
                  def persist_pems
         
     | 
| 
      
 55 
     | 
    
         
            +
                    return unless persist_dir
         
     | 
| 
      
 56 
     | 
    
         
            +
                    return nil unless persist_dir.directory? && persist_dir.writable?
         
     | 
| 
      
 57 
     | 
    
         
            +
             
     | 
| 
      
 58 
     | 
    
         
            +
                    @cert_path = persist_dir.join("#{serial}.crt")
         
     | 
| 
      
 59 
     | 
    
         
            +
                    @cert_path.write(cert_pem)
         
     | 
| 
      
 60 
     | 
    
         
            +
             
     | 
| 
      
 61 
     | 
    
         
            +
                    @private_key_path = persist_dir.join("#{serial}.key")
         
     | 
| 
      
 62 
     | 
    
         
            +
                    @private_key_path.write(key_pem)
         
     | 
| 
       59 
63 
     | 
    
         
             
                  end
         
     | 
| 
       60 
64 
     | 
    
         | 
| 
       61 
65 
     | 
    
         
             
                  def purge_working_files
         
     | 
| 
       62 
     | 
    
         
            -
                     
     | 
| 
       63 
     | 
    
         
            -
                     
     | 
| 
      
 66 
     | 
    
         
            +
                    return unless persist_dir
         
     | 
| 
      
 67 
     | 
    
         
            +
                    return nil unless persist_dir.directory? && persist_dir.writable?
         
     | 
| 
      
 68 
     | 
    
         
            +
             
     | 
| 
      
 69 
     | 
    
         
            +
                    [@cert_path, @private_key_path].each do |path|
         
     | 
| 
      
 70 
     | 
    
         
            +
                      next unless path&.exist? && path&.writable?
         
     | 
| 
      
 71 
     | 
    
         
            +
             
     | 
| 
      
 72 
     | 
    
         
            +
                      path.delete
         
     | 
| 
      
 73 
     | 
    
         
            +
                    end
         
     | 
| 
       64 
74 
     | 
    
         
             
                  end
         
     | 
| 
       65 
75 
     | 
    
         
             
                end
         
     | 
| 
       66 
76 
     | 
    
         
             
              end
         
     | 
| 
         @@ -15,19 +15,22 @@ module Anchor 
     | 
|
| 
       15 
15 
     | 
    
         | 
| 
       16 
16 
     | 
    
         
             
                  extend Forwardable
         
     | 
| 
       17 
17 
     | 
    
         
             
                  def_delegators :@configuration,
         
     | 
| 
       18 
     | 
    
         
            -
                                 : 
     | 
| 
      
 18 
     | 
    
         
            +
                                 :check_every_seconds,
         
     | 
| 
      
 19 
     | 
    
         
            +
                                 :cache_dir,
         
     | 
| 
       19 
20 
     | 
    
         
             
                                 :contact,
         
     | 
| 
       20 
21 
     | 
    
         
             
                                 :directory,
         
     | 
| 
       21 
22 
     | 
    
         
             
                                 :external_account_binding,
         
     | 
| 
      
 23 
     | 
    
         
            +
                                 :fallback_identifier,
         
     | 
| 
       22 
24 
     | 
    
         
             
                                 :renew_before_fraction,
         
     | 
| 
       23 
     | 
    
         
            -
                                 :renew_before_seconds
         
     | 
| 
      
 25 
     | 
    
         
            +
                                 :renew_before_seconds,
         
     | 
| 
      
 26 
     | 
    
         
            +
                                 :work_dir
         
     | 
| 
       24 
27 
     | 
    
         | 
| 
       25 
     | 
    
         
            -
                  attr_reader : 
     | 
| 
      
 28 
     | 
    
         
            +
                  attr_reader :disk_store,
         
     | 
| 
      
 29 
     | 
    
         
            +
                              :client,
         
     | 
| 
       26 
30 
     | 
    
         
             
                              :configuration,
         
     | 
| 
       27 
31 
     | 
    
         
             
                              :directory_url,
         
     | 
| 
       28 
32 
     | 
    
         
             
                              :identifier_policies,
         
     | 
| 
       29 
     | 
    
         
            -
                              :tos_acceptors 
     | 
| 
       30 
     | 
    
         
            -
                              :work_dir
         
     | 
| 
      
 33 
     | 
    
         
            +
                              :tos_acceptors
         
     | 
| 
       31 
34 
     | 
    
         | 
| 
       32 
35 
     | 
    
         
             
                  def self.for(configuration)
         
     | 
| 
       33 
36 
     | 
    
         
             
                    new(configuration: configuration)
         
     | 
| 
         @@ -37,10 +40,12 @@ module Anchor 
     | 
|
| 
       37 
40 
     | 
    
         
             
                    configuration.validate!
         
     | 
| 
       38 
41 
     | 
    
         
             
                    @configuration = configuration
         
     | 
| 
       39 
42 
     | 
    
         | 
| 
      
 43 
     | 
    
         
            +
                    # disk store early since other things may use it
         
     | 
| 
      
 44 
     | 
    
         
            +
                    @disk_store = DiskStore.new(dir: @configuration.cache_dir, basename: 'autocert-manager')
         
     | 
| 
      
 45 
     | 
    
         
            +
             
     | 
| 
       40 
46 
     | 
    
         
             
                    @identifier_policies = IdentifierPolicy.build(@configuration.allow_identifiers)
         
     | 
| 
       41 
47 
     | 
    
         
             
                    @tos_acceptors = Array(@configuration.tos_acceptors)
         
     | 
| 
       42 
48 
     | 
    
         
             
                    @directory_url = URI.parse(@configuration.directory_url)
         
     | 
| 
       43 
     | 
    
         
            -
                    @work_dir      = Pathname.new(@configuration.work_dir || Dir.mktmpdir)
         
     | 
| 
       44 
49 
     | 
    
         | 
| 
       45 
50 
     | 
    
         
             
                    @account_opts = {
         
     | 
| 
       46 
51 
     | 
    
         
             
                      contact: @configuration.contact,
         
     | 
| 
         @@ -49,30 +54,59 @@ module Anchor 
     | 
|
| 
       49 
54 
     | 
    
         | 
| 
       50 
55 
     | 
    
         
             
                    @client = client || new_client(contact: @configuration.contact)
         
     | 
| 
       51 
56 
     | 
    
         
             
                    @enabled = true
         
     | 
| 
      
 57 
     | 
    
         
            +
                    @managed_certificates = {}
         
     | 
| 
       52 
58 
     | 
    
         
             
                  end
         
     | 
| 
       53 
59 
     | 
    
         | 
| 
       54 
60 
     | 
    
         
             
                  # It is currently assumed that the common name is the first of the
         
     | 
| 
       55 
61 
     | 
    
         
             
                  # `identifiers` passed into this method. If that is not the case, then
         
     | 
| 
       56 
62 
     | 
    
         
             
                  # the `common_name` parameter needs to be set explicitly
         
     | 
| 
       57 
     | 
    
         
            -
                  def managed_certificate(identifiers:, algorithm: :ecdsa, common_name: Array(identifiers).first, 
     | 
| 
       58 
     | 
    
         
            -
             
     | 
| 
       59 
     | 
    
         
            -
             
     | 
| 
      
 63 
     | 
    
         
            +
                  def managed_certificate(identifiers:, algorithm: :ecdsa, common_name: Array(identifiers).first,
         
     | 
| 
      
 64 
     | 
    
         
            +
                                          now: Time.now.utc, **opts)
         
     | 
| 
      
 65 
     | 
    
         
            +
                    full_ids = consolidate_identifiers(common_name: common_name, identifiers: identifiers)
         
     | 
| 
      
 66 
     | 
    
         
            +
                    denied_ids = denied_identifiers(full_ids)
         
     | 
| 
      
 67 
     | 
    
         
            +
             
     | 
| 
      
 68 
     | 
    
         
            +
                    # Fallback to a configured identifier if the requested one(s) are denied
         
     | 
| 
      
 69 
     | 
    
         
            +
                    if denied_ids.any?
         
     | 
| 
      
 70 
     | 
    
         
            +
                      common_name = fallback_identifier
         
     | 
| 
      
 71 
     | 
    
         
            +
                      identifiers = []
         
     | 
| 
       60 
72 
     | 
    
         
             
                    end
         
     | 
| 
       61 
73 
     | 
    
         | 
| 
       62 
     | 
    
         
            -
                     
     | 
| 
       63 
     | 
    
         
            -
                     
     | 
| 
      
 74 
     | 
    
         
            +
                    # first look and see if its memory
         
     | 
| 
      
 75 
     | 
    
         
            +
                    managed_certificate = @managed_certificates[common_name]
         
     | 
| 
      
 76 
     | 
    
         
            +
                    return managed_certificate if managed_certificate && !needs_renewal?(cert: managed_certificate, now: now)
         
     | 
| 
      
 77 
     | 
    
         
            +
             
     | 
| 
      
 78 
     | 
    
         
            +
                    # then look into the disk cache
         
     | 
| 
      
 79 
     | 
    
         
            +
                    if @disk_store
         
     | 
| 
      
 80 
     | 
    
         
            +
                      key_pem = @disk_store["#{common_name}.key.pem"]
         
     | 
| 
      
 81 
     | 
    
         
            +
                      cert_pem = @disk_store["#{common_name}.cert.pem"]
         
     | 
| 
      
 82 
     | 
    
         
            +
                    end
         
     | 
| 
       64 
83 
     | 
    
         | 
| 
       65 
84 
     | 
    
         
             
                    if !key_pem.nil? && !cert_pem.nil?
         
     | 
| 
       66 
     | 
    
         
            -
                       
     | 
| 
       67 
     | 
    
         
            -
             
     | 
| 
      
 85 
     | 
    
         
            +
                      managed_certificate = ManagedCertificate.new(cert_pem: cert_pem,
         
     | 
| 
      
 86 
     | 
    
         
            +
                                                                   key_pem: key_pem,
         
     | 
| 
      
 87 
     | 
    
         
            +
                                                                   persist_dir: work_dir)
         
     | 
| 
      
 88 
     | 
    
         
            +
                      if managed_certificate && !needs_renewal?(cert: managed_certificate, now: now)
         
     | 
| 
      
 89 
     | 
    
         
            +
                        return managed_certificate
         
     | 
| 
      
 90 
     | 
    
         
            +
                      end
         
     | 
| 
       68 
91 
     | 
    
         
             
                    end
         
     | 
| 
       69 
92 
     | 
    
         | 
| 
       70 
     | 
    
         
            -
                     
     | 
| 
      
 93 
     | 
    
         
            +
                    # and then provision a new one
         
     | 
| 
      
 94 
     | 
    
         
            +
                    cert_pem, key_pem = provision_or_fallback(
         
     | 
| 
      
 95 
     | 
    
         
            +
                      identifiers: identifiers, algorithm: algorithm,
         
     | 
| 
      
 96 
     | 
    
         
            +
                      common_name: common_name,
         
     | 
| 
      
 97 
     | 
    
         
            +
                      **opts
         
     | 
| 
      
 98 
     | 
    
         
            +
                    )
         
     | 
| 
       71 
99 
     | 
    
         | 
| 
       72 
     | 
    
         
            -
                     
     | 
| 
       73 
     | 
    
         
            -
                    cache&.write(common_name, cert_pem)
         
     | 
| 
      
 100 
     | 
    
         
            +
                    managed_certificate = ManagedCertificate.new(cert_pem: cert_pem, key_pem: key_pem, persist_dir: work_dir)
         
     | 
| 
       74 
101 
     | 
    
         | 
| 
       75 
     | 
    
         
            -
                     
     | 
| 
      
 102 
     | 
    
         
            +
                    @managed_certificates[common_name] = managed_certificate
         
     | 
| 
      
 103 
     | 
    
         
            +
             
     | 
| 
      
 104 
     | 
    
         
            +
                    if @disk_store
         
     | 
| 
      
 105 
     | 
    
         
            +
                      @disk_store["#{common_name}.key.pem"] = key_pem
         
     | 
| 
      
 106 
     | 
    
         
            +
                      @disk_store["#{common_name}.cert.pem"] = cert_pem
         
     | 
| 
      
 107 
     | 
    
         
            +
                    end
         
     | 
| 
      
 108 
     | 
    
         
            +
             
     | 
| 
      
 109 
     | 
    
         
            +
                    managed_certificate
         
     | 
| 
       76 
110 
     | 
    
         
             
                  end
         
     | 
| 
       77 
111 
     | 
    
         | 
| 
       78 
112 
     | 
    
         
             
                  def needs_renewal?(cert:, now: Time.now.utc)
         
     | 
| 
         @@ -97,12 +131,25 @@ module Anchor 
     | 
|
| 
       97 
131 
     | 
    
         | 
| 
       98 
132 
     | 
    
         
             
                  private
         
     | 
| 
       99 
133 
     | 
    
         | 
| 
      
 134 
     | 
    
         
            +
                  def provision_or_fallback(identifiers:, algorithm:, common_name:, **opts)
         
     | 
| 
      
 135 
     | 
    
         
            +
                    cert_pem = nil
         
     | 
| 
      
 136 
     | 
    
         
            +
                    key_pem = nil
         
     | 
| 
      
 137 
     | 
    
         
            +
                    begin
         
     | 
| 
      
 138 
     | 
    
         
            +
                      cert_pem, key_pem = provision(identifiers: identifiers, algorithm: algorithm, common_name: common_name,
         
     | 
| 
      
 139 
     | 
    
         
            +
                                                    **opts)
         
     | 
| 
      
 140 
     | 
    
         
            +
                    rescue StandardError => _e
         
     | 
| 
      
 141 
     | 
    
         
            +
                      cert_pem, key_pem = provision(identifiers: [], algorithm: algorithm, common_name: fallback_identifier,
         
     | 
| 
      
 142 
     | 
    
         
            +
                                                    **opts)
         
     | 
| 
      
 143 
     | 
    
         
            +
                    end
         
     | 
| 
      
 144 
     | 
    
         
            +
                    [cert_pem, key_pem]
         
     | 
| 
      
 145 
     | 
    
         
            +
                  end
         
     | 
| 
      
 146 
     | 
    
         
            +
             
     | 
| 
       100 
147 
     | 
    
         
             
                  def provision(identifiers:, algorithm:, common_name:, **opts)
         
     | 
| 
       101 
     | 
    
         
            -
                    identifiers =  
     | 
| 
      
 148 
     | 
    
         
            +
                    identifiers = consolidate_identifiers(common_name: common_name, identifiers: identifiers)
         
     | 
| 
       102 
149 
     | 
    
         
             
                    load_or_build_account
         
     | 
| 
       103 
150 
     | 
    
         
             
                    key_pem ||= new_key(algorithm).to_pem
         
     | 
| 
       104 
     | 
    
         
            -
                    csr = Acme::Client::CertificateRequest.new( 
     | 
| 
       105 
     | 
    
         
            -
                                                                
     | 
| 
      
 151 
     | 
    
         
            +
                    csr = Acme::Client::CertificateRequest.new(common_name: common_name, names: identifiers,
         
     | 
| 
      
 152 
     | 
    
         
            +
                                                               private_key: parse_key_pem(key_pem))
         
     | 
| 
       106 
153 
     | 
    
         | 
| 
       107 
154 
     | 
    
         
             
                    order = @client.new_order(identifiers: identifiers, **opts)
         
     | 
| 
       108 
155 
     | 
    
         
             
                    order.finalize(csr: csr)
         
     | 
| 
         @@ -154,12 +201,13 @@ module Anchor 
     | 
|
| 
       154 
201 
     | 
    
         
             
                  end
         
     | 
| 
       155 
202 
     | 
    
         | 
| 
       156 
203 
     | 
    
         
             
                  def new_client(account_key: nil, contact: nil, **)
         
     | 
| 
       157 
     | 
    
         
            -
                    account_key ||=  
     | 
| 
      
 204 
     | 
    
         
            +
                    account_key ||= account_key_for(contact)
         
     | 
| 
       158 
205 
     | 
    
         | 
| 
       159 
206 
     | 
    
         
             
                    Acme::Client.new(private_key: account_key, directory: @directory_url)
         
     | 
| 
       160 
207 
     | 
    
         
             
                  end
         
     | 
| 
       161 
208 
     | 
    
         | 
| 
       162 
     | 
    
         
            -
                   
     | 
| 
      
 209 
     | 
    
         
            +
                  # currently only using ecdsa algorithm
         
     | 
| 
      
 210 
     | 
    
         
            +
                  def new_key(algorithm = :ecdsa)
         
     | 
| 
       163 
211 
     | 
    
         
             
                    case algorithm
         
     | 
| 
       164 
212 
     | 
    
         
             
                    when :ecdsa then OpenSSL::PKey::EC.generate('prime256v1')
         
     | 
| 
       165 
213 
     | 
    
         
             
                    else
         
     | 
| 
         @@ -167,25 +215,19 @@ module Anchor 
     | 
|
| 
       167 
215 
     | 
    
         
             
                    end
         
     | 
| 
       168 
216 
     | 
    
         
             
                  end
         
     | 
| 
       169 
217 
     | 
    
         | 
| 
       170 
     | 
    
         
            -
                  def  
     | 
| 
       171 
     | 
    
         
            -
                     
     | 
| 
       172 
     | 
    
         
            -
                    parse_key_pem(cache&.fetch(id) { parse_key_pem(yield.to_pem) } || yield.to_pem)
         
     | 
| 
       173 
     | 
    
         
            -
                  end
         
     | 
| 
      
 218 
     | 
    
         
            +
                  def account_key_for(contact)
         
     | 
| 
      
 219 
     | 
    
         
            +
                    return new_key unless @disk_store
         
     | 
| 
       174 
220 
     | 
    
         | 
| 
       175 
     | 
    
         
            -
             
     | 
| 
       176 
     | 
    
         
            -
                     
     | 
| 
       177 
     | 
    
         
            -
                     
     | 
| 
      
 221 
     | 
    
         
            +
                    account_key_id = "#{contact || 'default'}+#{@directory_url}+key"
         
     | 
| 
      
 222 
     | 
    
         
            +
                    pem = @disk_store[account_key_id]
         
     | 
| 
      
 223 
     | 
    
         
            +
                    return parse_key_pem(pem) if pem
         
     | 
| 
       178 
224 
     | 
    
         | 
| 
       179 
     | 
    
         
            -
                     
     | 
| 
      
 225 
     | 
    
         
            +
                    raw_key = new_key
         
     | 
| 
      
 226 
     | 
    
         
            +
                    @disk_store[account_key_id] = raw_key.to_pem
         
     | 
| 
      
 227 
     | 
    
         
            +
                    raw_key
         
     | 
| 
       180 
228 
     | 
    
         
             
                  end
         
     | 
| 
       181 
229 
     | 
    
         | 
| 
       182 
     | 
    
         
            -
                  def  
     | 
| 
       183 
     | 
    
         
            -
                    OpenSSL::PKey::RSA.new(data)
         
     | 
| 
       184 
     | 
    
         
            -
                  rescue StandardError
         
     | 
| 
       185 
     | 
    
         
            -
                    nil
         
     | 
| 
       186 
     | 
    
         
            -
                  end
         
     | 
| 
       187 
     | 
    
         
            -
             
     | 
| 
       188 
     | 
    
         
            -
                  def parse_ecdsa_pem(data)
         
     | 
| 
      
 230 
     | 
    
         
            +
                  def parse_key_pem(data)
         
     | 
| 
       189 
231 
     | 
    
         
             
                    OpenSSL::PKey::EC.new(data)
         
     | 
| 
       190 
232 
     | 
    
         
             
                  rescue StandardError
         
     | 
| 
       191 
233 
     | 
    
         
             
                    nil
         
     | 
| 
         @@ -197,6 +239,12 @@ module Anchor 
     | 
|
| 
       197 
239 
     | 
    
         
             
                      @identifier_policies.any? { |policy| policy.allow?(identifier) }
         
     | 
| 
       198 
240 
     | 
    
         
             
                    end
         
     | 
| 
       199 
241 
     | 
    
         
             
                  end
         
     | 
| 
      
 242 
     | 
    
         
            +
             
     | 
| 
      
 243 
     | 
    
         
            +
                  # return a list of identifiers with duplicates removed
         
     | 
| 
      
 244 
     | 
    
         
            +
                  #  preserving order with the common_name first
         
     | 
| 
      
 245 
     | 
    
         
            +
                  def consolidate_identifiers(common_name:, identifiers: [])
         
     | 
| 
      
 246 
     | 
    
         
            +
                    [common_name, *identifiers].compact.uniq
         
     | 
| 
      
 247 
     | 
    
         
            +
                  end
         
     | 
| 
       200 
248 
     | 
    
         
             
                end
         
     | 
| 
       201 
249 
     | 
    
         
             
              end
         
     | 
| 
       202 
250 
     | 
    
         
             
            end
         
     | 
| 
         @@ -67,7 +67,7 @@ module Anchor 
     | 
|
| 
       67 
67 
     | 
    
         
             
                    # set explicitly.
         
     | 
| 
       68 
68 
     | 
    
         
             
                    acme_scratch_dir = app.root / 'tmp' / 'acme'
         
     | 
| 
       69 
69 
     | 
    
         
             
                    acme_scratch_dir.mkpath
         
     | 
| 
       70 
     | 
    
         
            -
                    auto_cert_config. 
     | 
| 
      
 70 
     | 
    
         
            +
                    auto_cert_config.cache_dir ||= (acme_scratch_dir / 'cache')
         
     | 
| 
       71 
71 
     | 
    
         
             
                    auto_cert_config.work_dir ||= (acme_scratch_dir / 'work')
         
     | 
| 
       72 
72 
     | 
    
         | 
| 
       73 
73 
     | 
    
         
             
                    auto_cert_config
         
     | 
| 
         @@ -16,19 +16,20 @@ module Anchor 
     | 
|
| 
       16 
16 
     | 
    
         
             
                class RenewalBusyWait
         
     | 
| 
       17 
17 
     | 
    
         
             
                  ONE_HOUR = 60 * 60
         
     | 
| 
       18 
18 
     | 
    
         | 
| 
       19 
     | 
    
         
            -
                  def self.wait_for_it(managed_certificate:, check_every: ONE_HOUR, &keep_going)
         
     | 
| 
       20 
     | 
    
         
            -
                    waiter = new(managed_certificate: managed_certificate, check_every: check_every)
         
     | 
| 
      
 19 
     | 
    
         
            +
                  def self.wait_for_it(manager:, managed_certificate:, check_every: ONE_HOUR, &keep_going)
         
     | 
| 
      
 20 
     | 
    
         
            +
                    waiter = new(manager: manager, managed_certificate: managed_certificate, check_every: check_every)
         
     | 
| 
       21 
21 
     | 
    
         
             
                    waiter.wait_for_it(&keep_going)
         
     | 
| 
       22 
22 
     | 
    
         
             
                  end
         
     | 
| 
       23 
23 
     | 
    
         | 
| 
       24 
     | 
    
         
            -
                  def initialize(managed_certificate:, check_every: ONE_HOUR)
         
     | 
| 
      
 24 
     | 
    
         
            +
                  def initialize(manager:, managed_certificate:, check_every: ONE_HOUR)
         
     | 
| 
      
 25 
     | 
    
         
            +
                    @manager = manager
         
     | 
| 
       25 
26 
     | 
    
         
             
                    @managed_certificate = managed_certificate
         
     | 
| 
       26 
27 
     | 
    
         
             
                    @check_every = check_every
         
     | 
| 
       27 
28 
     | 
    
         
             
                  end
         
     | 
| 
       28 
29 
     | 
    
         | 
| 
       29 
30 
     | 
    
         
             
                  def wait_for_it
         
     | 
| 
       30 
31 
     | 
    
         
             
                    loop do
         
     | 
| 
       31 
     | 
    
         
            -
                      break if @ 
     | 
| 
      
 32 
     | 
    
         
            +
                      break if @manager.needs_renewal?(cert: @managed_certificate)
         
     | 
| 
       32 
33 
     | 
    
         
             
                      break unless yield
         
     | 
| 
       33 
34 
     | 
    
         | 
| 
       34 
35 
     | 
    
         
             
                      sleep @check_every
         
     | 
| 
         @@ -0,0 +1,31 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            require 'pstore'
         
     | 
| 
      
 4 
     | 
    
         
            +
            require 'tempfile'
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
            module Anchor
         
     | 
| 
      
 7 
     | 
    
         
            +
              # DiskStore is a simple key/value store that persists to disk using PStore.
         
     | 
| 
      
 8 
     | 
    
         
            +
              #
         
     | 
| 
      
 9 
     | 
    
         
            +
              class DiskStore
         
     | 
| 
      
 10 
     | 
    
         
            +
                def initialize(dir: nil, basename: 'anchor-disk-store')
         
     | 
| 
      
 11 
     | 
    
         
            +
                  @dir = dir || Dir.mktmpdir
         
     | 
| 
      
 12 
     | 
    
         
            +
                  @basename = basename
         
     | 
| 
      
 13 
     | 
    
         
            +
                  @path = File.join(@dir, @basename)
         
     | 
| 
      
 14 
     | 
    
         
            +
                  @pstore = PStore.new(@path, true)
         
     | 
| 
      
 15 
     | 
    
         
            +
                end
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
                def [](key)
         
     | 
| 
      
 18 
     | 
    
         
            +
                  data = nil
         
     | 
| 
      
 19 
     | 
    
         
            +
                  @pstore.transaction do
         
     | 
| 
      
 20 
     | 
    
         
            +
                    data = @pstore[key]
         
     | 
| 
      
 21 
     | 
    
         
            +
                  end
         
     | 
| 
      
 22 
     | 
    
         
            +
                  data
         
     | 
| 
      
 23 
     | 
    
         
            +
                end
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
                def []=(key, value)
         
     | 
| 
      
 26 
     | 
    
         
            +
                  @pstore.transaction do
         
     | 
| 
      
 27 
     | 
    
         
            +
                    @pstore[key] = value
         
     | 
| 
      
 28 
     | 
    
         
            +
                  end
         
     | 
| 
      
 29 
     | 
    
         
            +
                end
         
     | 
| 
      
 30 
     | 
    
         
            +
              end
         
     | 
| 
      
 31 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,79 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module Anchor
         
     | 
| 
      
 4 
     | 
    
         
            +
              # PEMBundle is a collection of PEM encoded certificates. It can be written
         
     | 
| 
      
 5 
     | 
    
         
            +
              # to a temporarly file on disk as a bundle if needed.
         
     | 
| 
      
 6 
     | 
    
         
            +
              #
         
     | 
| 
      
 7 
     | 
    
         
            +
              # This temp file to disk is needed for some other libraries that require a
         
     | 
| 
      
 8 
     | 
    
         
            +
              # path to a pem file, and not a string of pem encoded certificates.
         
     | 
| 
      
 9 
     | 
    
         
            +
              #
         
     | 
| 
      
 10 
     | 
    
         
            +
              class PemBundle
         
     | 
| 
      
 11 
     | 
    
         
            +
                DEFAULT_BASENAME = 'bundle.pem'
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
                def initialize(pems: [])
         
     | 
| 
      
 14 
     | 
    
         
            +
                  @pems = pems || []
         
     | 
| 
      
 15 
     | 
    
         
            +
                  @path = nil
         
     | 
| 
      
 16 
     | 
    
         
            +
                  @temp_dir = nil
         
     | 
| 
      
 17 
     | 
    
         
            +
                  @basename = DEFAULT_BASENAME
         
     | 
| 
      
 18 
     | 
    
         
            +
                end
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
      
 20 
     | 
    
         
            +
                def pems
         
     | 
| 
      
 21 
     | 
    
         
            +
                  @pems.dup
         
     | 
| 
      
 22 
     | 
    
         
            +
                end
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
                def path
         
     | 
| 
      
 25 
     | 
    
         
            +
                  write
         
     | 
| 
      
 26 
     | 
    
         
            +
                  @path
         
     | 
| 
      
 27 
     | 
    
         
            +
                end
         
     | 
| 
      
 28 
     | 
    
         
            +
             
     | 
| 
      
 29 
     | 
    
         
            +
                def with_path
         
     | 
| 
      
 30 
     | 
    
         
            +
                  yield(path)
         
     | 
| 
      
 31 
     | 
    
         
            +
                ensure
         
     | 
| 
      
 32 
     | 
    
         
            +
                  remove_path
         
     | 
| 
      
 33 
     | 
    
         
            +
                end
         
     | 
| 
      
 34 
     | 
    
         
            +
             
     | 
| 
      
 35 
     | 
    
         
            +
                def add_cert(cert)
         
     | 
| 
      
 36 
     | 
    
         
            +
                  @pems << cert
         
     | 
| 
      
 37 
     | 
    
         
            +
                  remove_path
         
     | 
| 
      
 38 
     | 
    
         
            +
                end
         
     | 
| 
      
 39 
     | 
    
         
            +
             
     | 
| 
      
 40 
     | 
    
         
            +
                def to_s
         
     | 
| 
      
 41 
     | 
    
         
            +
                  @pems.join("\n")
         
     | 
| 
      
 42 
     | 
    
         
            +
                end
         
     | 
| 
      
 43 
     | 
    
         
            +
             
     | 
| 
      
 44 
     | 
    
         
            +
                def clear
         
     | 
| 
      
 45 
     | 
    
         
            +
                  @pems.clear
         
     | 
| 
      
 46 
     | 
    
         
            +
                  remove_path
         
     | 
| 
      
 47 
     | 
    
         
            +
                end
         
     | 
| 
      
 48 
     | 
    
         
            +
             
     | 
| 
      
 49 
     | 
    
         
            +
                private
         
     | 
| 
      
 50 
     | 
    
         
            +
             
     | 
| 
      
 51 
     | 
    
         
            +
                def temp_path
         
     | 
| 
      
 52 
     | 
    
         
            +
                  @temp_dir = Dir.mktmpdir
         
     | 
| 
      
 53 
     | 
    
         
            +
                  File.join(@temp_dir, @basename)
         
     | 
| 
      
 54 
     | 
    
         
            +
                end
         
     | 
| 
      
 55 
     | 
    
         
            +
             
     | 
| 
      
 56 
     | 
    
         
            +
                def ensure_path
         
     | 
| 
      
 57 
     | 
    
         
            +
                  return @path if @path
         
     | 
| 
      
 58 
     | 
    
         
            +
             
     | 
| 
      
 59 
     | 
    
         
            +
                  @path = temp_path
         
     | 
| 
      
 60 
     | 
    
         
            +
                end
         
     | 
| 
      
 61 
     | 
    
         
            +
             
     | 
| 
      
 62 
     | 
    
         
            +
                def write
         
     | 
| 
      
 63 
     | 
    
         
            +
                  remove_path
         
     | 
| 
      
 64 
     | 
    
         
            +
                  ensure_path
         
     | 
| 
      
 65 
     | 
    
         
            +
                  File.open(@path, 'w+', 0o400) do |io|
         
     | 
| 
      
 66 
     | 
    
         
            +
                    io.write(to_s)
         
     | 
| 
      
 67 
     | 
    
         
            +
                  end
         
     | 
| 
      
 68 
     | 
    
         
            +
                end
         
     | 
| 
      
 69 
     | 
    
         
            +
             
     | 
| 
      
 70 
     | 
    
         
            +
                def remove_path
         
     | 
| 
      
 71 
     | 
    
         
            +
                  File.unlink(@path) if @path && File.exist?(@path)
         
     | 
| 
      
 72 
     | 
    
         
            +
             
     | 
| 
      
 73 
     | 
    
         
            +
                  if @temp_dir && Dir.exist?(@temp_dir)
         
     | 
| 
      
 74 
     | 
    
         
            +
                    FileUtils.remove_entry_secure(@temp_dir)
         
     | 
| 
      
 75 
     | 
    
         
            +
                    @temp_dir = nil
         
     | 
| 
      
 76 
     | 
    
         
            +
                  end
         
     | 
| 
      
 77 
     | 
    
         
            +
                end
         
     | 
| 
      
 78 
     | 
    
         
            +
              end
         
     | 
| 
      
 79 
     | 
    
         
            +
            end
         
     | 
    
        data/lib/anchor/version.rb
    CHANGED
    
    
    
        data/lib/anchor.rb
    CHANGED
    
    
| 
         @@ -13,7 +13,7 @@ module Puma 
     | 
|
| 
       13 
13 
     | 
    
         
             
                    def ssl_bind_options(managed_certificate:)
         
     | 
| 
       14 
14 
     | 
    
         
             
                      {
         
     | 
| 
       15 
15 
     | 
    
         
             
                        cert: managed_certificate.cert_path,
         
     | 
| 
       16 
     | 
    
         
            -
                        key: managed_certificate. 
     | 
| 
      
 16 
     | 
    
         
            +
                        key: managed_certificate.private_key_path
         
     | 
| 
       17 
17 
     | 
    
         
             
                      }
         
     | 
| 
       18 
18 
     | 
    
         
             
                    end
         
     | 
| 
       19 
19 
     | 
    
         
             
                  end
         
     | 
| 
         @@ -22,32 +22,33 @@ module Puma 
     | 
|
| 
       22 
22 
     | 
    
         
             
                  # a plugin is created
         
     | 
| 
       23 
23 
     | 
    
         
             
                  module PluginInstanceMethods
         
     | 
| 
       24 
24 
     | 
    
         
             
                    attr_accessor :managed_certificate
         
     | 
| 
       25 
     | 
    
         
            -
                    attr_reader :port
         
     | 
| 
      
 25 
     | 
    
         
            +
                    attr_reader :manager, :port
         
     | 
| 
       26 
26 
     | 
    
         | 
| 
       27 
27 
     | 
    
         
             
                    def config(dsl)
         
     | 
| 
       28 
28 
     | 
    
         
             
                      @port         = dsl.auto_cert_port || ENV.fetch('HTTPS_PORT', nil)
         
     | 
| 
       29 
29 
     | 
    
         
             
                      name          = dsl.auto_cert_name || ENV.fetch('AUTO_CERT_NAME', 'default')
         
     | 
| 
       30 
30 
     | 
    
         
             
                      configuration = ::Anchor::AutoCert::Registry.fetch(name)
         
     | 
| 
       31 
31 
     | 
    
         
             
                      identifiers   = configuration.allow_identifiers
         
     | 
| 
       32 
     | 
    
         
            -
                      manager 
     | 
| 
      
 32 
     | 
    
         
            +
                      @manager      = ::Anchor::AutoCert::Manager.new(configuration: configuration)
         
     | 
| 
       33 
33 
     | 
    
         | 
| 
       34 
34 
     | 
    
         
             
                      @managed_certificate = manager.managed_certificate(identifiers: identifiers)
         
     | 
| 
       35 
35 
     | 
    
         | 
| 
       36 
36 
     | 
    
         
             
                      options = ::Puma::Plugin::AutoCert.ssl_bind_options(managed_certificate: @managed_certificate)
         
     | 
| 
       37 
37 
     | 
    
         | 
| 
       38 
38 
     | 
    
         
             
                      dsl.ssl_bind '[::]', port, options
         
     | 
| 
       39 
     | 
    
         
            -
                    rescue StandardError
         
     | 
| 
      
 39 
     | 
    
         
            +
                    rescue StandardError => _e
         
     | 
| 
      
 40 
     | 
    
         
            +
                      @manager = nil
         
     | 
| 
       40 
41 
     | 
    
         
             
                      @managed_certificate = nil
         
     | 
| 
       41 
42 
     | 
    
         
             
                    end
         
     | 
| 
       42 
43 
     | 
    
         | 
| 
       43 
44 
     | 
    
         
             
                    def start(launcher)
         
     | 
| 
       44 
45 
     | 
    
         
             
                      @launcher = launcher
         
     | 
| 
       45 
     | 
    
         
            -
                      unless  
     | 
| 
      
 46 
     | 
    
         
            +
                      unless manager&.enabled? && managed_certificate
         
     | 
| 
       46 
47 
     | 
    
         
             
                        log_writer.log 'AutoCert >> Not enabled - skipping certificate renewal process'
         
     | 
| 
       47 
48 
     | 
    
         
             
                        return
         
     | 
| 
       48 
49 
     | 
    
         
             
                      end
         
     | 
| 
       49 
50 
     | 
    
         | 
| 
       50 
     | 
    
         
            -
                       
     | 
| 
      
 51 
     | 
    
         
            +
                      managed_certificate.identifiers.each do |identifier|
         
     | 
| 
       51 
52 
     | 
    
         
             
                        log_writer.log "AutoCert >> Available at https://#{identifier}:#{port}/"
         
     | 
| 
       52 
53 
     | 
    
         
             
                      end
         
     | 
| 
       53 
54 
     | 
    
         | 
| 
         @@ -56,7 +57,8 @@ module Puma 
     | 
|
| 
       56 
57 
     | 
    
         
             
                                    ::Anchor::AutoCert::RenewalBusyWait::ONE_HOUR
         
     | 
| 
       57 
58 
     | 
    
         | 
| 
       58 
59 
     | 
    
         
             
                      in_background do
         
     | 
| 
       59 
     | 
    
         
            -
                        Anchor::AutoCert::RenewalBusyWait.wait_for_it( 
     | 
| 
      
 60 
     | 
    
         
            +
                        Anchor::AutoCert::RenewalBusyWait.wait_for_it(manager: manager,
         
     | 
| 
      
 61 
     | 
    
         
            +
                                                                      managed_certificate: managed_certificate,
         
     | 
| 
       60 
62 
     | 
    
         
             
                                                                      check_every: check_every) do
         
     | 
| 
       61 
63 
     | 
    
         
             
                          dump_cert_info
         
     | 
| 
       62 
64 
     | 
    
         | 
    
        metadata
    CHANGED
    
    | 
         @@ -1,14 +1,14 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            --- !ruby/object:Gem::Specification
         
     | 
| 
       2 
2 
     | 
    
         
             
            name: anchor-pki
         
     | 
| 
       3 
3 
     | 
    
         
             
            version: !ruby/object:Gem::Version
         
     | 
| 
       4 
     | 
    
         
            -
              version: 0. 
     | 
| 
      
 4 
     | 
    
         
            +
              version: 0.6.0
         
     | 
| 
       5 
5 
     | 
    
         
             
            platform: ruby
         
     | 
| 
       6 
6 
     | 
    
         
             
            authors:
         
     | 
| 
       7 
7 
     | 
    
         
             
            - Anchor Security, Inc
         
     | 
| 
       8 
8 
     | 
    
         
             
            autorequire:
         
     | 
| 
       9 
9 
     | 
    
         
             
            bindir: bin
         
     | 
| 
       10 
10 
     | 
    
         
             
            cert_chain: []
         
     | 
| 
       11 
     | 
    
         
            -
            date: 2023- 
     | 
| 
      
 11 
     | 
    
         
            +
            date: 2023-11-29 00:00:00.000000000 Z
         
     | 
| 
       12 
12 
     | 
    
         
             
            dependencies:
         
     | 
| 
       13 
13 
     | 
    
         
             
            - !ruby/object:Gem::Dependency
         
     | 
| 
       14 
14 
     | 
    
         
             
              name: acme-client
         
     | 
| 
         @@ -24,6 +24,20 @@ dependencies: 
     | 
|
| 
       24 
24 
     | 
    
         
             
                - - "~>"
         
     | 
| 
       25 
25 
     | 
    
         
             
                  - !ruby/object:Gem::Version
         
     | 
| 
       26 
26 
     | 
    
         
             
                    version: 2.0.13
         
     | 
| 
      
 27 
     | 
    
         
            +
            - !ruby/object:Gem::Dependency
         
     | 
| 
      
 28 
     | 
    
         
            +
              name: pstore
         
     | 
| 
      
 29 
     | 
    
         
            +
              requirement: !ruby/object:Gem::Requirement
         
     | 
| 
      
 30 
     | 
    
         
            +
                requirements:
         
     | 
| 
      
 31 
     | 
    
         
            +
                - - "~>"
         
     | 
| 
      
 32 
     | 
    
         
            +
                  - !ruby/object:Gem::Version
         
     | 
| 
      
 33 
     | 
    
         
            +
                    version: '0.1'
         
     | 
| 
      
 34 
     | 
    
         
            +
              type: :runtime
         
     | 
| 
      
 35 
     | 
    
         
            +
              prerelease: false
         
     | 
| 
      
 36 
     | 
    
         
            +
              version_requirements: !ruby/object:Gem::Requirement
         
     | 
| 
      
 37 
     | 
    
         
            +
                requirements:
         
     | 
| 
      
 38 
     | 
    
         
            +
                - - "~>"
         
     | 
| 
      
 39 
     | 
    
         
            +
                  - !ruby/object:Gem::Version
         
     | 
| 
      
 40 
     | 
    
         
            +
                    version: '0.1'
         
     | 
| 
       27 
41 
     | 
    
         
             
            - !ruby/object:Gem::Dependency
         
     | 
| 
       28 
42 
     | 
    
         
             
              name: minitest
         
     | 
| 
       29 
43 
     | 
    
         
             
              requirement: !ruby/object:Gem::Requirement
         
     | 
| 
         @@ -94,6 +108,20 @@ dependencies: 
     | 
|
| 
       94 
108 
     | 
    
         
             
                - - "~>"
         
     | 
| 
       95 
109 
     | 
    
         
             
                  - !ruby/object:Gem::Version
         
     | 
| 
       96 
110 
     | 
    
         
             
                    version: '2.22'
         
     | 
| 
      
 111 
     | 
    
         
            +
            - !ruby/object:Gem::Dependency
         
     | 
| 
      
 112 
     | 
    
         
            +
              name: simplecov
         
     | 
| 
      
 113 
     | 
    
         
            +
              requirement: !ruby/object:Gem::Requirement
         
     | 
| 
      
 114 
     | 
    
         
            +
                requirements:
         
     | 
| 
      
 115 
     | 
    
         
            +
                - - "~>"
         
     | 
| 
      
 116 
     | 
    
         
            +
                  - !ruby/object:Gem::Version
         
     | 
| 
      
 117 
     | 
    
         
            +
                    version: '0.22'
         
     | 
| 
      
 118 
     | 
    
         
            +
              type: :development
         
     | 
| 
      
 119 
     | 
    
         
            +
              prerelease: false
         
     | 
| 
      
 120 
     | 
    
         
            +
              version_requirements: !ruby/object:Gem::Requirement
         
     | 
| 
      
 121 
     | 
    
         
            +
                requirements:
         
     | 
| 
      
 122 
     | 
    
         
            +
                - - "~>"
         
     | 
| 
      
 123 
     | 
    
         
            +
                  - !ruby/object:Gem::Version
         
     | 
| 
      
 124 
     | 
    
         
            +
                    version: '0.22'
         
     | 
| 
       97 
125 
     | 
    
         
             
            - !ruby/object:Gem::Dependency
         
     | 
| 
       98 
126 
     | 
    
         
             
              name: vcr
         
     | 
| 
       99 
127 
     | 
    
         
             
              requirement: !ruby/object:Gem::Requirement
         
     | 
| 
         @@ -152,7 +180,9 @@ files: 
     | 
|
| 
       152 
180 
     | 
    
         
             
            - lib/anchor/auto_cert/registry.rb
         
     | 
| 
       153 
181 
     | 
    
         
             
            - lib/anchor/auto_cert/renewal_busy_wait.rb
         
     | 
| 
       154 
182 
     | 
    
         
             
            - lib/anchor/auto_cert/terms_of_service_acceptor.rb
         
     | 
| 
      
 183 
     | 
    
         
            +
            - lib/anchor/disk_store.rb
         
     | 
| 
       155 
184 
     | 
    
         
             
            - lib/anchor/oid.rb
         
     | 
| 
      
 185 
     | 
    
         
            +
            - lib/anchor/pem_bundle.rb
         
     | 
| 
       156 
186 
     | 
    
         
             
            - lib/anchor/version.rb
         
     | 
| 
       157 
187 
     | 
    
         
             
            - lib/puma/dsl.rb
         
     | 
| 
       158 
188 
     | 
    
         
             
            - lib/puma/plugin/auto_cert.rb
         
     |