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.
- checksums.yaml +4 -4
- data/bin/leap +6 -13
- data/lib/leap/platform.rb +2 -0
- data/lib/leap_cli.rb +2 -1
- data/lib/leap_cli/bootstrap.rb +197 -0
- data/lib/leap_cli/commands/common.rb +61 -0
- data/lib/leap_cli/commands/new.rb +5 -1
- data/lib/leap_cli/commands/pre.rb +1 -66
- data/lib/leap_cli/config/environment.rb +180 -0
- data/lib/leap_cli/config/manager.rb +100 -197
- data/lib/leap_cli/config/node.rb +2 -2
- data/lib/leap_cli/config/object.rb +56 -43
- data/lib/leap_cli/config/object_list.rb +6 -3
- data/lib/leap_cli/config/provider.rb +11 -0
- data/lib/leap_cli/config/secrets.rb +14 -1
- data/lib/leap_cli/config/tag.rb +2 -2
- data/lib/leap_cli/leapfile.rb +1 -0
- data/lib/leap_cli/log.rb +1 -0
- data/lib/leap_cli/logger.rb +16 -12
- data/lib/leap_cli/markdown_document_listener.rb +3 -1
- data/lib/leap_cli/path.rb +12 -0
- data/lib/leap_cli/remote/leap_plugin.rb +9 -34
- data/lib/leap_cli/remote/puppet_plugin.rb +0 -40
- data/lib/leap_cli/remote/tasks.rb +9 -34
- data/lib/leap_cli/ssh_key.rb +5 -2
- data/lib/leap_cli/version.rb +2 -2
- metadata +5 -18
- data/lib/leap_cli/commands/ca.rb +0 -518
- data/lib/leap_cli/commands/clean.rb +0 -16
- data/lib/leap_cli/commands/compile.rb +0 -340
- data/lib/leap_cli/commands/db.rb +0 -65
- data/lib/leap_cli/commands/deploy.rb +0 -368
- data/lib/leap_cli/commands/env.rb +0 -76
- data/lib/leap_cli/commands/facts.rb +0 -100
- data/lib/leap_cli/commands/inspect.rb +0 -144
- data/lib/leap_cli/commands/list.rb +0 -132
- data/lib/leap_cli/commands/node.rb +0 -165
- data/lib/leap_cli/commands/node_init.rb +0 -169
- data/lib/leap_cli/commands/ssh.rb +0 -220
- data/lib/leap_cli/commands/test.rb +0 -74
- data/lib/leap_cli/commands/user.rb +0 -136
- data/lib/leap_cli/commands/util.rb +0 -50
- 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
|
-
|
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
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
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
|
data/lib/leap_cli/ssh_key.rb
CHANGED
@@ -161,8 +161,11 @@ module LeapCli
|
|
161
161
|
end
|
162
162
|
|
163
163
|
def summary
|
164
|
-
|
165
|
-
|
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
|
data/lib/leap_cli/version.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
module LeapCli
|
2
2
|
unless defined?(LeapCli::VERSION)
|
3
|
-
VERSION = '1.
|
4
|
-
COMPATIBLE_PLATFORM_VERSION = '0.
|
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.
|
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:
|
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/
|
191
|
-
- lib/leap_cli/commands/
|
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/
|
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
|
data/lib/leap_cli/commands/ca.rb
DELETED
@@ -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
|