leap_cli 1.7.4 → 1.8

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 (43) hide show
  1. checksums.yaml +4 -4
  2. data/bin/leap +6 -13
  3. data/lib/leap/platform.rb +2 -0
  4. data/lib/leap_cli.rb +2 -1
  5. data/lib/leap_cli/bootstrap.rb +197 -0
  6. data/lib/leap_cli/commands/common.rb +61 -0
  7. data/lib/leap_cli/commands/new.rb +5 -1
  8. data/lib/leap_cli/commands/pre.rb +1 -66
  9. data/lib/leap_cli/config/environment.rb +180 -0
  10. data/lib/leap_cli/config/manager.rb +100 -197
  11. data/lib/leap_cli/config/node.rb +2 -2
  12. data/lib/leap_cli/config/object.rb +56 -43
  13. data/lib/leap_cli/config/object_list.rb +6 -3
  14. data/lib/leap_cli/config/provider.rb +11 -0
  15. data/lib/leap_cli/config/secrets.rb +14 -1
  16. data/lib/leap_cli/config/tag.rb +2 -2
  17. data/lib/leap_cli/leapfile.rb +1 -0
  18. data/lib/leap_cli/log.rb +1 -0
  19. data/lib/leap_cli/logger.rb +16 -12
  20. data/lib/leap_cli/markdown_document_listener.rb +3 -1
  21. data/lib/leap_cli/path.rb +12 -0
  22. data/lib/leap_cli/remote/leap_plugin.rb +9 -34
  23. data/lib/leap_cli/remote/puppet_plugin.rb +0 -40
  24. data/lib/leap_cli/remote/tasks.rb +9 -34
  25. data/lib/leap_cli/ssh_key.rb +5 -2
  26. data/lib/leap_cli/version.rb +2 -2
  27. metadata +5 -18
  28. data/lib/leap_cli/commands/ca.rb +0 -518
  29. data/lib/leap_cli/commands/clean.rb +0 -16
  30. data/lib/leap_cli/commands/compile.rb +0 -340
  31. data/lib/leap_cli/commands/db.rb +0 -65
  32. data/lib/leap_cli/commands/deploy.rb +0 -368
  33. data/lib/leap_cli/commands/env.rb +0 -76
  34. data/lib/leap_cli/commands/facts.rb +0 -100
  35. data/lib/leap_cli/commands/inspect.rb +0 -144
  36. data/lib/leap_cli/commands/list.rb +0 -132
  37. data/lib/leap_cli/commands/node.rb +0 -165
  38. data/lib/leap_cli/commands/node_init.rb +0 -169
  39. data/lib/leap_cli/commands/ssh.rb +0 -220
  40. data/lib/leap_cli/commands/test.rb +0 -74
  41. data/lib/leap_cli/commands/user.rb +0 -136
  42. data/lib/leap_cli/commands/util.rb +0 -50
  43. data/lib/leap_cli/commands/vagrant.rb +0 -197
@@ -25,44 +25,19 @@ end
25
25
  task :install_insecure_vagrant_key, :max_hosts => MAX_HOSTS do
26
26
  leap.log :installing, "insecure vagrant key" do
27
27
  leap.mkdirs '/root/.ssh'
28
- key_file = File.expand_path('../../../vendor/vagrant_ssh_keys/vagrant.pub', File.dirname(__FILE__))
29
- upload key_file, '/root/.ssh/authorized_keys2', :mode => '600'
28
+ upload LeapCli::Path.vagrant_ssh_pub_key_file, '/root/.ssh/authorized_keys2', :mode => '600'
30
29
  end
31
30
  end
32
31
 
33
- BAD_APT_GET_UPDATE = /(BADSIG|NO_PUBKEY|KEYEXPIRED|REVKEYSIG|NODATA)/
34
-
35
32
  task :install_prerequisites, :max_hosts => MAX_HOSTS do
36
- apt_get = "DEBIAN_FRONTEND=noninteractive apt-get -q -y -o DPkg::Options::=--force-confold"
37
- apt_get_update = "apt-get update -o Acquire::Languages=none"
38
- leap.mkdirs Leap::Platform.leap_dir
39
- run "echo 'en_US.UTF-8 UTF-8' > /etc/locale.gen"
40
- leap.log :updating, "package list" do
41
- run apt_get_update do |channel, stream, data|
42
- # sadly exitcode is unreliable measure if apt-get update hit a failure.
43
- if data =~ BAD_APT_GET_UPDATE
44
- LeapCli::Util.bail! do
45
- LeapCli::Util.log :fatal_error, "in `apt-get update`: #{data}", :host => channel[:host]
46
- end
47
- else
48
- logger.log(1, data, channel[:host])
49
- end
50
- end
51
- end
52
- leap.log :updating, "server time" do
53
- run "( test -f /etc/init.d/ntp && /etc/init.d/ntp stop ) || true"
54
- run "test -f /usr/sbin/ntpdate || #{apt_get} install ntpdate"
55
- leap.log :running, "ntpdate..." do
56
- run "test -f /usr/sbin/ntpdate && ntpdate 0.debian.pool.ntp.org 1.debian.pool.ntp.org 2.debian.pool.ntp.org 3.debian.pool.ntp.org"
57
- end
58
- run "( test -f /etc/init.d/ntp && /etc/init.d/ntp start ) || true"
59
- end
60
- leap.log :installing, "required packages" do
61
- run %[#{apt_get} install #{leap.required_wheezy_packages}]
33
+ bin_dir = File.join(Leap::Platform.leap_dir, 'bin')
34
+ node_init_path = File.join(bin_dir, 'node_init')
35
+
36
+ leap.log :running, "node_init script" do
37
+ leap.mkdirs bin_dir
38
+ upload LeapCli::Path.node_init_script, node_init_path, :mode => '500'
39
+ run node_init_path
62
40
  end
63
- #run "locale-gen"
64
- leap.mkdirs("/etc/leap", "/srv/leap")
65
- leap.mark_initialized
66
41
  end
67
42
 
68
43
  #
@@ -73,4 +48,4 @@ task :skip_errors_task, :on_error => :continue, :max_hosts => MAX_HOSTS do
73
48
  end
74
49
 
75
50
  task :standard_task, :max_hosts => MAX_HOSTS do
76
- end
51
+ end
@@ -161,8 +161,11 @@ module LeapCli
161
161
  end
162
162
 
163
163
  def summary
164
- #"%s %s %s (%s)" % [self.type, self.bits, self.fingerprint, self.filename || self.comment || '']
165
- "%s %s %s" % [self.type, self.bits, self.fingerprint]
164
+ if self.filename
165
+ "%s %s %s (%s)" % [self.type, self.bits, self.fingerprint, File.basename(self.filename)]
166
+ else
167
+ "%s %s %s" % [self.type, self.bits, self.fingerprint]
168
+ end
166
169
  end
167
170
 
168
171
  def to_s
@@ -1,7 +1,7 @@
1
1
  module LeapCli
2
2
  unless defined?(LeapCli::VERSION)
3
- VERSION = '1.7.4'
4
- COMPATIBLE_PLATFORM_VERSION = '0.7.1'..'0.99'
3
+ VERSION = '1.8'
4
+ COMPATIBLE_PLATFORM_VERSION = '0.8'..'0.99'
5
5
  SUMMARY = 'Command line interface to the LEAP platform'
6
6
  DESCRIPTION = 'The command "leap" can be used to manage a bevy of servers running the LEAP platform from the comfort of your own home.'
7
7
  LOAD_PATHS = ['lib', 'vendor/certificate_authority/lib', 'vendor/rsync_command/lib']
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: leap_cli
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.7.4
4
+ version: '1.8'
5
5
  platform: ruby
6
6
  authors:
7
7
  - LEAP Encryption Access Project
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-07-29 00:00:00.000000000 Z
11
+ date: 2016-05-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: minitest
@@ -187,24 +187,11 @@ files:
187
187
  - bin/leap
188
188
  - lib/leap/platform.rb
189
189
  - lib/leap_cli.rb
190
- - lib/leap_cli/commands/ca.rb
191
- - lib/leap_cli/commands/clean.rb
192
- - lib/leap_cli/commands/compile.rb
193
- - lib/leap_cli/commands/db.rb
194
- - lib/leap_cli/commands/deploy.rb
195
- - lib/leap_cli/commands/env.rb
196
- - lib/leap_cli/commands/facts.rb
197
- - lib/leap_cli/commands/inspect.rb
198
- - lib/leap_cli/commands/list.rb
190
+ - lib/leap_cli/bootstrap.rb
191
+ - lib/leap_cli/commands/common.rb
199
192
  - lib/leap_cli/commands/new.rb
200
- - lib/leap_cli/commands/node.rb
201
- - lib/leap_cli/commands/node_init.rb
202
193
  - lib/leap_cli/commands/pre.rb
203
- - lib/leap_cli/commands/ssh.rb
204
- - lib/leap_cli/commands/test.rb
205
- - lib/leap_cli/commands/user.rb
206
- - lib/leap_cli/commands/util.rb
207
- - lib/leap_cli/commands/vagrant.rb
194
+ - lib/leap_cli/config/environment.rb
208
195
  - lib/leap_cli/config/filter.rb
209
196
  - lib/leap_cli/config/manager.rb
210
197
  - lib/leap_cli/config/node.rb
@@ -1,518 +0,0 @@
1
- autoload :OpenSSL, 'openssl'
2
- autoload :CertificateAuthority, 'certificate_authority'
3
- autoload :Date, 'date'
4
- require 'digest/md5'
5
-
6
- module LeapCli; module Commands
7
-
8
- desc "Manage X.509 certificates"
9
- command :cert do |cert|
10
-
11
- cert.desc 'Creates two Certificate Authorities (one for validating servers and one for validating clients).'
12
- cert.long_desc 'See see what values are used in the generation of the certificates (like name and key size), run `leap inspect provider` and look for the "ca" property. To see the details of the created certs, run `leap inspect <file>`.'
13
- cert.command :ca do |ca|
14
- ca.action do |global_options,options,args|
15
- assert_config! 'provider.ca.name'
16
- generate_new_certificate_authority(:ca_key, :ca_cert, provider.ca.name)
17
- generate_new_certificate_authority(:client_ca_key, :client_ca_cert, provider.ca.name + ' (client certificates only!)')
18
- end
19
- end
20
-
21
- cert.desc 'Creates or renews a X.509 certificate/key pair for a single node or all nodes, but only if needed.'
22
- cert.long_desc 'This command will a generate new certificate for a node if some value in the node has changed ' +
23
- 'that is included in the certificate (like hostname or IP address), or if the old certificate will be expiring soon. ' +
24
- 'Sometimes, you might want to force the generation of a new certificate, ' +
25
- 'such as in the cases where you have changed a CA parameter for server certificates, like bit size or digest hash. ' +
26
- 'In this case, use --force. If <node-filter> is empty, this command will apply to all nodes.'
27
- cert.arg_name 'FILTER'
28
- cert.command :update do |update|
29
- update.switch 'force', :desc => 'Always generate new certificates', :negatable => false
30
- update.action do |global_options,options,args|
31
- update_certificates(manager.filter!(args), options)
32
- end
33
- end
34
-
35
- cert.desc 'Creates a Diffie-Hellman parameter file, needed for forward secret OpenVPN ciphers.' # (needed for server-side of some TLS connections)
36
- cert.command :dh do |dh|
37
- dh.action do |global_options,options,args|
38
- long_running do
39
- if cmd_exists?('certtool')
40
- log 0, 'Generating DH parameters (takes a long time)...'
41
- output = assert_run!('certtool --generate-dh-params --sec-param high')
42
- output.sub! /.*(-----BEGIN DH PARAMETERS-----.*-----END DH PARAMETERS-----).*/m, '\1'
43
- output << "\n"
44
- write_file!(:dh_params, output)
45
- else
46
- log 0, 'Generating DH parameters (takes a REALLY long time)...'
47
- output = OpenSSL::PKey::DH.generate(3248).to_pem
48
- write_file!(:dh_params, output)
49
- end
50
- end
51
- end
52
- end
53
-
54
- #
55
- # hints:
56
- #
57
- # inspect CSR:
58
- # openssl req -noout -text -in files/cert/x.csr
59
- #
60
- # generate CSR with openssl to see how it compares:
61
- # openssl req -sha256 -nodes -newkey rsa:2048 -keyout example.key -out example.csr
62
- #
63
- # validate a CSR:
64
- # http://certlogik.com/decoder/
65
- #
66
- # nice details about CSRs:
67
- # http://www.redkestrel.co.uk/Articles/CSR.html
68
- #
69
- cert.desc "Creates a CSR for use in buying a commercial X.509 certificate."
70
- cert.long_desc "Unless specified, the CSR is created for the provider's primary domain. "+
71
- "The properties used for this CSR come from `provider.ca.server_certificates`, "+
72
- "but may be overridden here."
73
- cert.command :csr do |csr|
74
- csr.flag 'domain', :arg_name => 'DOMAIN', :desc => 'Specify what domain to create the CSR for.'
75
- csr.flag ['organization', 'O'], :arg_name => 'ORGANIZATION', :desc => "Override default O in distinguished name."
76
- csr.flag ['unit', 'OU'], :arg_name => 'UNIT', :desc => "Set OU in distinguished name."
77
- csr.flag 'email', :arg_name => 'EMAIL', :desc => "Set emailAddress in distinguished name."
78
- csr.flag ['locality', 'L'], :arg_name => 'LOCALITY', :desc => "Set L in distinguished name."
79
- csr.flag ['state', 'ST'], :arg_name => 'STATE', :desc => "Set ST in distinguished name."
80
- csr.flag ['country', 'C'], :arg_name => 'COUNTRY', :desc => "Set C in distinguished name."
81
- csr.flag :bits, :arg_name => 'BITS', :desc => "Override default certificate bit length"
82
- csr.flag :digest, :arg_name => 'DIGEST', :desc => "Override default signature digest"
83
- csr.action do |global_options,options,args|
84
- assert_config! 'provider.domain'
85
- assert_config! 'provider.name'
86
- assert_config! 'provider.default_language'
87
- assert_config! 'provider.ca.server_certificates.bit_size'
88
- assert_config! 'provider.ca.server_certificates.digest'
89
- domain = options[:domain] || provider.domain
90
-
91
- unless global_options[:force]
92
- assert_files_missing! [:commercial_key, domain], [:commercial_csr, domain],
93
- :msg => 'If you really want to create a new key and CSR, remove these files first or run with --force.'
94
- end
95
-
96
- server_certificates = provider.ca.server_certificates
97
-
98
- # RSA key
99
- keypair = CertificateAuthority::MemoryKeyMaterial.new
100
- bit_size = (options[:bits] || server_certificates.bit_size).to_i
101
- log :generating, "%s bit RSA key" % bit_size do
102
- keypair.generate_key(bit_size)
103
- write_file! [:commercial_key, domain], keypair.private_key.to_pem
104
- end
105
-
106
- # CSR
107
- dn = CertificateAuthority::DistinguishedName.new
108
- dn.common_name = domain
109
- dn.organization = options[:organization] || provider.name[provider.default_language]
110
- dn.ou = options[:organizational_unit] # optional
111
- dn.email_address = options[:email] # optional
112
- dn.country = options[:country] || server_certificates['country'] # optional
113
- dn.state = options[:state] || server_certificates['state'] # optional
114
- dn.locality = options[:locality] || server_certificates['locality'] # optional
115
-
116
- digest = options[:digest] || server_certificates.digest
117
- log :generating, "CSR with #{digest} digest and #{print_dn(dn)}" do
118
- csr = create_csr(dn, keypair, digest)
119
- request = csr.to_x509_csr
120
- write_file! [:commercial_csr, domain], csr.to_pem
121
- end
122
-
123
- # Sign using our own CA, for use in testing but hopefully not production.
124
- # It is not that commerical CAs are so secure, it is just that signing your own certs is
125
- # a total drag for the user because they must click through dire warnings.
126
- #if options[:sign]
127
- log :generating, "self-signed x509 server certificate for testing purposes" do
128
- cert = csr.to_cert
129
- cert.serial_number.number = cert_serial_number(domain)
130
- cert.not_before = yesterday
131
- cert.not_after = yesterday.advance(:years => 1)
132
- cert.parent = ca_root
133
- cert.sign! domain_test_signing_profile
134
- write_file! [:commercial_cert, domain], cert.to_pem
135
- log "please replace this file with the real certificate you get from a CA using #{Path.relative_path([:commercial_csr, domain])}"
136
- end
137
- #end
138
-
139
- # FAKE CA
140
- unless file_exists? :commercial_ca_cert
141
- log :using, "generated CA in place of commercial CA for testing purposes" do
142
- write_file! :commercial_ca_cert, read_file!(:ca_cert)
143
- log "please also replace this file with the CA cert from the commercial authority you use."
144
- end
145
- end
146
- end
147
- end
148
- end
149
-
150
- protected
151
-
152
- #
153
- # will generate new certificates for the specified nodes, if needed.
154
- #
155
- def update_certificates(nodes, options={})
156
- assert_files_exist! :ca_cert, :ca_key, :msg => 'Run `leap cert ca` to create them'
157
- assert_config! 'provider.ca.server_certificates.bit_size'
158
- assert_config! 'provider.ca.server_certificates.digest'
159
- assert_config! 'provider.ca.server_certificates.life_span'
160
- assert_config! 'common.x509.use'
161
-
162
- nodes.each_node do |node|
163
- warn_if_commercial_cert_will_soon_expire(node)
164
- if !node.x509.use
165
- remove_file!([:node_x509_key, node.name])
166
- remove_file!([:node_x509_cert, node.name])
167
- elsif options[:force] || cert_needs_updating?(node)
168
- generate_cert_for_node(node)
169
- end
170
- end
171
- end
172
-
173
- private
174
-
175
- def generate_new_certificate_authority(key_file, cert_file, common_name)
176
- assert_files_missing! key_file, cert_file
177
- assert_config! 'provider.ca.name'
178
- assert_config! 'provider.ca.bit_size'
179
- assert_config! 'provider.ca.life_span'
180
-
181
- root = CertificateAuthority::Certificate.new
182
-
183
- # set subject
184
- root.subject.common_name = common_name
185
- possible = ['country', 'state', 'locality', 'organization', 'organizational_unit', 'email_address']
186
- provider.ca.keys.each do |key|
187
- if possible.include?(key)
188
- root.subject.send(key + '=', provider.ca[key])
189
- end
190
- end
191
-
192
- # set expiration
193
- root.not_before = yesterday
194
- root.not_after = yesterday_advance(provider.ca.life_span)
195
-
196
- # generate private key
197
- root.serial_number.number = 1
198
- root.key_material.generate_key(provider.ca.bit_size)
199
-
200
- # sign self
201
- root.signing_entity = true
202
- root.parent = root
203
- root.sign!(ca_root_signing_profile)
204
-
205
- # save
206
- write_file!(key_file, root.key_material.private_key.to_pem)
207
- write_file!(cert_file, root.to_pem)
208
- end
209
-
210
- #
211
- # returns true if the certs associated with +node+ need to be regenerated.
212
- #
213
- def cert_needs_updating?(node)
214
- if !file_exists?([:node_x509_cert, node.name], [:node_x509_key, node.name])
215
- return true
216
- else
217
- cert = load_certificate_file([:node_x509_cert, node.name])
218
- if cert.not_after < Time.now.advance(:months => 2)
219
- log :updating, "cert for node '#{node.name}' because it will expire soon"
220
- return true
221
- end
222
- if cert.subject.common_name != node.domain.full
223
- log :updating, "cert for node '#{node.name}' because domain.full has changed (was #{cert.subject.common_name}, now #{node.domain.full})"
224
- return true
225
- end
226
- cert.openssl_body.extensions.each do |ext|
227
- if ext.oid == "subjectAltName"
228
- ips = []
229
- dns_names = []
230
- ext.value.split(",").each do |value|
231
- value.strip!
232
- ips << $1 if value =~ /^IP Address:(.*)$/
233
- dns_names << $1 if value =~ /^DNS:(.*)$/
234
- end
235
- dns_names.sort!
236
- if ips.first != node.ip_address
237
- log :updating, "cert for node '#{node.name}' because ip_address has changed (from #{ips.first} to #{node.ip_address})"
238
- return true
239
- elsif dns_names != dns_names_for_node(node)
240
- log :updating, "cert for node '#{node.name}' because domain name aliases have changed\n from: #{dns_names.inspect}\n to: #{dns_names_for_node(node).inspect})"
241
- return true
242
- end
243
- end
244
- end
245
- end
246
- return false
247
- end
248
-
249
- def warn_if_commercial_cert_will_soon_expire(node)
250
- dns_names_for_node(node).each do |domain|
251
- if file_exists?([:commercial_cert, domain])
252
- cert = load_certificate_file([:commercial_cert, domain])
253
- path = Path.relative_path([:commercial_cert, domain])
254
- if cert.not_after < Time.now.utc
255
- log :error, "the commercial certificate '#{path}' has EXPIRED! " +
256
- "You should renew it with `leap cert csr --domain #{domain}`."
257
- elsif cert.not_after < Time.now.advance(:months => 2)
258
- log :warning, "the commercial certificate '#{path}' will expire soon. "+
259
- "You should renew it with `leap cert csr --domain #{domain}`."
260
- end
261
- end
262
- end
263
- end
264
-
265
- def generate_cert_for_node(node)
266
- return if node.x509.use == false
267
-
268
- cert = CertificateAuthority::Certificate.new
269
-
270
- # set subject
271
- cert.subject.common_name = node.domain.full
272
- cert.serial_number.number = cert_serial_number(node.domain.full)
273
-
274
- # set expiration
275
- cert.not_before = yesterday
276
- cert.not_after = yesterday_advance(provider.ca.server_certificates.life_span)
277
-
278
- # generate key
279
- cert.key_material.generate_key(provider.ca.server_certificates.bit_size)
280
-
281
- # sign
282
- cert.parent = ca_root
283
- cert.sign!(server_signing_profile(node))
284
-
285
- # save
286
- write_file!([:node_x509_key, node.name], cert.key_material.private_key.to_pem)
287
- write_file!([:node_x509_cert, node.name], cert.to_pem)
288
- end
289
-
290
- #
291
- # yields client key and cert suitable for testing
292
- #
293
- def generate_test_client_cert(prefix=nil)
294
- cert = CertificateAuthority::Certificate.new
295
- cert.serial_number.number = cert_serial_number(provider.domain)
296
- cert.subject.common_name = [prefix, random_common_name(provider.domain)].join
297
- cert.not_before = yesterday
298
- cert.not_after = yesterday.advance(:years => 1)
299
- cert.key_material.generate_key(1024) # just for testing, remember!
300
- cert.parent = client_ca_root
301
- cert.sign! client_test_signing_profile
302
- yield cert.key_material.private_key.to_pem, cert.to_pem
303
- end
304
-
305
- #
306
- # creates a CSR and returns it.
307
- # with the correct extReq attribute so that the CA
308
- # doens't generate certs with extensions we don't want.
309
- #
310
- def create_csr(dn, keypair, digest)
311
- csr = CertificateAuthority::SigningRequest.new
312
- csr.distinguished_name = dn
313
- csr.key_material = keypair
314
- csr.digest = digest
315
-
316
- # define extensions manually (library doesn't support setting these on CSRs)
317
- extensions = []
318
- extensions << CertificateAuthority::Extensions::BasicConstraints.new.tap {|basic|
319
- basic.ca = false
320
- }
321
- extensions << CertificateAuthority::Extensions::KeyUsage.new.tap {|keyusage|
322
- keyusage.usage = ["digitalSignature", "keyEncipherment"]
323
- }
324
- extensions << CertificateAuthority::Extensions::ExtendedKeyUsage.new.tap {|extkeyusage|
325
- extkeyusage.usage = [ "serverAuth"]
326
- }
327
-
328
- # convert extensions to attribute 'extReq'
329
- # aka "Requested Extensions"
330
- factory = OpenSSL::X509::ExtensionFactory.new
331
- attrval = OpenSSL::ASN1::Set([OpenSSL::ASN1::Sequence(
332
- extensions.map{|e| factory.create_ext(e.openssl_identifier, e.to_s, e.critical)}
333
- )])
334
- attrs = [
335
- OpenSSL::X509::Attribute.new("extReq", attrval),
336
- ]
337
- csr.attributes = attrs
338
-
339
- return csr
340
- end
341
-
342
- def ca_root
343
- @ca_root ||= begin
344
- load_certificate_file(:ca_cert, :ca_key)
345
- end
346
- end
347
-
348
- def client_ca_root
349
- @client_ca_root ||= begin
350
- load_certificate_file(:client_ca_cert, :client_ca_key)
351
- end
352
- end
353
-
354
- def load_certificate_file(crt_file, key_file=nil, password=nil)
355
- crt = read_file!(crt_file)
356
- openssl_cert = OpenSSL::X509::Certificate.new(crt)
357
- cert = CertificateAuthority::Certificate.from_openssl(openssl_cert)
358
- if key_file
359
- key = read_file!(key_file)
360
- cert.key_material.private_key = OpenSSL::PKey::RSA.new(key, password)
361
- end
362
- return cert
363
- end
364
-
365
- def ca_root_signing_profile
366
- {
367
- "extensions" => {
368
- "basicConstraints" => {"ca" => true},
369
- "keyUsage" => {
370
- "usage" => ["critical", "keyCertSign"]
371
- },
372
- "extendedKeyUsage" => {
373
- "usage" => []
374
- }
375
- }
376
- }
377
- end
378
-
379
- #
380
- # For keyusage, openvpn server certs can have keyEncipherment or keyAgreement.
381
- # Web browsers seem to break without keyEncipherment.
382
- # For now, I am using digitalSignature + keyEncipherment
383
- #
384
- # * digitalSignature -- for (EC)DHE cipher suites
385
- # "The digitalSignature bit is asserted when the subject public key is used
386
- # with a digital signature mechanism to support security services other
387
- # than certificate signing (bit 5), or CRL signing (bit 6). Digital
388
- # signature mechanisms are often used for entity authentication and data
389
- # origin authentication with integrity."
390
- #
391
- # * keyEncipherment ==> for plain RSA cipher suites
392
- # "The keyEncipherment bit is asserted when the subject public key is used for
393
- # key transport. For example, when an RSA key is to be used for key management,
394
- # then this bit is set."
395
- #
396
- # * keyAgreement ==> for used with DH, not RSA.
397
- # "The keyAgreement bit is asserted when the subject public key is used for key
398
- # agreement. For example, when a Diffie-Hellman key is to be used for key
399
- # management, then this bit is set."
400
- #
401
- # digest options: SHA512, SHA256, SHA1
402
- #
403
- def server_signing_profile(node)
404
- {
405
- "digest" => provider.ca.server_certificates.digest,
406
- "extensions" => {
407
- "keyUsage" => {
408
- "usage" => ["digitalSignature", "keyEncipherment"]
409
- },
410
- "extendedKeyUsage" => {
411
- "usage" => ["serverAuth", "clientAuth"]
412
- },
413
- "subjectAltName" => {
414
- "ips" => [node.ip_address],
415
- "dns_names" => dns_names_for_node(node)
416
- }
417
- }
418
- }
419
- end
420
-
421
- #
422
- # This is used when signing the main cert for the provider's domain
423
- # with our own CA (for testing purposes). Typically, this cert would
424
- # be purchased from a commercial CA, and not signed this way.
425
- #
426
- def domain_test_signing_profile
427
- {
428
- "digest" => "SHA256",
429
- "extensions" => {
430
- "keyUsage" => {
431
- "usage" => ["digitalSignature", "keyEncipherment"]
432
- },
433
- "extendedKeyUsage" => {
434
- "usage" => ["serverAuth"]
435
- }
436
- }
437
- }
438
- end
439
-
440
- #
441
- # This is used when signing a dummy client certificate that is only to be
442
- # used for testing.
443
- #
444
- def client_test_signing_profile
445
- {
446
- "digest" => "SHA256",
447
- "extensions" => {
448
- "keyUsage" => {
449
- "usage" => ["digitalSignature"]
450
- },
451
- "extendedKeyUsage" => {
452
- "usage" => ["clientAuth"]
453
- }
454
- }
455
- }
456
- end
457
-
458
- def dns_names_for_node(node)
459
- names = [node.domain.internal, node.domain.full]
460
- if node['dns'] && node.dns['aliases'] && node.dns.aliases.any?
461
- names += node.dns.aliases
462
- end
463
- names.compact!
464
- names.sort!
465
- names.uniq!
466
- return names
467
- end
468
-
469
- #
470
- # For cert serial numbers, we need a non-colliding number less than 160 bits.
471
- # md5 will do nicely, since there is no need for a secure hash, just a short one.
472
- # (md5 is 128 bits)
473
- #
474
- def cert_serial_number(domain_name)
475
- Digest::MD5.hexdigest("#{domain_name} -- #{Time.now}").to_i(16)
476
- end
477
-
478
- #
479
- # for the random common name, we need a text string that will be unique across all certs.
480
- # ruby 1.8 doesn't have a built-in uuid generator, or we would use SecureRandom.uuid
481
- #
482
- def random_common_name(domain_name)
483
- cert_serial_number(domain_name).to_s(36)
484
- end
485
-
486
- # prints CertificateAuthority::DistinguishedName fields
487
- def print_dn(dn)
488
- fields = {}
489
- [:common_name, :locality, :state, :country, :organization, :organizational_unit, :email_address].each do |attr|
490
- fields[attr] = dn.send(attr) if dn.send(attr)
491
- end
492
- fields.inspect
493
- end
494
-
495
- ##
496
- ## TIME HELPERS
497
- ##
498
- ## note: we use 'yesterday' instead of 'today', because times are in UTC, and some people on the planet
499
- ## are behind UTC.
500
- ##
501
-
502
- def yesterday
503
- t = Time.now - 24*24*60
504
- Time.utc t.year, t.month, t.day
505
- end
506
-
507
- def yesterday_advance(string)
508
- number, unit = string.split(' ')
509
- unless ['years', 'months', 'days', 'hours', 'minutes'].include? unit
510
- bail!("The time property '#{string}' is missing a unit (one of: years, months, days, hours, minutes).")
511
- end
512
- unless number.to_i.to_s == number
513
- bail!("The time property '#{string}' is missing a number.")
514
- end
515
- yesterday.advance(unit.to_sym => number.to_i)
516
- end
517
-
518
- end; end