leap_cli 1.5.6 → 1.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. data/bin/leap +29 -6
  2. data/lib/leap/platform.rb +36 -1
  3. data/lib/leap_cli/commands/ca.rb +97 -20
  4. data/lib/leap_cli/commands/compile.rb +49 -8
  5. data/lib/leap_cli/commands/db.rb +13 -4
  6. data/lib/leap_cli/commands/deploy.rb +138 -29
  7. data/lib/leap_cli/commands/env.rb +76 -0
  8. data/lib/leap_cli/commands/facts.rb +10 -3
  9. data/lib/leap_cli/commands/inspect.rb +2 -2
  10. data/lib/leap_cli/commands/list.rb +10 -10
  11. data/lib/leap_cli/commands/node.rb +7 -132
  12. data/lib/leap_cli/commands/node_init.rb +169 -0
  13. data/lib/leap_cli/commands/pre.rb +4 -27
  14. data/lib/leap_cli/commands/ssh.rb +152 -0
  15. data/lib/leap_cli/commands/test.rb +22 -13
  16. data/lib/leap_cli/commands/user.rb +12 -4
  17. data/lib/leap_cli/commands/vagrant.rb +4 -4
  18. data/lib/leap_cli/config/filter.rb +175 -0
  19. data/lib/leap_cli/config/manager.rb +130 -61
  20. data/lib/leap_cli/config/node.rb +32 -0
  21. data/lib/leap_cli/config/object.rb +69 -44
  22. data/lib/leap_cli/config/object_list.rb +44 -39
  23. data/lib/leap_cli/config/secrets.rb +24 -12
  24. data/lib/leap_cli/config/tag.rb +7 -0
  25. data/lib/{core_ext → leap_cli/core_ext}/boolean.rb +0 -0
  26. data/lib/{core_ext → leap_cli/core_ext}/hash.rb +0 -0
  27. data/lib/{core_ext → leap_cli/core_ext}/json.rb +0 -0
  28. data/lib/{core_ext → leap_cli/core_ext}/nil.rb +0 -0
  29. data/lib/{core_ext → leap_cli/core_ext}/string.rb +0 -0
  30. data/lib/leap_cli/core_ext/yaml.rb +29 -0
  31. data/lib/leap_cli/exceptions.rb +24 -0
  32. data/lib/leap_cli/leapfile.rb +60 -10
  33. data/lib/{lib_ext → leap_cli/lib_ext}/capistrano_connections.rb +0 -0
  34. data/lib/{lib_ext → leap_cli/lib_ext}/gli.rb +0 -0
  35. data/lib/leap_cli/log.rb +1 -1
  36. data/lib/leap_cli/logger.rb +18 -1
  37. data/lib/leap_cli/markdown_document_listener.rb +1 -1
  38. data/lib/leap_cli/override/json.rb +11 -0
  39. data/lib/leap_cli/path.rb +20 -6
  40. data/lib/leap_cli/remote/leap_plugin.rb +2 -2
  41. data/lib/leap_cli/remote/puppet_plugin.rb +1 -1
  42. data/lib/leap_cli/remote/rsync_plugin.rb +1 -1
  43. data/lib/leap_cli/remote/tasks.rb +1 -1
  44. data/lib/leap_cli/ssh_key.rb +63 -1
  45. data/lib/leap_cli/util/remote_command.rb +19 -2
  46. data/lib/leap_cli/util/secret.rb +1 -1
  47. data/lib/leap_cli/util/x509.rb +3 -2
  48. data/lib/leap_cli/util.rb +11 -3
  49. data/lib/leap_cli/version.rb +2 -2
  50. data/lib/leap_cli.rb +24 -14
  51. data/vendor/certificate_authority/lib/certificate_authority/certificate.rb +85 -29
  52. data/vendor/certificate_authority/lib/certificate_authority/distinguished_name.rb +5 -0
  53. data/vendor/certificate_authority/lib/certificate_authority/extensions.rb +406 -41
  54. data/vendor/certificate_authority/lib/certificate_authority/key_material.rb +0 -34
  55. data/vendor/certificate_authority/lib/certificate_authority/serial_number.rb +6 -0
  56. data/vendor/certificate_authority/lib/certificate_authority/signing_request.rb +36 -1
  57. metadata +25 -24
  58. data/lib/leap_cli/commands/shell.rb +0 -89
  59. data/lib/leap_cli/config/macros.rb +0 -430
  60. data/lib/leap_cli/constants.rb +0 -7
  61. data/lib/leap_cli/requirements.rb +0 -19
  62. data/lib/lib_ext/markdown_document_listener.rb +0 -122
@@ -5,6 +5,10 @@ module CertificateAuthority
5
5
  raise "Implementation required"
6
6
  end
7
7
 
8
+ def self.parse(value, critical)
9
+ raise "Implementation required"
10
+ end
11
+
8
12
  def config_extensions
9
13
  {}
10
14
  end
@@ -12,21 +16,40 @@ module CertificateAuthority
12
16
  def openssl_identifier
13
17
  raise "Implementation required"
14
18
  end
19
+
20
+ def ==(value)
21
+ raise "Implementation required"
22
+ end
15
23
  end
16
24
 
25
+ # Specifies whether an X.509v3 certificate can act as a CA, signing other
26
+ # certificates to be verified. If set, a path length constraint can also be
27
+ # specified.
28
+ # Reference: Section 4.2.1.10 of RFC3280
29
+ # http://tools.ietf.org/html/rfc3280#section-4.2.1.10
17
30
  class BasicConstraints
31
+ OPENSSL_IDENTIFIER = "basicConstraints"
32
+
18
33
  include ExtensionAPI
19
34
  include ActiveModel::Validations
35
+
36
+ attr_accessor :critical
20
37
  attr_accessor :ca
21
38
  attr_accessor :path_len
39
+ validates :critical, :inclusion => [true,false]
22
40
  validates :ca, :inclusion => [true,false]
23
41
 
24
42
  def initialize
25
- self.ca = false
43
+ @critical = false
44
+ @ca = false
45
+ end
46
+
47
+ def openssl_identifier
48
+ OPENSSL_IDENTIFIER
26
49
  end
27
50
 
28
51
  def is_ca?
29
- self.ca
52
+ @ca
30
53
  end
31
54
 
32
55
  def path_len=(value)
@@ -34,29 +57,54 @@ module CertificateAuthority
34
57
  @path_len = value
35
58
  end
36
59
 
37
- def openssl_identifier
38
- "basicConstraints"
60
+ def to_s
61
+ res = []
62
+ res << "CA:#{@ca}"
63
+ res << "pathlen:#{@path_len}" unless @path_len.nil?
64
+ res.join(',')
39
65
  end
40
66
 
41
- def to_s
42
- result = ""
43
- result += "CA:#{self.ca}"
44
- result += ",pathlen:#{self.path_len}" unless self.path_len.nil?
45
- result
67
+ def ==(o)
68
+ o.class == self.class && o.state == state
69
+ end
70
+
71
+ def self.parse(value, critical)
72
+ obj = self.new
73
+ return obj if value.nil?
74
+ obj.critical = critical
75
+ value.split(/,\s*/).each do |v|
76
+ c = v.split(':', 2)
77
+ obj.ca = (c.last.upcase == "TRUE") if c.first == "CA"
78
+ obj.path_len = c.last.to_i if c.first == "pathlen"
79
+ end
80
+ obj
81
+ end
82
+
83
+ protected
84
+ def state
85
+ [@critical,@ca,@path_len]
46
86
  end
47
87
  end
48
88
 
89
+ # Specifies where CRL information be be retrieved. This extension isn't
90
+ # critical, but is recommended for proper CAs.
91
+ # Reference: Section 4.2.1.14 of RFC3280
92
+ # http://tools.ietf.org/html/rfc3280#section-4.2.1.14
49
93
  class CrlDistributionPoints
94
+ OPENSSL_IDENTIFIER = "crlDistributionPoints"
95
+
50
96
  include ExtensionAPI
51
97
 
52
- attr_accessor :uri
98
+ attr_accessor :critical
99
+ attr_accessor :uris
53
100
 
54
101
  def initialize
55
- # self.uri = "http://moo.crlendPoint.example.com/something.crl"
102
+ @critical = false
103
+ @uris = []
56
104
  end
57
105
 
58
106
  def openssl_identifier
59
- "crlDistributionPoints"
107
+ OPENSSL_IDENTIFIER
60
108
  end
61
109
 
62
110
  ## NB: At this time it seems OpenSSL's extension handlers don't support
@@ -69,99 +117,302 @@ module CertificateAuthority
69
117
  }
70
118
  end
71
119
 
120
+ # This is for legacy support. Technically it can (and probably should)
121
+ # be an array. But if someone is calling the old accessor we shouldn't
122
+ # necessarily break it.
123
+ def uri=(value)
124
+ @uris << value
125
+ end
126
+
72
127
  def to_s
73
- return "" if self.uri.nil?
74
- "URI:#{self.uri}"
128
+ res = []
129
+ @uris.each do |uri|
130
+ res << "URI:#{uri}"
131
+ end
132
+ res.join(',')
133
+ end
134
+
135
+ def ==(o)
136
+ o.class == self.class && o.state == state
137
+ end
138
+
139
+ def self.parse(value, critical)
140
+ obj = self.new
141
+ return obj if value.nil?
142
+ obj.critical = critical
143
+ value.split(/,\s*/).each do |v|
144
+ c = v.split(':', 2)
145
+ obj.uris << c.last if c.first == "URI"
146
+ end
147
+ obj
148
+ end
149
+
150
+ protected
151
+ def state
152
+ [@critical,@uri]
75
153
  end
76
154
  end
77
155
 
156
+ # Identifies the public key associated with a given certificate.
157
+ # Should be required for "CA" certificates.
158
+ # Reference: Section 4.2.1.2 of RFC3280
159
+ # http://tools.ietf.org/html/rfc3280#section-4.2.1.2
78
160
  class SubjectKeyIdentifier
161
+ OPENSSL_IDENTIFIER = "subjectKeyIdentifier"
162
+
79
163
  include ExtensionAPI
164
+
165
+ attr_accessor :critical
166
+ attr_accessor :identifier
167
+
168
+ def initialize
169
+ @critical = false
170
+ @identifier = "hash"
171
+ end
172
+
80
173
  def openssl_identifier
81
- "subjectKeyIdentifier"
174
+ OPENSSL_IDENTIFIER
82
175
  end
83
176
 
84
177
  def to_s
85
- "hash"
178
+ res = []
179
+ res << @identifier
180
+ res.join(',')
181
+ end
182
+
183
+ def ==(o)
184
+ o.class == self.class && o.state == state
185
+ end
186
+
187
+ def self.parse(value, critical)
188
+ obj = self.new
189
+ return obj if value.nil?
190
+ obj.critical = critical
191
+ obj.identifier = value
192
+ obj
193
+ end
194
+
195
+ protected
196
+ def state
197
+ [@critical,@identifier]
86
198
  end
87
199
  end
88
200
 
201
+ # Identifies the public key associated with a given private key.
202
+ # Reference: Section 4.2.1.1 of RFC3280
203
+ # http://tools.ietf.org/html/rfc3280#section-4.2.1.1
89
204
  class AuthorityKeyIdentifier
205
+ OPENSSL_IDENTIFIER = "authorityKeyIdentifier"
206
+
90
207
  include ExtensionAPI
91
208
 
209
+ attr_accessor :critical
210
+ attr_accessor :identifier
211
+
212
+ def initialize
213
+ @critical = false
214
+ @identifier = ["keyid", "issuer"]
215
+ end
216
+
92
217
  def openssl_identifier
93
- "authorityKeyIdentifier"
218
+ OPENSSL_IDENTIFIER
94
219
  end
95
220
 
96
221
  def to_s
97
- "keyid,issuer"
222
+ res = []
223
+ res += @identifier
224
+ res.join(',')
225
+ end
226
+
227
+ def ==(o)
228
+ o.class == self.class && o.state == state
229
+ end
230
+
231
+ def self.parse(value, critical)
232
+ obj = self.new
233
+ return obj if value.nil?
234
+ obj.critical = critical
235
+ obj.identifier = value.split(/,\s*/).last.chomp
236
+ obj
237
+ end
238
+
239
+ protected
240
+ def state
241
+ [@critical,@identifier]
98
242
  end
99
243
  end
100
244
 
245
+ # Specifies how to access CA information and services for the CA that
246
+ # issued this certificate.
247
+ # Generally used to specify OCSP servers.
248
+ # Reference: Section 4.2.2.1 of RFC3280
249
+ # http://tools.ietf.org/html/rfc3280#section-4.2.2.1
101
250
  class AuthorityInfoAccess
251
+ OPENSSL_IDENTIFIER = "authorityInfoAccess"
252
+
102
253
  include ExtensionAPI
103
254
 
255
+ attr_accessor :critical
104
256
  attr_accessor :ocsp
257
+ attr_accessor :ca_issuers
105
258
 
106
259
  def initialize
107
- self.ocsp = []
260
+ @critical = false
261
+ @ocsp = []
262
+ @ca_issuers = []
108
263
  end
109
264
 
110
265
  def openssl_identifier
111
- "authorityInfoAccess"
266
+ OPENSSL_IDENTIFIER
112
267
  end
113
268
 
114
269
  def to_s
115
- return "" if self.ocsp.empty?
116
- "OCSP;URI:#{self.ocsp}"
270
+ res = []
271
+ res += @ocsp.map {|o| "OCSP;URI:#{o}" }
272
+ res += @ca_issuers.map {|c| "caIssuers;URI:#{c}" }
273
+ res.join(',')
274
+ end
275
+
276
+ def ==(o)
277
+ o.class == self.class && o.state == state
278
+ end
279
+
280
+ def self.parse(value, critical)
281
+ obj = self.new
282
+ return obj if value.nil?
283
+ obj.critical = critical
284
+ value.split("\n").each do |v|
285
+ if v =~ /^OCSP/
286
+ obj.ocsp << v.split.last
287
+ end
288
+
289
+ if v =~ /^CA Issuers/
290
+ obj.ca_issuers << v.split.last
291
+ end
292
+ end
293
+ obj
294
+ end
295
+
296
+ protected
297
+ def state
298
+ [@critical,@ocsp,@ca_issuers]
117
299
  end
118
300
  end
119
301
 
302
+ # Specifies the allowed usage purposes of the keypair specified in this certificate.
303
+ # Reference: Section 4.2.1.3 of RFC3280
304
+ # http://tools.ietf.org/html/rfc3280#section-4.2.1.3
305
+ #
306
+ # Note: OpenSSL when parsing an extension will return results in the form
307
+ # 'Digital Signature', but on signing you have to set it to 'digitalSignature'.
308
+ # So copying an extension from an imported cert isn't going to work yet.
120
309
  class KeyUsage
310
+ OPENSSL_IDENTIFIER = "keyUsage"
311
+
121
312
  include ExtensionAPI
122
313
 
314
+ attr_accessor :critical
123
315
  attr_accessor :usage
124
316
 
125
317
  def initialize
126
- self.usage = ["digitalSignature", "nonRepudiation"]
318
+ @critical = false
319
+ @usage = ["digitalSignature", "nonRepudiation"]
127
320
  end
128
321
 
129
322
  def openssl_identifier
130
- "keyUsage"
323
+ OPENSSL_IDENTIFIER
131
324
  end
132
325
 
133
326
  def to_s
134
- "#{self.usage.join(',')}"
327
+ res = []
328
+ res += @usage
329
+ res.join(',')
330
+ end
331
+
332
+ def ==(o)
333
+ o.class == self.class && o.state == state
334
+ end
335
+
336
+ def self.parse(value, critical)
337
+ obj = self.new
338
+ return obj if value.nil?
339
+ obj.critical = critical
340
+ obj.usage = value.split(/,\s*/)
341
+ obj
342
+ end
343
+
344
+ protected
345
+ def state
346
+ [@critical,@usage]
135
347
  end
136
348
  end
137
349
 
350
+ # Specifies even more allowed usages in addition to what is specified in
351
+ # the Key Usage extension.
352
+ # Reference: Section 4.2.1.13 of RFC3280
353
+ # http://tools.ietf.org/html/rfc3280#section-4.2.1.13
138
354
  class ExtendedKeyUsage
355
+ OPENSSL_IDENTIFIER = "extendedKeyUsage"
356
+
139
357
  include ExtensionAPI
140
358
 
359
+ attr_accessor :critical
141
360
  attr_accessor :usage
142
361
 
143
362
  def initialize
144
- self.usage = ["serverAuth","clientAuth"]
363
+ @critical = false
364
+ @usage = ["serverAuth"]
145
365
  end
146
366
 
147
367
  def openssl_identifier
148
- "extendedKeyUsage"
368
+ OPENSSL_IDENTIFIER
149
369
  end
150
370
 
151
371
  def to_s
152
- "#{self.usage.join(',')}"
372
+ res = []
373
+ res += @usage
374
+ res.join(',')
375
+ end
376
+
377
+ def ==(o)
378
+ o.class == self.class && o.state == state
379
+ end
380
+
381
+ def self.parse(value, critical)
382
+ obj = self.new
383
+ return obj if value.nil?
384
+ obj.critical = critical
385
+ obj.usage = value.split(/,\s*/)
386
+ obj
387
+ end
388
+
389
+ protected
390
+ def state
391
+ [@critical,@usage]
153
392
  end
154
393
  end
155
394
 
395
+ # Specifies additional "names" for which this certificate is valid.
396
+ # Reference: Section 4.2.1.7 of RFC3280
397
+ # http://tools.ietf.org/html/rfc3280#section-4.2.1.7
156
398
  class SubjectAlternativeName
399
+ OPENSSL_IDENTIFIER = "subjectAltName"
400
+
157
401
  include ExtensionAPI
158
402
 
159
- attr_accessor :uris, :dns_names, :ips
403
+ attr_accessor :critical
404
+ attr_accessor :uris, :dns_names, :ips, :emails
160
405
 
161
406
  def initialize
162
- self.uris = []
163
- self.dns_names = []
164
- self.ips = []
407
+ @critical = false
408
+ @uris = []
409
+ @dns_names = []
410
+ @ips = []
411
+ @emails = []
412
+ end
413
+
414
+ def openssl_identifier
415
+ OPENSSL_IDENTIFIER
165
416
  end
166
417
 
167
418
  def uris=(value)
@@ -179,22 +430,50 @@ module CertificateAuthority
179
430
  @ips = value
180
431
  end
181
432
 
182
- def openssl_identifier
183
- "subjectAltName"
433
+ def emails=(value)
434
+ raise "Emails must be an array" unless value.is_a?(Array)
435
+ @emails = value
184
436
  end
185
437
 
186
438
  def to_s
187
- res = self.uris.map {|u| "URI:#{u}" }
188
- res += self.dns_names.map {|d| "DNS:#{d}" }
189
- res += self.ips.map {|i| "IP:#{i}" }
439
+ res = []
440
+ res += @uris.map {|u| "URI:#{u}" }
441
+ res += @dns_names.map {|d| "DNS:#{d}" }
442
+ res += @ips.map {|i| "IP:#{i}" }
443
+ res += @emails.map {|i| "email:#{i}" }
444
+ res.join(',')
445
+ end
446
+
447
+ def ==(o)
448
+ o.class == self.class && o.state == state
449
+ end
450
+
451
+ def self.parse(value, critical)
452
+ obj = self.new
453
+ return obj if value.nil?
454
+ obj.critical = critical
455
+ value.split(/,\s*/).each do |v|
456
+ c = v.split(':', 2)
457
+ obj.uris << c.last if c.first == "URI"
458
+ obj.dns_names << c.last if c.first == "DNS"
459
+ obj.ips << c.last if c.first == "IP"
460
+ obj.emails << c.last if c.first == "EMAIL"
461
+ end
462
+ obj
463
+ end
190
464
 
191
- return res.join(',')
465
+ protected
466
+ def state
467
+ [@critical,@uris,@dns_names,@ips,@emails]
192
468
  end
193
469
  end
194
470
 
195
471
  class CertificatePolicies
472
+ OPENSSL_IDENTIFIER = "certificatePolicies"
473
+
196
474
  include ExtensionAPI
197
475
 
476
+ attr_accessor :critical
198
477
  attr_accessor :policy_identifier
199
478
  attr_accessor :cps_uris
200
479
  ##User notice
@@ -203,12 +482,12 @@ module CertificateAuthority
203
482
  attr_accessor :notice_numbers
204
483
 
205
484
  def initialize
485
+ self.critical = false
206
486
  @contains_data = false
207
487
  end
208
488
 
209
-
210
489
  def openssl_identifier
211
- "certificatePolicies"
490
+ OPENSSL_IDENTIFIER
212
491
  end
213
492
 
214
493
  def user_notice=(value={})
@@ -258,7 +537,93 @@ module CertificateAuthority
258
537
 
259
538
  def to_s
260
539
  return "" unless @contains_data
261
- "ia5org,@custom_policies"
540
+ res = []
541
+ res << "ia5org"
542
+ res += @config_extensions["custom_policies"] unless @config_extensions.nil?
543
+ res.join(',')
544
+ end
545
+
546
+ def self.parse(value, critical)
547
+ obj = self.new
548
+ return obj if value.nil?
549
+ obj.critical = critical
550
+ value.split(/,\s*/).each do |v|
551
+ c = v.split(':', 2)
552
+ obj.policy_identifier = c.last if c.first == "policyIdentifier"
553
+ obj.cps_uris << c.last if c.first =~ %r{CPS.\d+}
554
+ # TODO: explicit_text, organization, notice_numbers
555
+ end
556
+ obj
557
+ end
558
+ end
559
+
560
+ # DEPRECATED
561
+ # Specifics the purposes for which a certificate can be used.
562
+ # The basicConstraints, keyUsage, and extendedKeyUsage extensions are now used instead.
563
+ # https://www.openssl.org/docs/apps/x509v3_config.html#Netscape_Certificate_Type
564
+ class NetscapeCertificateType
565
+ OPENSSL_IDENTIFIER = "nsCertType"
566
+
567
+ include ExtensionAPI
568
+
569
+ attr_accessor :critical
570
+ attr_accessor :flags
571
+
572
+ def initialize
573
+ self.critical = false
574
+ self.flags = []
575
+ end
576
+
577
+ def openssl_identifier
578
+ OPENSSL_IDENTIFIER
579
+ end
580
+
581
+ def to_s
582
+ res = []
583
+ res += self.flags
584
+ res.join(',')
585
+ end
586
+
587
+ def self.parse(value, critical)
588
+ obj = self.new
589
+ return obj if value.nil?
590
+ obj.critical = critical
591
+ obj.flags = value.split(/,\s*/)
592
+ obj
593
+ end
594
+ end
595
+
596
+ # DEPRECATED
597
+ # Contains a comment which will be displayed when the certificate is viewed in some browsers.
598
+ # https://www.openssl.org/docs/apps/x509v3_config.html#Netscape_String_extensions_
599
+ class NetscapeComment
600
+ OPENSSL_IDENTIFIER = "nsComment"
601
+
602
+ include ExtensionAPI
603
+
604
+ attr_accessor :critical
605
+ attr_accessor :comment
606
+
607
+ def initialize
608
+ self.critical = false
609
+ end
610
+
611
+ def openssl_identifier
612
+ OPENSSL_IDENTIFIER
613
+ end
614
+
615
+ def to_s
616
+ res = []
617
+ res << self.comment if self.comment
618
+ res.join(',')
619
+ end
620
+
621
+ def self.parse(value, critical)
622
+ obj = self.new
623
+ return obj if value.nil?
624
+ obj.critical = critical
625
+ obj.comment = value
626
+ obj
262
627
  end
263
628
  end
264
629
 
@@ -111,38 +111,4 @@ module CertificateAuthority
111
111
  @public_key
112
112
  end
113
113
  end
114
-
115
- class SigningRequestKeyMaterial
116
- include KeyMaterial
117
- include ActiveModel::Validations
118
-
119
- validates_each :public_key do |record, attr, value|
120
- record.errors.add :public_key, "cannot be blank" if record.public_key.nil?
121
- end
122
-
123
- attr_accessor :public_key
124
-
125
- def initialize(request=nil)
126
- if request.is_a? OpenSSL::X509::Request
127
- raise "Invalid certificate signing request" unless request.verify request.public_key
128
- self.public_key = request.public_key
129
- end
130
- end
131
-
132
- def is_in_hardware?
133
- false
134
- end
135
-
136
- def is_in_memory?
137
- true
138
- end
139
-
140
- def private_key
141
- nil
142
- end
143
-
144
- def public_key
145
- @public_key
146
- end
147
- end
148
114
  end
@@ -1,3 +1,5 @@
1
+ require 'securerandom'
2
+
1
3
  module CertificateAuthority
2
4
  class SerialNumber
3
5
  include ActiveModel::Validations
@@ -6,5 +8,9 @@ module CertificateAuthority
6
8
  attr_accessor :number
7
9
 
8
10
  validates :number, :presence => true, :numericality => {:greater_than => 0}
11
+
12
+ def initialize
13
+ self.number = SecureRandom.random_number(2**128-1)
14
+ end
9
15
  end
10
16
  end