puppetserver-ca 0.3.1 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/puppetserver/ca/action/clean.rb +102 -0
- data/lib/puppetserver/ca/action/create.rb +161 -0
- data/lib/puppetserver/ca/action/generate.rb +313 -0
- data/lib/puppetserver/ca/action/import.rb +132 -0
- data/lib/puppetserver/ca/action/list.rb +132 -0
- data/lib/puppetserver/ca/action/revoke.rb +101 -0
- data/lib/puppetserver/ca/action/sign.rb +126 -0
- data/lib/puppetserver/ca/certificate_authority.rb +224 -0
- data/lib/puppetserver/ca/cli.rb +17 -16
- data/lib/puppetserver/ca/config/puppet.rb +242 -0
- data/lib/puppetserver/ca/config/puppetserver.rb +85 -0
- data/lib/puppetserver/ca/utils/cli_parsing.rb +82 -0
- data/lib/puppetserver/ca/utils/config.rb +13 -0
- data/lib/puppetserver/ca/utils/file_system.rb +90 -0
- data/lib/puppetserver/ca/utils/http_client.rb +129 -0
- data/lib/puppetserver/ca/utils/signing_digest.rb +27 -0
- data/lib/puppetserver/ca/version.rb +1 -1
- metadata +17 -17
- data/lib/puppetserver/ca/clean_action.rb +0 -157
- data/lib/puppetserver/ca/config_utils.rb +0 -11
- data/lib/puppetserver/ca/create_action.rb +0 -265
- data/lib/puppetserver/ca/generate_action.rb +0 -227
- data/lib/puppetserver/ca/import_action.rb +0 -153
- data/lib/puppetserver/ca/list_action.rb +0 -153
- data/lib/puppetserver/ca/puppet_config.rb +0 -197
- data/lib/puppetserver/ca/puppetserver_config.rb +0 -83
- data/lib/puppetserver/ca/revoke_action.rb +0 -136
- data/lib/puppetserver/ca/sign_action.rb +0 -190
- data/lib/puppetserver/ca/utils.rb +0 -80
- data/lib/puppetserver/settings/ttl_setting.rb +0 -48
- data/lib/puppetserver/utils/file_utilities.rb +0 -78
- data/lib/puppetserver/utils/http_client.rb +0 -129
- data/lib/puppetserver/utils/signing_digest.rb +0 -25
@@ -1,157 +0,0 @@
|
|
1
|
-
require 'puppetserver/ca/utils'
|
2
|
-
require 'puppetserver/utils/http_client'
|
3
|
-
require 'puppetserver/utils/file_utilities'
|
4
|
-
require 'puppetserver/ca/puppet_config'
|
5
|
-
require 'puppetserver/ca/revoke_action'
|
6
|
-
|
7
|
-
require 'optparse'
|
8
|
-
require 'json'
|
9
|
-
|
10
|
-
module Puppetserver
|
11
|
-
module Ca
|
12
|
-
class CleanAction
|
13
|
-
|
14
|
-
include Puppetserver::Utils
|
15
|
-
|
16
|
-
CERTNAME_BLACKLIST = %w{--all --config}
|
17
|
-
|
18
|
-
SUMMARY = 'Clean files from the CA for certificate(s)'
|
19
|
-
BANNER = <<-BANNER
|
20
|
-
Usage:
|
21
|
-
puppetserver ca clean [--help|--version]
|
22
|
-
puppetserver ca clean [--config] --certname CERTNAME[,ADDLCERTNAME]
|
23
|
-
|
24
|
-
Description:
|
25
|
-
Given one or more valid certnames, instructs the CA to revoke certificates
|
26
|
-
matching the given certnames if they exist, and then remove files pertaining
|
27
|
-
to them (keys, cert, and certificate request) over HTTPS using the local
|
28
|
-
agent's PKI
|
29
|
-
|
30
|
-
Options:
|
31
|
-
BANNER
|
32
|
-
|
33
|
-
def self.parser(parsed = {})
|
34
|
-
parsed['certnames'] = []
|
35
|
-
OptionParser.new do |o|
|
36
|
-
o.banner = BANNER
|
37
|
-
o.on('--certname foo,bar', Array,
|
38
|
-
'One or more comma separated certnames') do |certs|
|
39
|
-
parsed['certnames'] += certs
|
40
|
-
end
|
41
|
-
o.on('--config PUPPET.CONF', 'Custom path to puppet.conf') do |conf|
|
42
|
-
parsed['config'] = conf
|
43
|
-
end
|
44
|
-
o.on('--help', 'Displays this clean specific help output') do |help|
|
45
|
-
parsed['help'] = help
|
46
|
-
end
|
47
|
-
end
|
48
|
-
end
|
49
|
-
|
50
|
-
def initialize(logger)
|
51
|
-
@logger = logger
|
52
|
-
end
|
53
|
-
|
54
|
-
def parse(args)
|
55
|
-
results = {}
|
56
|
-
parser = self.class.parser(results)
|
57
|
-
|
58
|
-
errors = Utils.parse_with_errors(parser, args)
|
59
|
-
|
60
|
-
results['certnames'].each do |certname|
|
61
|
-
if CERTNAME_BLACKLIST.include?(certname)
|
62
|
-
errors << " Cannot manage cert named `#{certname}` from " +
|
63
|
-
"the CLI, if needed use the HTTP API directly"
|
64
|
-
end
|
65
|
-
end
|
66
|
-
|
67
|
-
if results['certnames'].empty?
|
68
|
-
errors << ' At least one certname is required to clean'
|
69
|
-
end
|
70
|
-
|
71
|
-
errors_were_handled = Utils.handle_errors(@logger, errors, parser.help)
|
72
|
-
|
73
|
-
exit_code = errors_were_handled ? 1 : nil
|
74
|
-
|
75
|
-
return results, exit_code
|
76
|
-
end
|
77
|
-
|
78
|
-
def run(args)
|
79
|
-
certnames = args['certnames']
|
80
|
-
config = args['config']
|
81
|
-
|
82
|
-
if config
|
83
|
-
errors = FileUtilities.validate_file_paths(config)
|
84
|
-
return 1 if Utils.handle_errors(@logger, errors)
|
85
|
-
end
|
86
|
-
|
87
|
-
puppet = PuppetConfig.parse(config)
|
88
|
-
return 1 if Utils.handle_errors(@logger, puppet.errors)
|
89
|
-
|
90
|
-
passed = clean_certs(certnames, puppet.settings)
|
91
|
-
|
92
|
-
return passed ? 0 : 1
|
93
|
-
end
|
94
|
-
|
95
|
-
def clean_certs(certnames, settings)
|
96
|
-
client = HttpClient.new(settings)
|
97
|
-
|
98
|
-
url = client.make_ca_url(settings[:ca_server],
|
99
|
-
settings[:ca_port],
|
100
|
-
'certificate_status')
|
101
|
-
|
102
|
-
results = client.with_connection(url) do |connection|
|
103
|
-
certnames.map do |certname|
|
104
|
-
url.resource_name = certname
|
105
|
-
revoke_result = connection.put(RevokeAction::REQUEST_BODY, url)
|
106
|
-
revoked = check_revocation(revoke_result, certname)
|
107
|
-
|
108
|
-
cleaned = nil
|
109
|
-
unless revoked == :error
|
110
|
-
clean_result = connection.delete(url)
|
111
|
-
cleaned = check_result(clean_result, certname)
|
112
|
-
end
|
113
|
-
|
114
|
-
cleaned == :success && [:success, :not_found].include?(revoked)
|
115
|
-
end
|
116
|
-
end
|
117
|
-
|
118
|
-
return results.all?
|
119
|
-
end
|
120
|
-
|
121
|
-
# possibly logs the action, always returns a status symbol 👑
|
122
|
-
def check_revocation(result, certname)
|
123
|
-
case result.code
|
124
|
-
when '200', '204'
|
125
|
-
@logger.inform "Revoked certificate for #{certname}"
|
126
|
-
return :success
|
127
|
-
when '404'
|
128
|
-
return :not_found
|
129
|
-
else
|
130
|
-
@logger.err 'Error:'
|
131
|
-
@logger.err " Failed revoking certificate for #{certname}"
|
132
|
-
@logger.err " Received code: #{result.code}, body: #{result.body}"
|
133
|
-
return :error
|
134
|
-
end
|
135
|
-
end
|
136
|
-
|
137
|
-
# logs the action and returns a status symbol 👑
|
138
|
-
def check_result(result, certname)
|
139
|
-
case result.code
|
140
|
-
when '200', '204'
|
141
|
-
@logger.inform "Cleaned files related to #{certname}"
|
142
|
-
return :success
|
143
|
-
when '404'
|
144
|
-
@logger.err 'Error:'
|
145
|
-
@logger.err " Could not find files for #{certname}"
|
146
|
-
return :not_found
|
147
|
-
else
|
148
|
-
@logger.err 'Error:'
|
149
|
-
@logger.err " When cleaning #{certname} received:"
|
150
|
-
@logger.err " code: #{result.code}"
|
151
|
-
@logger.err " body: #{result.body.to_s}" if result.body
|
152
|
-
return :error
|
153
|
-
end
|
154
|
-
end
|
155
|
-
end
|
156
|
-
end
|
157
|
-
end
|
@@ -1,265 +0,0 @@
|
|
1
|
-
require 'puppetserver/ca/utils'
|
2
|
-
require 'puppetserver/ca/host'
|
3
|
-
require 'puppetserver/ca/puppet_config'
|
4
|
-
require 'puppetserver/utils/file_utilities'
|
5
|
-
require 'puppetserver/utils/http_client'
|
6
|
-
require 'puppetserver/utils/signing_digest'
|
7
|
-
require 'json'
|
8
|
-
|
9
|
-
module Puppetserver
|
10
|
-
module Ca
|
11
|
-
class CreateAction
|
12
|
-
|
13
|
-
include Puppetserver::Utils
|
14
|
-
|
15
|
-
# Only allow printing ascii characters, excluding /
|
16
|
-
VALID_CERTNAME = /\A[ -.0-~]+\Z/
|
17
|
-
CERTNAME_BLACKLIST = %w{--all --config}
|
18
|
-
|
19
|
-
SUMMARY = "Create a new certificate signed by the CA"
|
20
|
-
BANNER = <<-BANNER
|
21
|
-
Usage:
|
22
|
-
puppetserver ca create [--help]
|
23
|
-
puppetserver ca create [--config] --certname CERTNAME[,ADDLCERTNAME]
|
24
|
-
|
25
|
-
Description:
|
26
|
-
Creates a new certificate signed by the intermediate CA
|
27
|
-
and stores generated keys and certs on disk.
|
28
|
-
|
29
|
-
To determine the target location, the default puppet.conf
|
30
|
-
is consulted for custom values. If using a custom puppet.conf
|
31
|
-
provide it with the --config flag
|
32
|
-
|
33
|
-
Options:
|
34
|
-
BANNER
|
35
|
-
def initialize(logger)
|
36
|
-
@logger = logger
|
37
|
-
end
|
38
|
-
|
39
|
-
def self.parser(parsed = {})
|
40
|
-
parsed['certnames'] = []
|
41
|
-
OptionParser.new do |opts|
|
42
|
-
opts.banner = BANNER
|
43
|
-
opts.on('--certname FOO,BAR', Array,
|
44
|
-
'One or more comma separated certnames') do |certs|
|
45
|
-
parsed['certnames'] += certs
|
46
|
-
end
|
47
|
-
opts.on('--help', 'Display this create specific help output') do |help|
|
48
|
-
parsed['help'] = true
|
49
|
-
end
|
50
|
-
opts.on('--config CONF', 'Path to puppet.conf') do |conf|
|
51
|
-
parsed['config'] = conf
|
52
|
-
end
|
53
|
-
end
|
54
|
-
end
|
55
|
-
|
56
|
-
def parse(args)
|
57
|
-
results = {}
|
58
|
-
parser = self.class.parser(results)
|
59
|
-
|
60
|
-
errors = Utils.parse_with_errors(parser, args)
|
61
|
-
|
62
|
-
if results['certnames'].empty?
|
63
|
-
errors << ' At least one certname is required to create'
|
64
|
-
else
|
65
|
-
results['certnames'].each do |certname|
|
66
|
-
if CERTNAME_BLACKLIST.include?(certname)
|
67
|
-
errors << " Cannot manage cert named `#{certname}` from " +
|
68
|
-
"the CLI, if needed use the HTTP API directly"
|
69
|
-
end
|
70
|
-
|
71
|
-
if certname.match(/\p{Upper}/)
|
72
|
-
errors << " Certificate names must be lower case"
|
73
|
-
end
|
74
|
-
|
75
|
-
unless certname =~ VALID_CERTNAME
|
76
|
-
errors << " Certname #{certname} must not contain unprintable or non-ASCII characters"
|
77
|
-
end
|
78
|
-
end
|
79
|
-
end
|
80
|
-
|
81
|
-
errors_were_handled = Utils.handle_errors(@logger, errors, parser.help)
|
82
|
-
|
83
|
-
exit_code = errors_were_handled ? 1 : nil
|
84
|
-
|
85
|
-
return results, exit_code
|
86
|
-
end
|
87
|
-
|
88
|
-
def run(input)
|
89
|
-
certnames = input['certnames']
|
90
|
-
config_path = input['config']
|
91
|
-
|
92
|
-
# Validate config_path provided
|
93
|
-
if config_path
|
94
|
-
errors = FileUtilities.validate_file_paths(config_path)
|
95
|
-
return 1 if Utils.handle_errors(@logger, errors)
|
96
|
-
end
|
97
|
-
|
98
|
-
# Load, resolve, and validate puppet config settings
|
99
|
-
puppet = PuppetConfig.parse(config_path)
|
100
|
-
return 1 if Utils.handle_errors(@logger, puppet.errors)
|
101
|
-
|
102
|
-
# Load most secure signing digest we can for csr signing.
|
103
|
-
signer = SigningDigest.new
|
104
|
-
return 1 if Utils.handle_errors(@logger, signer.errors)
|
105
|
-
|
106
|
-
# Make sure we have all the directories where we will be writing files
|
107
|
-
FileUtilities.ensure_dir(puppet.settings[:certdir])
|
108
|
-
FileUtilities.ensure_dir(puppet.settings[:privatekeydir])
|
109
|
-
FileUtilities.ensure_dir(puppet.settings[:publickeydir])
|
110
|
-
|
111
|
-
# Generate and save certs and associated keys
|
112
|
-
all_passed = generate_certs(certnames, puppet.settings, signer.digest)
|
113
|
-
return all_passed ? 0 : 1
|
114
|
-
end
|
115
|
-
|
116
|
-
# Create csrs and keys, then submit them to CA, request for the CA to sign
|
117
|
-
# them, download the signed certificates from the CA, and finally save
|
118
|
-
# the signed certs and associated keys. Returns true if all certs were
|
119
|
-
# successfully created and saved.
|
120
|
-
def generate_certs(certnames, settings, digest)
|
121
|
-
passed = certnames.map do |certname|
|
122
|
-
key, csr = generate_key_csr(certname, settings, digest)
|
123
|
-
return false unless submit_certificate_request(certname, csr.to_s, settings)
|
124
|
-
return false unless sign_cert(certname, settings)
|
125
|
-
if download_cert(certname, settings)
|
126
|
-
save_keys(key, certname, settings)
|
127
|
-
true
|
128
|
-
else
|
129
|
-
false
|
130
|
-
end
|
131
|
-
end
|
132
|
-
passed.all?
|
133
|
-
end
|
134
|
-
|
135
|
-
def generate_key_csr(certname, settings, digest)
|
136
|
-
host = Puppetserver::Ca::Host.new(digest)
|
137
|
-
private_key = host.create_private_key(settings[:keylength])
|
138
|
-
csr = host.create_csr(certname, private_key)
|
139
|
-
|
140
|
-
return private_key, csr
|
141
|
-
end
|
142
|
-
|
143
|
-
def http_client(settings)
|
144
|
-
@client ||= HttpClient.new(settings)
|
145
|
-
end
|
146
|
-
|
147
|
-
# Make an HTTP request to submit certificate requests to CA
|
148
|
-
# @param certname [String] the name of the certificate to fetch
|
149
|
-
# @param csr [String] string version of a OpenSSL::X509::Request
|
150
|
-
# @param settings [Hash] a hash of config settings
|
151
|
-
# @return [Boolean] success of all csrs being submitted to CA
|
152
|
-
def submit_certificate_request(certname, csr, settings)
|
153
|
-
client = http_client(settings)
|
154
|
-
url = client.make_ca_url(settings[:ca_server],
|
155
|
-
settings[:ca_port],
|
156
|
-
'certificate_request',
|
157
|
-
certname)
|
158
|
-
|
159
|
-
client.with_connection(url) do |connection|
|
160
|
-
result = connection.put(csr, url)
|
161
|
-
check_submit_result(result, certname)
|
162
|
-
end
|
163
|
-
end
|
164
|
-
|
165
|
-
def check_submit_result(result, certname)
|
166
|
-
case result.code
|
167
|
-
when '200', '204'
|
168
|
-
@logger.inform "Successfully submitted certificate request for #{certname}"
|
169
|
-
return true
|
170
|
-
else
|
171
|
-
@logger.err 'Error:'
|
172
|
-
@logger.err " When certificate request submitted for #{certname}:"
|
173
|
-
@logger.err " code: #{result.code}"
|
174
|
-
@logger.err " body: #{result.body.to_s}" if result.body
|
175
|
-
return false
|
176
|
-
end
|
177
|
-
end
|
178
|
-
|
179
|
-
# Make an HTTP request to CA to sign the named certificates
|
180
|
-
# @param certname [String] the name of the certificate to have signed
|
181
|
-
# @param settings [Hash] a hash of config settings
|
182
|
-
# @return [Boolean] the success of certificates being signed
|
183
|
-
def sign_cert(certname, settings)
|
184
|
-
client = http_client(settings)
|
185
|
-
|
186
|
-
url = client.make_ca_url(settings[:ca_server],
|
187
|
-
settings[:ca_port],
|
188
|
-
'certificate_status',
|
189
|
-
certname)
|
190
|
-
|
191
|
-
client.with_connection(url) do |connection|
|
192
|
-
body = JSON.dump({desired_state: 'signed'})
|
193
|
-
result = connection.put(body, url)
|
194
|
-
check_sign_result(result, certname)
|
195
|
-
end
|
196
|
-
end
|
197
|
-
|
198
|
-
def check_sign_result(result, certname)
|
199
|
-
case result.code
|
200
|
-
when '204'
|
201
|
-
@logger.inform "Successfully signed certificate request for #{certname}"
|
202
|
-
return true
|
203
|
-
else
|
204
|
-
@logger.err 'Error:'
|
205
|
-
@logger.err " When signing request submitted for #{certname}:"
|
206
|
-
@logger.err " code: #{result.code}"
|
207
|
-
@logger.err " body: #{result.body.to_s}" if result.body
|
208
|
-
return false
|
209
|
-
end
|
210
|
-
end
|
211
|
-
|
212
|
-
# Make an HTTP request to fetch the named certificates from CA
|
213
|
-
# @param certname [String] the name of the certificate to fetch
|
214
|
-
# @param settings [Hash] a hash of config settings
|
215
|
-
# @return [Boolean] the success of certificate being downloaded
|
216
|
-
def download_cert(certname, settings)
|
217
|
-
client = http_client(settings)
|
218
|
-
url = client.make_ca_url(settings[:ca_server],
|
219
|
-
settings[:ca_port],
|
220
|
-
'certificate',
|
221
|
-
certname)
|
222
|
-
client.with_connection(url) do |connection|
|
223
|
-
result = connection.get(url)
|
224
|
-
if downloaded = check_download_result(result, certname)
|
225
|
-
save_file(result.body, certname, settings[:certdir], "Certificate")
|
226
|
-
@logger.inform "Successfully downloaded and saved certificate #{certname} to #{settings[:certdir]}/#{certname}.pem"
|
227
|
-
end
|
228
|
-
downloaded
|
229
|
-
end
|
230
|
-
end
|
231
|
-
|
232
|
-
def check_download_result(result, certname)
|
233
|
-
case result.code
|
234
|
-
when '200'
|
235
|
-
return true
|
236
|
-
when '404'
|
237
|
-
@logger.err 'Error:'
|
238
|
-
@logger.err " Signed certificate #{certname} could not be found on the CA"
|
239
|
-
return false
|
240
|
-
else
|
241
|
-
@logger.err 'Error:'
|
242
|
-
@logger.err " When download requested for certificate #{certname}:"
|
243
|
-
@logger.err " code: #{result.code}"
|
244
|
-
@logger.err " body: #{result.body.to_s}" if result.body
|
245
|
-
return false
|
246
|
-
end
|
247
|
-
end
|
248
|
-
|
249
|
-
def save_keys(key, certname, settings)
|
250
|
-
public_key = key.public_key
|
251
|
-
save_file(key, certname, settings[:privatekeydir], "Private key")
|
252
|
-
save_file(public_key, certname, settings[:publickeydir], "Public key")
|
253
|
-
@logger.inform "Successfully saved private key for #{certname} to #{settings[:privatekeydir]}/#{certname}.pem"
|
254
|
-
@logger.inform "Successfully saved public key for #{certname} to #{settings[:publickeydir]}/#{certname}.pem"
|
255
|
-
end
|
256
|
-
|
257
|
-
|
258
|
-
def save_file(content, certname, dir, type)
|
259
|
-
location = File.join(dir, "#{certname}.pem")
|
260
|
-
@logger.warn "#{type} #{certname}.pem already exists, overwriting" if File.exist?(location)
|
261
|
-
FileUtilities.write_file(location, content, 0640)
|
262
|
-
end
|
263
|
-
end
|
264
|
-
end
|
265
|
-
end
|
@@ -1,227 +0,0 @@
|
|
1
|
-
require 'optparse'
|
2
|
-
require 'openssl'
|
3
|
-
require 'puppetserver/utils/file_utilities'
|
4
|
-
require 'puppetserver/ca/host'
|
5
|
-
require 'puppetserver/utils/signing_digest'
|
6
|
-
|
7
|
-
module Puppetserver
|
8
|
-
module Ca
|
9
|
-
class GenerateAction
|
10
|
-
include Puppetserver::Utils
|
11
|
-
|
12
|
-
CA_EXTENSIONS = [
|
13
|
-
["basicConstraints", "CA:TRUE", true],
|
14
|
-
["keyUsage", "keyCertSign, cRLSign", true],
|
15
|
-
["subjectKeyIdentifier", "hash", false],
|
16
|
-
["authorityKeyIdentifier", "keyid:always", false]
|
17
|
-
].freeze
|
18
|
-
|
19
|
-
# Make the certificate valid as of yesterday, because so many people's
|
20
|
-
# clocks are out of sync. This gives one more day of validity than people
|
21
|
-
# might expect, but is better than making every person who has a messed up
|
22
|
-
# clock fail, and better than having every cert we generate expire a day
|
23
|
-
# before the user expected it to when they asked for "one year".
|
24
|
-
CERT_VALID_FROM = (Time.now - (60*60*24)).freeze
|
25
|
-
|
26
|
-
SUMMARY = "Generate a root and intermediate signing CA for Puppet Server"
|
27
|
-
BANNER = <<-BANNER
|
28
|
-
Usage:
|
29
|
-
puppetserver ca generate [--help]
|
30
|
-
puppetserver ca generate [--config PATH]
|
31
|
-
|
32
|
-
Description:
|
33
|
-
Generate a root and intermediate signing CA for Puppet Server
|
34
|
-
and store generated CA keys, certs, and crls on disk.
|
35
|
-
|
36
|
-
To determine the target location, the default puppet.conf
|
37
|
-
is consulted for custom values. If using a custom puppet.conf
|
38
|
-
provide it with the --config flag
|
39
|
-
|
40
|
-
Options:
|
41
|
-
BANNER
|
42
|
-
|
43
|
-
def initialize(logger)
|
44
|
-
@logger = logger
|
45
|
-
end
|
46
|
-
|
47
|
-
def run(input)
|
48
|
-
# Validate config_path provided
|
49
|
-
config_path = input['config']
|
50
|
-
if config_path
|
51
|
-
errors = FileUtilities.validate_file_paths(config_path)
|
52
|
-
return 1 if Utils.handle_errors(@logger, errors)
|
53
|
-
end
|
54
|
-
|
55
|
-
# Load, resolve, and validate puppet config settings
|
56
|
-
puppet = PuppetConfig.parse(config_path)
|
57
|
-
return 1 if Utils.handle_errors(@logger, puppet.errors)
|
58
|
-
|
59
|
-
# Load most secure signing digest we can for cers/crl/csr signing.
|
60
|
-
signer = SigningDigest.new
|
61
|
-
return 1 if Utils.handle_errors(@logger, signer.errors)
|
62
|
-
|
63
|
-
# Generate root and intermediate ca and put all the certificates, crls,
|
64
|
-
# and keys where they should go.
|
65
|
-
generate_root_and_intermediate_ca(puppet.settings, signer.digest)
|
66
|
-
|
67
|
-
# Puppet's internal CA expects these file to exist.
|
68
|
-
FileUtilities.ensure_file(puppet.settings[:serial], "001", 0640)
|
69
|
-
FileUtilities.ensure_file(puppet.settings[:cert_inventory], "", 0640)
|
70
|
-
|
71
|
-
@logger.inform "Generation succeeded. Find your files in #{puppet.settings[:cadir]}"
|
72
|
-
return 0
|
73
|
-
end
|
74
|
-
|
75
|
-
def generate_root_and_intermediate_ca(settings, signing_digest)
|
76
|
-
valid_until = Time.now + settings[:ca_ttl]
|
77
|
-
host = Puppetserver::Ca::Host.new(signing_digest)
|
78
|
-
|
79
|
-
root_key = host.create_private_key(settings[:keylength])
|
80
|
-
root_cert = self_signed_ca(root_key, settings[:root_ca_name], valid_until, signing_digest)
|
81
|
-
root_crl = create_crl_for(root_cert, root_key, valid_until, signing_digest)
|
82
|
-
|
83
|
-
int_key = host.create_private_key(settings[:keylength])
|
84
|
-
int_csr = host.create_csr(settings[:ca_name], int_key)
|
85
|
-
int_cert = sign_intermediate(root_key, root_cert, int_csr, valid_until, signing_digest)
|
86
|
-
int_crl = create_crl_for(int_cert, int_key, valid_until, signing_digest)
|
87
|
-
|
88
|
-
FileUtilities.ensure_dir(settings[:cadir])
|
89
|
-
|
90
|
-
file_properties = [
|
91
|
-
[settings[:cacert], [int_cert, root_cert]],
|
92
|
-
[settings[:cakey], int_key],
|
93
|
-
[settings[:rootkey], root_key],
|
94
|
-
[settings[:cacrl], [int_crl, root_crl]]
|
95
|
-
]
|
96
|
-
|
97
|
-
file_properties.each do |location, content|
|
98
|
-
@logger.warn "#{location} exists, overwriting" if File.exist?(location)
|
99
|
-
FileUtilities.write_file(location, content, 0640)
|
100
|
-
end
|
101
|
-
end
|
102
|
-
|
103
|
-
def self_signed_ca(key, name, valid_until, signing_digest)
|
104
|
-
cert = OpenSSL::X509::Certificate.new
|
105
|
-
|
106
|
-
cert.public_key = key.public_key
|
107
|
-
cert.subject = OpenSSL::X509::Name.new([["CN", name]])
|
108
|
-
cert.issuer = cert.subject
|
109
|
-
cert.version = 2
|
110
|
-
cert.serial = 1
|
111
|
-
|
112
|
-
cert.not_before = CERT_VALID_FROM
|
113
|
-
cert.not_after = valid_until
|
114
|
-
|
115
|
-
ef = extension_factory_for(cert, cert)
|
116
|
-
CA_EXTENSIONS.each do |ext|
|
117
|
-
extension = ef.create_extension(*ext)
|
118
|
-
cert.add_extension(extension)
|
119
|
-
end
|
120
|
-
|
121
|
-
cert.sign(key, signing_digest)
|
122
|
-
|
123
|
-
cert
|
124
|
-
end
|
125
|
-
|
126
|
-
def extension_factory_for(ca, cert = nil)
|
127
|
-
ef = OpenSSL::X509::ExtensionFactory.new
|
128
|
-
ef.issuer_certificate = ca
|
129
|
-
ef.subject_certificate = cert if cert
|
130
|
-
|
131
|
-
ef
|
132
|
-
end
|
133
|
-
|
134
|
-
def create_crl_for(ca_cert, ca_key, valid_until, signing_digest)
|
135
|
-
crl = OpenSSL::X509::CRL.new
|
136
|
-
crl.version = 1
|
137
|
-
crl.issuer = ca_cert.subject
|
138
|
-
|
139
|
-
ef = extension_factory_for(ca_cert)
|
140
|
-
crl.add_extension(
|
141
|
-
ef.create_extension(["authorityKeyIdentifier", "keyid:always", false]))
|
142
|
-
crl.add_extension(
|
143
|
-
OpenSSL::X509::Extension.new("crlNumber", OpenSSL::ASN1::Integer(0)))
|
144
|
-
|
145
|
-
crl.last_update = CERT_VALID_FROM
|
146
|
-
crl.next_update = valid_until
|
147
|
-
crl.sign(ca_key, signing_digest)
|
148
|
-
|
149
|
-
crl
|
150
|
-
end
|
151
|
-
|
152
|
-
def sign_intermediate(ca_key, ca_cert, csr, valid_until, signing_digest)
|
153
|
-
cert = OpenSSL::X509::Certificate.new
|
154
|
-
|
155
|
-
cert.public_key = csr.public_key
|
156
|
-
cert.subject = csr.subject
|
157
|
-
cert.issuer = ca_cert.subject
|
158
|
-
cert.version = 2
|
159
|
-
cert.serial = 2
|
160
|
-
|
161
|
-
cert.not_before = CERT_VALID_FROM
|
162
|
-
cert.not_after = valid_until
|
163
|
-
|
164
|
-
ef = extension_factory_for(ca_cert, cert)
|
165
|
-
CA_EXTENSIONS.each do |ext|
|
166
|
-
extension = ef.create_extension(*ext)
|
167
|
-
cert.add_extension(extension)
|
168
|
-
end
|
169
|
-
cert.sign(ca_key, signing_digest)
|
170
|
-
|
171
|
-
cert
|
172
|
-
end
|
173
|
-
|
174
|
-
def parse(cli_args)
|
175
|
-
parser, inputs, unparsed = parse_inputs(cli_args)
|
176
|
-
|
177
|
-
if !unparsed.empty?
|
178
|
-
@logger.err 'Error:'
|
179
|
-
@logger.err 'Unknown arguments or flags:'
|
180
|
-
unparsed.each do |arg|
|
181
|
-
@logger.err " #{arg}"
|
182
|
-
end
|
183
|
-
|
184
|
-
@logger.err ''
|
185
|
-
@logger.err parser.help
|
186
|
-
|
187
|
-
exit_code = 1
|
188
|
-
else
|
189
|
-
exit_code = nil
|
190
|
-
end
|
191
|
-
|
192
|
-
return inputs, exit_code
|
193
|
-
end
|
194
|
-
|
195
|
-
def parse_inputs(inputs)
|
196
|
-
parsed = {}
|
197
|
-
unparsed = []
|
198
|
-
|
199
|
-
parser = self.class.parser(parsed)
|
200
|
-
|
201
|
-
begin
|
202
|
-
parser.order!(inputs) do |nonopt|
|
203
|
-
unparsed << nonopt
|
204
|
-
end
|
205
|
-
rescue OptionParser::ParseError => e
|
206
|
-
unparsed += e.args
|
207
|
-
unparsed << inputs.shift unless inputs.first =~ /^-{1,2}/
|
208
|
-
retry
|
209
|
-
end
|
210
|
-
|
211
|
-
return parser, parsed, unparsed
|
212
|
-
end
|
213
|
-
|
214
|
-
def self.parser(parsed = {})
|
215
|
-
OptionParser.new do |opts|
|
216
|
-
opts.banner = BANNER
|
217
|
-
opts.on('--help', 'Display this generate specific help output') do |help|
|
218
|
-
parsed['help'] = true
|
219
|
-
end
|
220
|
-
opts.on('--config CONF', 'Path to puppet.conf') do |conf|
|
221
|
-
parsed['config'] = conf
|
222
|
-
end
|
223
|
-
end
|
224
|
-
end
|
225
|
-
end
|
226
|
-
end
|
227
|
-
end
|