leap_cli 1.7.4 → 1.8

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