Class: LetsCert::Runner

Inherits:
Object
  • Object
show all
Defined in:
lib/letscert/runner.rb

Defined Under Namespace

Classes: LoggerFormatter

Constant Summary collapse

RETURN_OK =

Exit value for OK

1
RETURN_OK_CERT =

Exit value for OK but with creation/renewal of certificate data

0
RETURN_ERROR =

Exit value for error(s)

2

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeRunner

Returns a new instance of Runner.



56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
# File 'lib/letscert/runner.rb', line 56

def initialize
  @options = {
    verbose: 0,
    domains: [],
    files: [],
    cert_key_size: 2048,
    valid_min: 30 * 24 * 60 * 60,
    account_key_public_exponent: 65537,
    account_key_size: 4096,
    tos_sha256: '33d233c8ab558ba6c8ebc370a509acdded8b80e5d587aa5d192193f35226540f',
    user_agent: 'letscert/0',
    server: 'https://acme-v01.api.letsencrypt.org/directory',
  }

  @logger = Logger.new(STDOUT)
  @logger.formatter = LoggerFormatter.new
end

Instance Attribute Details

#loggerLogger (readonly)

Returns:

  • (Logger)


44
45
46
# File 'lib/letscert/runner.rb', line 44

def logger
  @logger
end

Class Method Details

.runInteger

Run LetsCert

Returns:

  • (Integer)

See Also:



49
50
51
52
53
# File 'lib/letscert/runner.rb', line 49

def self.run
  runner = new
  runner.parse_options
  runner.run
end

Instance Method Details

#parse_optionsObject



144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
# File 'lib/letscert/runner.rb', line 144

def parse_options
  @opt_parser = OptionParser.new do |opts|
    opts.banner = "Usage: lestcert [options]"

    opts.separator('')

    opts.on('-h', '--help', 'Show this help message and exit') do
      @options[:print_help] = true
    end
    opts.on('-V', '--version', 'Show version and exit') do |v|
      @options[:show_version] = v
    end
    opts.on('-v', '--verbose', 'Run verbosely') { |v| @options[:verbose] += 1 if v }


    opts.separator("\nWebroot manager:")

    opts.on('-d', '--domain DOMAIN[:PATH]',
            'Domain name to include in the certificate.',
            'Must be specified at least once.',
            'Its path on the disk must also be provided.') do |domain|
      @options[:domains] << domain
    end

    opts.on('--default_root PATH', 'Default webroot path',
            'Use for domains without PATH part.') do |path|
      @options[:default_root] = path
    end

    opts.separator("\nCertificate data files:")

    opts.on('--revoke', 'Revoke existing certificates') do |revoke|
      @options[:revoke] = revoke
    end

    opts.on("-f", "--file FILE", 'Input/output file.',
            'Can be specified multiple times',
            'Allowed values: account_key.json, cert.der,',
            'cert.pem, chain.pem, full.pem,',
            'fullchain.pem, key.der, key.pem.') do |file|
      @options[:files] << file
    end

    opts.on('--cert-key-size BITS', Integer,
            'Certificate key size in bits',
            '(default: 2048)') do |bits|
      @options[:cert_key_size] = bits
    end

    opts.on('--valid-min SECONDS', Integer, 'Renew existing certificate if validity',
            'is lesser than SECONDS (default: 2592000 (30 days))') do |time|
      @options[:valid_min] = time
    end

    opts.on('--reuse-key', 'Reuse previous private key') do |rk|
      @options[:reuse_key] = rk
    end

    opts.separator("\nRegistration:")
    opts.separator("  Automatically register an account with he ACME CA specified" +
                   " by --server")
    opts.separator('')

    opts.on('--account-key-public-exponent BITS', Integer,
            'Account key public exponent value (default: 65537)') do |bits|
      @options[:account_key_public_exponent] = bits
    end

    opts.on('--account-key-size BITS', Integer,
            'Account key size (default: 4096)') do |bits|
      @options[:account_key_size] = bits
    end

    opts.on('--tos-sha256 HASH', String,
            'SHA-256 digest of the content of Terms Of Service URI') do |hash|
      @options[:tos_sha256] = hash
    end

    opts.on('--email EMAIL', String,
            'E-mail address. CA is likely to use it to',
            'remind about expiring certificates, as well',
            'as for account recovery. It is highly',
            'recommended to set this value.') do |email|
      @options[:email] = email
    end

    opts.separator("\nHTTP:")
    opts.separator('  Configure properties of HTTP requests and responses.')
    opts.separator('')

    opts.on('--user-agent NAME', 'User-Agent sent in all HTTP requests',
            '(default: letscert/0)') do |ua|
      @options[:user_agent] = ua
    end
    
    opts.on('--server URI', 'URI for the CA ACME API endpoint',
            '(default: https://acme-v01.api.letsencrypt.org/directory)') do |uri|
      @options[:server] = uri
    end
  end

  @opt_parser.parse!
end

#revokeObject



248
249
250
251
252
253
254
255
256
257
# File 'lib/letscert/runner.rb', line 248

def revoke
  @logger.info { "load certificates: #{@options[:files].join(', ')}" }
  if @options[:files].empty?
    raise Error, 'no certificate to revoke. Pass at least one with '+
                 ' -f option.'
  end

  # Temp
  @logger.warn "Not yet implemented"
end

#runInteger

Returns exit code

  • 0 if certificate data were created or updated

  • 1 if renewal was not necessery

  • 2 in case of errors.

Returns:

  • (Integer)

    exit code

    • 0 if certificate data were created or updated

    • 1 if renewal was not necessery

    • 2 in case of errors



78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
# File 'lib/letscert/runner.rb', line 78

def run
  if @options[:print_help]
    puts @opt_parser
    exit RETURN_OK
  end

  if @options[:show_version]
    puts "letscert #{LetsCert::VERSION}"
    puts "Copyright (c) 2016 Sylvain Daubert"
    puts "License MIT: see http://opensource.org/licenses/MIT"
    exit RETURN_OK
  end

  case @options[:verbose]
  when 0
    @logger.level = Logger::Severity::WARN
  when 1
    @logger.level = Logger::Severity::INFO
  when 2..5
    @logger.level = Logger::Severity::DEBUG
  end

  @logger.debug { "options are: #{@options.inspect}" }

  IOPlugin.logger = @logger

  begin
    if @options[:revoke]
      revoke
      RETURN_OK
    elsif @options[:domains].empty?
      raise Error, 'At leat one domain must be given with --domain option'
    else
      # Check all components are covered by plugins
      persisted = IOPlugin.empty_data
      @options[:files].each do |file|
        persisted.merge!(IOPlugin.registered[file].persisted) do |k, oldv, newv|
          oldv || newv
        end
      end
      not_persisted = persisted.keys.find_all { |k| !persisted[k] }
      unless not_persisted.empty?
        raise Error, 'Selected IO plugins do not cover following components: ' +
                     not_persisted.join(', ')
      end

      data = load_data_from_disk(@options[:files])

      if valid_existing_cert(data[:cert], @options[:domains], @options[:valid_min])
        @logger.info { 'no need to update cert' }
        RETURN_OK
      else
        # update/create cert
        new_data(data)
        RETURN_OK_CERT
      end
    end

  rescue Error, Acme::Client::Error => ex
    @logger.error ex.message
    puts "Error: #{ex.message}"
    RETURN_ERROR
  end
end