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