Class: LetsCert::Runner

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

Overview

Runner class: analyse and execute CLI commands.

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.



83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
# File 'lib/letscert/runner.rb', line 83

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)


71
72
73
# File 'lib/letscert/runner.rb', line 71

def logger
  @logger
end

Class Method Details

.runInteger

Run LetsCert

Returns:

  • (Integer)

See Also:



76
77
78
79
80
# File 'lib/letscert/runner.rb', line 76

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

Instance Method Details

#parse_optionsvoid

This method returns an undefined value.

Parse line command options

Raises:

  • (OptionParser::InvalidOption)

    on unrecognized or malformed option



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
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
# File 'lib/letscert/runner.rb', line 176

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

#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



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
142
143
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
# File 'lib/letscert/runner.rb', line 105

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
  Certificate.logger = @logger

  begin
    if @options[:revoke]
      Certificate.revoke @options[:files]
      RETURN_OK
    elsif @options[:domains].empty?
      raise Error, "At leat one domain must be given with --domain option.\n" +
                   "Try 'letscert --help' for more information."
    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
        Certificate.get @options, data
        RETURN_OK_CERT
      end
    end

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