leap_cli 1.5.6 → 1.6.2

Sign up to get free protection for your applications and to get access to all the features.
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