puppetserver-ca 0.3.1 → 0.4.0
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/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
|