puppetserver-ca 0.6.0 → 0.7.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 +4 -4
- data/lib/puppetserver/ca/action/generate.rb +123 -118
- data/lib/puppetserver/ca/action/import.rb +3 -3
- data/lib/puppetserver/ca/action/list.rb +1 -1
- data/lib/puppetserver/ca/action/revoke.rb +4 -4
- data/lib/puppetserver/ca/action/setup.rb +181 -0
- data/lib/puppetserver/ca/action/sign.rb +4 -4
- data/lib/puppetserver/ca/cli.rb +20 -10
- data/lib/puppetserver/ca/version.rb +1 -1
- metadata +3 -3
- data/lib/puppetserver/ca/action/create.rb +0 -185
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1d0f3441468d38c6a0385a7c2ed7e3056e9e7407
|
4
|
+
data.tar.gz: 84e643ade9ca258be16166e16b1577dd454ec9f8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6dd35a352ff2ca7aec331efd573781224471e03efcd451fd35d5c13416b6f2d2cceec34339d0ff9016840a77af321b9821d6fff1f2d6d42a568316786b8e19bd
|
7
|
+
data.tar.gz: 6e460d0db0cf328e17968950741eade841198ce165e306b5ca07e81343d1c29045b3baab88ce0be78c90c3a50c969fc4323c6e626f195814bd7fba6f08707e78
|
@@ -15,11 +15,11 @@ module Puppetserver
|
|
15
15
|
|
16
16
|
CERTNAME_BLACKLIST = %w{--all --config}
|
17
17
|
|
18
|
-
SUMMARY = '
|
18
|
+
SUMMARY = 'Revoke cert(s) and remove related files from CA'
|
19
19
|
BANNER = <<-BANNER
|
20
20
|
Usage:
|
21
21
|
puppetserver ca clean [--help]
|
22
|
-
puppetserver ca clean [--config] --certname
|
22
|
+
puppetserver ca clean [--config] --certname NAME[,NAME]
|
23
23
|
|
24
24
|
Description:
|
25
25
|
Given one or more valid certnames, instructs the CA to revoke certificates
|
@@ -34,11 +34,11 @@ BANNER
|
|
34
34
|
parsed['certnames'] = []
|
35
35
|
OptionParser.new do |o|
|
36
36
|
o.banner = BANNER
|
37
|
-
o.on('--certname
|
37
|
+
o.on('--certname NAME[,NAME]', Array,
|
38
38
|
'One or more comma separated certnames') do |certs|
|
39
39
|
parsed['certnames'] += certs
|
40
40
|
end
|
41
|
-
o.on('--config
|
41
|
+
o.on('--config CONF', 'Custom path to puppet.conf') do |conf|
|
42
42
|
parsed['config'] = conf
|
43
43
|
end
|
44
44
|
o.on('--help', 'Display this clean specific help output') do |help|
|
@@ -1,32 +1,31 @@
|
|
1
|
-
require 'optparse'
|
2
|
-
require 'puppetserver/ca/utils/file_system'
|
3
|
-
require 'puppetserver/ca/local_certificate_authority'
|
4
1
|
require 'puppetserver/ca/utils/cli_parsing'
|
5
|
-
require 'puppetserver/ca/
|
2
|
+
require 'puppetserver/ca/host'
|
3
|
+
require 'puppetserver/ca/certificate_authority'
|
6
4
|
require 'puppetserver/ca/config/puppet'
|
5
|
+
require 'puppetserver/ca/utils/file_system'
|
6
|
+
require 'puppetserver/ca/utils/signing_digest'
|
7
7
|
|
8
8
|
module Puppetserver
|
9
9
|
module Ca
|
10
10
|
module Action
|
11
11
|
class Generate
|
12
|
+
|
12
13
|
include Puppetserver::Ca::Utils
|
13
14
|
|
14
|
-
|
15
|
+
# Only allow printing ascii characters, excluding /
|
16
|
+
VALID_CERTNAME = /\A[ -.0-~]+\Z/
|
17
|
+
CERTNAME_BLACKLIST = %w{--all --config}
|
18
|
+
|
19
|
+
SUMMARY = "Generate a new certificate signed by the CA"
|
15
20
|
BANNER = <<-BANNER
|
16
21
|
Usage:
|
17
22
|
puppetserver ca generate [--help]
|
18
|
-
puppetserver ca generate [--config PATH] [--
|
19
|
-
[--
|
23
|
+
puppetserver ca generate [--config PATH] [--certname NAME[,NAME]]
|
24
|
+
[--subject-alt-names NAME[,NAME]]
|
20
25
|
|
21
26
|
Description:
|
22
|
-
|
23
|
-
and
|
24
|
-
|
25
|
-
The `--subject-alt-names` flag can be used to add SANs to the
|
26
|
-
certificate generated for the Puppet master. Multiple names can be
|
27
|
-
listed as a comma separated string. These can be either DNS names or
|
28
|
-
IP addresses, differentiated by prefixes: `DNS:foo.bar.com,IP:123.456.789`.
|
29
|
-
Names with no prefix will be treated as DNS names.
|
27
|
+
Generates a new certificate signed by the intermediate CA
|
28
|
+
and stores generated keys and certs on disk.
|
30
29
|
|
31
30
|
To determine the target location, the default puppet.conf
|
32
31
|
is consulted for custom values. If using a custom puppet.conf
|
@@ -34,14 +33,69 @@ provide it with the --config flag
|
|
34
33
|
|
35
34
|
Options:
|
36
35
|
BANNER
|
37
|
-
|
38
36
|
def initialize(logger)
|
39
37
|
@logger = logger
|
40
38
|
end
|
41
39
|
|
40
|
+
def self.parser(parsed = {})
|
41
|
+
parsed['certnames'] = []
|
42
|
+
parsed['subject-alt-names'] = ''
|
43
|
+
OptionParser.new do |opts|
|
44
|
+
opts.banner = BANNER
|
45
|
+
opts.on('--certname NAME[,NAME]', Array,
|
46
|
+
'One or more comma separated certnames') do |certs|
|
47
|
+
parsed['certnames'] += certs
|
48
|
+
end
|
49
|
+
opts.on('--help', 'Display this generate specific help output') do |help|
|
50
|
+
parsed['help'] = true
|
51
|
+
end
|
52
|
+
opts.on('--config CONF', 'Path to puppet.conf') do |conf|
|
53
|
+
parsed['config'] = conf
|
54
|
+
end
|
55
|
+
opts.on('--subject-alt-names NAME[,NAME]',
|
56
|
+
'Subject alternative names for the generated cert') do |sans|
|
57
|
+
parsed['subject-alt-names'] = sans
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def parse(args)
|
63
|
+
results = {}
|
64
|
+
parser = self.class.parser(results)
|
65
|
+
|
66
|
+
errors = CliParsing.parse_with_errors(parser, args)
|
67
|
+
|
68
|
+
if results['certnames'].empty?
|
69
|
+
errors << ' At least one certname is required to generate'
|
70
|
+
else
|
71
|
+
results['certnames'].each do |certname|
|
72
|
+
if CERTNAME_BLACKLIST.include?(certname)
|
73
|
+
errors << " Cannot manage cert named `#{certname}` from " +
|
74
|
+
"the CLI, if needed use the HTTP API directly"
|
75
|
+
end
|
76
|
+
|
77
|
+
if certname.match(/\p{Upper}/)
|
78
|
+
errors << " Certificate names must be lower case"
|
79
|
+
end
|
80
|
+
|
81
|
+
unless certname =~ VALID_CERTNAME
|
82
|
+
errors << " Certname #{certname} must not contain unprintable or non-ASCII characters"
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
errors_were_handled = CliParsing.handle_errors(@logger, errors, parser.help)
|
88
|
+
|
89
|
+
exit_code = errors_were_handled ? 1 : nil
|
90
|
+
|
91
|
+
return results, exit_code
|
92
|
+
end
|
93
|
+
|
42
94
|
def run(input)
|
43
|
-
|
95
|
+
certnames = input['certnames']
|
44
96
|
config_path = input['config']
|
97
|
+
|
98
|
+
# Validate config_path provided
|
45
99
|
if config_path
|
46
100
|
errors = FileSystem.validate_file_paths(config_path)
|
47
101
|
return 1 if CliParsing.handle_errors(@logger, errors)
|
@@ -49,130 +103,81 @@ BANNER
|
|
49
103
|
|
50
104
|
# Load, resolve, and validate puppet config settings
|
51
105
|
settings_overrides = {}
|
52
|
-
settings_overrides[:certname] = input['certname'] unless input['certname'].empty?
|
53
|
-
settings_overrides[:ca_name] = input['ca-name'] unless input['ca-name'].empty?
|
54
106
|
# Since puppet expects the key to be called 'dns_alt_names', we need to use that here
|
55
107
|
# to ensure that the overriding works correctly.
|
56
108
|
settings_overrides[:dns_alt_names] = input['subject-alt-names'] unless input['subject-alt-names'].empty?
|
57
|
-
|
58
109
|
puppet = Config::Puppet.new(config_path)
|
59
110
|
puppet.load(settings_overrides)
|
60
111
|
return 1 if CliParsing.handle_errors(@logger, puppet.errors)
|
61
112
|
|
62
|
-
# Load most secure signing digest we can for
|
113
|
+
# Load most secure signing digest we can for csr signing.
|
63
114
|
signer = SigningDigest.new
|
64
115
|
return 1 if CliParsing.handle_errors(@logger, signer.errors)
|
65
116
|
|
66
|
-
# Generate
|
67
|
-
|
68
|
-
|
69
|
-
return 1 if CliParsing.handle_errors(@logger, errors)
|
70
|
-
|
71
|
-
@logger.inform "Generation succeeded. Find your files in #{puppet.settings[:cadir]}"
|
72
|
-
return 0
|
117
|
+
# Generate and save certs and associated keys
|
118
|
+
all_passed = generate_certs(certnames, puppet.settings, signer.digest)
|
119
|
+
return all_passed ? 0 : 1
|
73
120
|
end
|
74
121
|
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
return ca.errors if ca.errors.any?
|
82
|
-
|
122
|
+
# Generate csrs and keys, then submit them to CA, request for the CA to sign
|
123
|
+
# them, download the signed certificates from the CA, and finally save
|
124
|
+
# the signed certs and associated keys. Returns true if all certs were
|
125
|
+
# successfully created and saved.
|
126
|
+
def generate_certs(certnames, settings, digest)
|
127
|
+
# Make sure we have all the directories where we will be writing files
|
83
128
|
FileSystem.ensure_dirs([settings[:ssldir],
|
84
|
-
settings[:cadir],
|
85
129
|
settings[:certdir],
|
86
130
|
settings[:privatekeydir],
|
87
|
-
settings[:publickeydir]
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
[File.join(settings[:signeddir], "#{settings[:certname]}.pem"), master_cert],
|
104
|
-
]
|
105
|
-
|
106
|
-
private_files = [
|
107
|
-
[settings[:hostprivkey], master_key],
|
108
|
-
[settings[:rootkey], root_key],
|
109
|
-
[settings[:cakey], int_key],
|
110
|
-
]
|
111
|
-
|
112
|
-
files_to_check = public_files + private_files
|
113
|
-
# We don't want to error if master's keys exist. Certain workflows
|
114
|
-
# allow the agent to have already be installed with keys and then
|
115
|
-
# upgraded to be a master. The host class will honor keys, if both
|
116
|
-
# public and private exist, and error if only one exists - as is
|
117
|
-
# previous behavior.
|
118
|
-
files_to_check = files_to_check.map(&:first) - [settings[:hostpubkey], settings[:hostprivkey]]
|
119
|
-
errors = FileSystem.check_for_existing_files(files_to_check)
|
120
|
-
|
121
|
-
if !errors.empty?
|
122
|
-
instructions = <<-ERR
|
123
|
-
If you would really like to replace your CA, please delete the existing files first.
|
124
|
-
Note that any certificates that were issued by this CA will become invalid if you
|
125
|
-
replace it!
|
126
|
-
ERR
|
127
|
-
errors << instructions
|
128
|
-
return errors
|
129
|
-
end
|
130
|
-
|
131
|
-
public_files.each do |location, content|
|
132
|
-
FileSystem.write_file(location, content, 0644)
|
131
|
+
settings[:publickeydir]])
|
132
|
+
|
133
|
+
ca = Puppetserver::Ca::CertificateAuthority.new(@logger, settings)
|
134
|
+
|
135
|
+
passed = certnames.map do |certname|
|
136
|
+
key, csr = generate_key_csr(certname, settings, digest)
|
137
|
+
return false unless csr
|
138
|
+
return false unless ca.submit_certificate_request(certname, csr)
|
139
|
+
return false unless ca.sign_certs([certname])
|
140
|
+
if result = ca.get_certificate(certname)
|
141
|
+
save_file(result.body, certname, settings[:certdir], "Certificate")
|
142
|
+
save_keys(certname, settings, key)
|
143
|
+
true
|
144
|
+
else
|
145
|
+
false
|
146
|
+
end
|
133
147
|
end
|
148
|
+
passed.all?
|
149
|
+
end
|
134
150
|
|
135
|
-
|
136
|
-
|
151
|
+
def generate_key_csr(certname, settings, digest)
|
152
|
+
host = Puppetserver::Ca::Host.new(digest)
|
153
|
+
private_key = host.create_private_key(settings[:keylength])
|
154
|
+
extensions = []
|
155
|
+
if !settings[:subject_alt_names].empty?
|
156
|
+
ef = OpenSSL::X509::ExtensionFactory.new
|
157
|
+
extensions << ef.create_extension("subjectAltName",
|
158
|
+
settings[:subject_alt_names],
|
159
|
+
false)
|
137
160
|
end
|
161
|
+
csr = host.create_csr(name: certname,
|
162
|
+
key: private_key,
|
163
|
+
cli_extensions: extensions,
|
164
|
+
csr_attributes_path: settings[:csr_attributes])
|
165
|
+
return if CliParsing.handle_errors(@logger, host.errors)
|
138
166
|
|
139
|
-
return
|
167
|
+
return private_key, csr
|
140
168
|
end
|
141
169
|
|
142
|
-
def
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
errors_were_handled = CliParsing.handle_errors(@logger, errors, parser.help)
|
147
|
-
exit_code = errors_were_handled ? 1 : nil
|
148
|
-
return results, exit_code
|
170
|
+
def save_keys(certname, settings, key)
|
171
|
+
public_key = key.public_key
|
172
|
+
save_file(key, certname, settings[:privatekeydir], "Private key")
|
173
|
+
save_file(public_key, certname, settings[:publickeydir], "Public key")
|
149
174
|
end
|
150
175
|
|
151
|
-
def
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
opts.banner = BANNER
|
157
|
-
opts.on('--help', 'Display this generate specific help output') do |help|
|
158
|
-
parsed['help'] = true
|
159
|
-
end
|
160
|
-
opts.on('--config CONF', 'Path to puppet.conf') do |conf|
|
161
|
-
parsed['config'] = conf
|
162
|
-
end
|
163
|
-
opts.on('--subject-alt-names NAME1[,NAME2]',
|
164
|
-
'Subject alternative names for the master cert') do |sans|
|
165
|
-
parsed['subject-alt-names'] = sans
|
166
|
-
end
|
167
|
-
opts.on('--ca-name NAME',
|
168
|
-
'Common name to use for the CA signing cert') do |name|
|
169
|
-
parsed['ca-name'] = name
|
170
|
-
end
|
171
|
-
opts.on('--certname NAME',
|
172
|
-
'Common name to use for the master cert') do |name|
|
173
|
-
parsed['certname'] = name
|
174
|
-
end
|
175
|
-
end
|
176
|
+
def save_file(content, certname, dir, type)
|
177
|
+
location = File.join(dir, "#{certname}.pem")
|
178
|
+
@logger.warn "#{type} #{certname}.pem already exists, overwriting" if File.exist?(location)
|
179
|
+
FileSystem.write_file(location, content, 0640)
|
180
|
+
@logger.inform "Successfully saved #{type.downcase} for #{certname} to #{location}"
|
176
181
|
end
|
177
182
|
end
|
178
183
|
end
|
@@ -12,12 +12,12 @@ module Puppetserver
|
|
12
12
|
class Import
|
13
13
|
include Puppetserver::Ca::Utils
|
14
14
|
|
15
|
-
SUMMARY = "Import
|
15
|
+
SUMMARY = "Import an external CA chain and generate master PKI"
|
16
16
|
BANNER = <<-BANNER
|
17
17
|
Usage:
|
18
18
|
puppetserver ca import [--help]
|
19
19
|
puppetserver ca import [--config PATH] [--certname NAME]
|
20
|
-
[--subject-alt-names
|
20
|
+
[--subject-alt-names NAME[,NAME]]
|
21
21
|
--private-key PATH --cert-bundle PATH --crl-chain PATH
|
22
22
|
|
23
23
|
Description:
|
@@ -181,7 +181,7 @@ ERR
|
|
181
181
|
'Common name to use for the master cert') do |name|
|
182
182
|
parsed['certname'] = name
|
183
183
|
end
|
184
|
-
opts.on('--subject-alt-names
|
184
|
+
opts.on('--subject-alt-names NAME[,NAME]',
|
185
185
|
'Subject alternative names for the master cert') do |sans|
|
186
186
|
parsed['subject-alt-names'] = sans
|
187
187
|
end
|
@@ -14,11 +14,11 @@ module Puppetserver
|
|
14
14
|
|
15
15
|
CERTNAME_BLACKLIST = %w{--all --config}
|
16
16
|
|
17
|
-
SUMMARY = 'Revoke
|
17
|
+
SUMMARY = 'Revoke certificate(s)'
|
18
18
|
BANNER = <<-BANNER
|
19
19
|
Usage:
|
20
20
|
puppetserver ca revoke [--help]
|
21
|
-
puppetserver ca revoke [--config] --certname
|
21
|
+
puppetserver ca revoke [--config] --certname NAME[,NAME]
|
22
22
|
|
23
23
|
Description:
|
24
24
|
Given one or more valid certnames, instructs the CA to revoke them over
|
@@ -31,11 +31,11 @@ BANNER
|
|
31
31
|
parsed['certnames'] = []
|
32
32
|
OptionParser.new do |o|
|
33
33
|
o.banner = BANNER
|
34
|
-
o.on('--certname
|
34
|
+
o.on('--certname NAME[,NAME]', Array,
|
35
35
|
'One or more comma separated certnames') do |certs|
|
36
36
|
parsed['certnames'] += certs
|
37
37
|
end
|
38
|
-
o.on('--config
|
38
|
+
o.on('--config CONF', 'Custom path to puppet.conf') do |conf|
|
39
39
|
parsed['config'] = conf
|
40
40
|
end
|
41
41
|
o.on('--help', 'Displays this revoke specific help output') do |help|
|
@@ -0,0 +1,181 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
require 'puppetserver/ca/utils/file_system'
|
3
|
+
require 'puppetserver/ca/local_certificate_authority'
|
4
|
+
require 'puppetserver/ca/utils/cli_parsing'
|
5
|
+
require 'puppetserver/ca/utils/signing_digest'
|
6
|
+
require 'puppetserver/ca/config/puppet'
|
7
|
+
|
8
|
+
module Puppetserver
|
9
|
+
module Ca
|
10
|
+
module Action
|
11
|
+
class Setup
|
12
|
+
include Puppetserver::Ca::Utils
|
13
|
+
|
14
|
+
SUMMARY = "Setup a self-signed CA chain for Puppet Server"
|
15
|
+
BANNER = <<-BANNER
|
16
|
+
Usage:
|
17
|
+
puppetserver ca setup [--help]
|
18
|
+
puppetserver ca setup [--config PATH] [--subject-alt-names NAME[,NAME]]
|
19
|
+
[--certname NAME] [--ca-name NAME]
|
20
|
+
|
21
|
+
Description:
|
22
|
+
Setup a root and intermediate signing CA for Puppet Server
|
23
|
+
and store generated CA keys, certs, crls, and associated
|
24
|
+
master related files on disk.
|
25
|
+
|
26
|
+
The `--subject-alt-names` flag can be used to add SANs to the
|
27
|
+
certificate generated for the Puppet master. Multiple names can be
|
28
|
+
listed as a comma separated string. These can be either DNS names or
|
29
|
+
IP addresses, differentiated by prefixes: `DNS:foo.bar.com,IP:123.456.789`.
|
30
|
+
Names with no prefix will be treated as DNS names.
|
31
|
+
|
32
|
+
To determine the target location, the default puppet.conf
|
33
|
+
is consulted for custom values. If using a custom puppet.conf
|
34
|
+
provide it with the --config flag
|
35
|
+
|
36
|
+
Options:
|
37
|
+
BANNER
|
38
|
+
|
39
|
+
def initialize(logger)
|
40
|
+
@logger = logger
|
41
|
+
end
|
42
|
+
|
43
|
+
def run(input)
|
44
|
+
# Validate config_path provided
|
45
|
+
config_path = input['config']
|
46
|
+
if config_path
|
47
|
+
errors = FileSystem.validate_file_paths(config_path)
|
48
|
+
return 1 if CliParsing.handle_errors(@logger, errors)
|
49
|
+
end
|
50
|
+
|
51
|
+
# Load, resolve, and validate puppet config settings
|
52
|
+
settings_overrides = {}
|
53
|
+
settings_overrides[:certname] = input['certname'] unless input['certname'].empty?
|
54
|
+
settings_overrides[:ca_name] = input['ca-name'] unless input['ca-name'].empty?
|
55
|
+
# Since puppet expects the key to be called 'dns_alt_names', we need to use that here
|
56
|
+
# to ensure that the overriding works correctly.
|
57
|
+
settings_overrides[:dns_alt_names] = input['subject-alt-names'] unless input['subject-alt-names'].empty?
|
58
|
+
|
59
|
+
puppet = Config::Puppet.new(config_path)
|
60
|
+
puppet.load(settings_overrides)
|
61
|
+
return 1 if CliParsing.handle_errors(@logger, puppet.errors)
|
62
|
+
|
63
|
+
# Load most secure signing digest we can for cers/crl/csr signing.
|
64
|
+
signer = SigningDigest.new
|
65
|
+
return 1 if CliParsing.handle_errors(@logger, signer.errors)
|
66
|
+
|
67
|
+
# Generate root and intermediate ca and put all the certificates, crls,
|
68
|
+
# and keys where they should go.
|
69
|
+
errors = generate_pki(puppet.settings, signer.digest)
|
70
|
+
return 1 if CliParsing.handle_errors(@logger, errors)
|
71
|
+
|
72
|
+
@logger.inform "Generation succeeded. Find your files in #{puppet.settings[:cadir]}"
|
73
|
+
return 0
|
74
|
+
end
|
75
|
+
|
76
|
+
def generate_pki(settings, signing_digest)
|
77
|
+
ca = Puppetserver::Ca::LocalCertificateAuthority.new(signing_digest, settings)
|
78
|
+
|
79
|
+
root_key, root_cert, root_crl = ca.create_root_cert
|
80
|
+
int_key, int_cert, int_crl = ca.create_intermediate_cert(root_key, root_cert)
|
81
|
+
master_key, master_cert = ca.create_master_cert(int_key, int_cert)
|
82
|
+
return ca.errors if ca.errors.any?
|
83
|
+
|
84
|
+
FileSystem.ensure_dirs([settings[:ssldir],
|
85
|
+
settings[:cadir],
|
86
|
+
settings[:certdir],
|
87
|
+
settings[:privatekeydir],
|
88
|
+
settings[:publickeydir],
|
89
|
+
settings[:signeddir]])
|
90
|
+
|
91
|
+
public_files = [
|
92
|
+
[settings[:cacert], [int_cert, root_cert]],
|
93
|
+
[settings[:cacrl], [int_crl, root_crl]],
|
94
|
+
[settings[:cadir] + '/infra_crl.pem', [int_crl, root_crl]],
|
95
|
+
[settings[:hostcert], master_cert],
|
96
|
+
[settings[:localcacert], [int_cert, root_cert]],
|
97
|
+
[settings[:hostcrl], [int_crl, root_crl]],
|
98
|
+
[settings[:hostpubkey], master_key.public_key],
|
99
|
+
[settings[:capub], int_key.public_key],
|
100
|
+
[settings[:cert_inventory], ca.inventory_entry(master_cert)],
|
101
|
+
[settings[:cadir] + '/infra_inventory.txt', ''],
|
102
|
+
[settings[:cadir] + '/infra_serials', ''],
|
103
|
+
[settings[:serial], "002"],
|
104
|
+
[File.join(settings[:signeddir], "#{settings[:certname]}.pem"), master_cert],
|
105
|
+
]
|
106
|
+
|
107
|
+
private_files = [
|
108
|
+
[settings[:hostprivkey], master_key],
|
109
|
+
[settings[:rootkey], root_key],
|
110
|
+
[settings[:cakey], int_key],
|
111
|
+
]
|
112
|
+
|
113
|
+
files_to_check = public_files + private_files
|
114
|
+
# We don't want to error if master's keys exist. Certain workflows
|
115
|
+
# allow the agent to have already be installed with keys and then
|
116
|
+
# upgraded to be a master. The host class will honor keys, if both
|
117
|
+
# public and private exist, and error if only one exists - as is
|
118
|
+
# previous behavior.
|
119
|
+
files_to_check = files_to_check.map(&:first) - [settings[:hostpubkey], settings[:hostprivkey]]
|
120
|
+
errors = FileSystem.check_for_existing_files(files_to_check)
|
121
|
+
|
122
|
+
if !errors.empty?
|
123
|
+
instructions = <<-ERR
|
124
|
+
If you would really like to replace your CA, please delete the existing files first.
|
125
|
+
Note that any certificates that were issued by this CA will become invalid if you
|
126
|
+
replace it!
|
127
|
+
ERR
|
128
|
+
errors << instructions
|
129
|
+
return errors
|
130
|
+
end
|
131
|
+
|
132
|
+
public_files.each do |location, content|
|
133
|
+
FileSystem.write_file(location, content, 0644)
|
134
|
+
end
|
135
|
+
|
136
|
+
private_files.each do |location, content|
|
137
|
+
FileSystem.write_file(location, content, 0640)
|
138
|
+
end
|
139
|
+
|
140
|
+
return []
|
141
|
+
end
|
142
|
+
|
143
|
+
def parse(cli_args)
|
144
|
+
results = {}
|
145
|
+
parser = self.class.parser(results)
|
146
|
+
errors = CliParsing.parse_with_errors(parser, cli_args)
|
147
|
+
errors_were_handled = CliParsing.handle_errors(@logger, errors, parser.help)
|
148
|
+
exit_code = errors_were_handled ? 1 : nil
|
149
|
+
return results, exit_code
|
150
|
+
end
|
151
|
+
|
152
|
+
def self.parser(parsed = {})
|
153
|
+
parsed['subject-alt-names'] = ''
|
154
|
+
parsed['ca-name'] = ''
|
155
|
+
parsed['certname'] = ''
|
156
|
+
OptionParser.new do |opts|
|
157
|
+
opts.banner = BANNER
|
158
|
+
opts.on('--help', 'Display this setup specific help output') do |help|
|
159
|
+
parsed['help'] = true
|
160
|
+
end
|
161
|
+
opts.on('--config CONF', 'Path to puppet.conf') do |conf|
|
162
|
+
parsed['config'] = conf
|
163
|
+
end
|
164
|
+
opts.on('--subject-alt-names NAME[,NAME]',
|
165
|
+
'Subject alternative names for the master cert') do |sans|
|
166
|
+
parsed['subject-alt-names'] = sans
|
167
|
+
end
|
168
|
+
opts.on('--ca-name NAME',
|
169
|
+
'Common name to use for the CA signing cert') do |name|
|
170
|
+
parsed['ca-name'] = name
|
171
|
+
end
|
172
|
+
opts.on('--certname NAME',
|
173
|
+
'Common name to use for the master cert') do |name|
|
174
|
+
parsed['certname'] = name
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
@@ -14,11 +14,11 @@ module Puppetserver
|
|
14
14
|
|
15
15
|
include Puppetserver::Ca::Utils
|
16
16
|
|
17
|
-
SUMMARY = 'Sign
|
17
|
+
SUMMARY = 'Sign certificate request(s)'
|
18
18
|
BANNER = <<-BANNER
|
19
19
|
Usage:
|
20
20
|
puppetserver ca sign [--help]
|
21
|
-
puppetserver ca sign [--config] --certname
|
21
|
+
puppetserver ca sign [--config] --certname NAME[,NAME]
|
22
22
|
puppetserver ca sign --all
|
23
23
|
|
24
24
|
Description:
|
@@ -30,10 +30,10 @@ Options:
|
|
30
30
|
def self.parser(parsed = {})
|
31
31
|
OptionParser.new do |opts|
|
32
32
|
opts.banner = BANNER
|
33
|
-
opts.on('--certname
|
33
|
+
opts.on('--certname NAME[,NAME]', Array, 'the name(s) of the cert(s) to be signed') do |cert|
|
34
34
|
parsed['certname'] = cert
|
35
35
|
end
|
36
|
-
opts.on('--config
|
36
|
+
opts.on('--config CONF', 'Custom path to Puppet\'s config file') do |conf|
|
37
37
|
parsed['config'] = conf
|
38
38
|
end
|
39
39
|
opts.on('--help', 'Display this command specific help output') do |help|
|
data/lib/puppetserver/ca/cli.rb
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
require 'optparse'
|
2
|
-
require 'puppetserver/ca/version'
|
3
|
-
require 'puppetserver/ca/logger'
|
4
2
|
require 'puppetserver/ca/action/clean'
|
5
|
-
require 'puppetserver/ca/action/create'
|
6
|
-
require 'puppetserver/ca/action/import'
|
7
3
|
require 'puppetserver/ca/action/generate'
|
8
|
-
require 'puppetserver/ca/action/
|
4
|
+
require 'puppetserver/ca/action/import'
|
9
5
|
require 'puppetserver/ca/action/list'
|
6
|
+
require 'puppetserver/ca/action/revoke'
|
7
|
+
require 'puppetserver/ca/action/setup'
|
10
8
|
require 'puppetserver/ca/action/sign'
|
9
|
+
require 'puppetserver/ca/logger'
|
10
|
+
require 'puppetserver/ca/version'
|
11
11
|
require 'puppetserver/ca/utils/cli_parsing'
|
12
12
|
|
13
13
|
|
@@ -21,18 +21,28 @@ Manage the Private Key Infrastructure for
|
|
21
21
|
Puppet Server's built-in Certificate Authority
|
22
22
|
BANNER
|
23
23
|
|
24
|
-
|
24
|
+
INIT_ACTIONS = {
|
25
|
+
'import' => Action::Import,
|
26
|
+
'setup' => Action::Setup,
|
27
|
+
}
|
28
|
+
|
29
|
+
MAINT_ACTIONS = {
|
25
30
|
'clean' => Action::Clean,
|
26
|
-
'create' => Action::Create,
|
27
31
|
'generate' => Action::Generate,
|
28
|
-
'import' => Action::Import,
|
29
32
|
'list' => Action::List,
|
30
33
|
'revoke' => Action::Revoke,
|
31
34
|
'sign' => Action::Sign
|
32
35
|
}
|
33
36
|
|
34
|
-
|
35
|
-
|
37
|
+
VALID_ACTIONS = INIT_ACTIONS.merge(MAINT_ACTIONS).sort.to_h
|
38
|
+
|
39
|
+
ACTION_LIST = "\nAvailable Actions:\n\n" +
|
40
|
+
" Certificate Actions (requires a running Puppet Server):\n\n" +
|
41
|
+
MAINT_ACTIONS.map do |action, cls|
|
42
|
+
" #{action}\t#{cls::SUMMARY}"
|
43
|
+
end.join("\n") + "\n\n" +
|
44
|
+
" Initialization Actions (requires Puppet Server to be stopped):\n\n" +
|
45
|
+
INIT_ACTIONS.map do |action, cls|
|
36
46
|
" #{action}\t#{cls::SUMMARY}"
|
37
47
|
end.join("\n")
|
38
48
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: puppetserver-ca
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.7.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Puppet, Inc.
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-09-
|
11
|
+
date: 2018-09-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: facter
|
@@ -93,11 +93,11 @@ files:
|
|
93
93
|
- exe/puppetserver-ca
|
94
94
|
- lib/puppetserver/ca.rb
|
95
95
|
- lib/puppetserver/ca/action/clean.rb
|
96
|
-
- lib/puppetserver/ca/action/create.rb
|
97
96
|
- lib/puppetserver/ca/action/generate.rb
|
98
97
|
- lib/puppetserver/ca/action/import.rb
|
99
98
|
- lib/puppetserver/ca/action/list.rb
|
100
99
|
- lib/puppetserver/ca/action/revoke.rb
|
100
|
+
- lib/puppetserver/ca/action/setup.rb
|
101
101
|
- lib/puppetserver/ca/action/sign.rb
|
102
102
|
- lib/puppetserver/ca/certificate_authority.rb
|
103
103
|
- lib/puppetserver/ca/cli.rb
|
@@ -1,185 +0,0 @@
|
|
1
|
-
require 'puppetserver/ca/utils/cli_parsing'
|
2
|
-
require 'puppetserver/ca/host'
|
3
|
-
require 'puppetserver/ca/certificate_authority'
|
4
|
-
require 'puppetserver/ca/config/puppet'
|
5
|
-
require 'puppetserver/ca/utils/file_system'
|
6
|
-
require 'puppetserver/ca/utils/signing_digest'
|
7
|
-
|
8
|
-
module Puppetserver
|
9
|
-
module Ca
|
10
|
-
module Action
|
11
|
-
class Create
|
12
|
-
|
13
|
-
include Puppetserver::Ca::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 PATH] [--certname CERTNAME[,ADDLCERTNAME]]
|
24
|
-
[--subject-alt-names ALTNAME1[,ALTNAME2...]]
|
25
|
-
|
26
|
-
Description:
|
27
|
-
Creates a new certificate signed by the intermediate CA
|
28
|
-
and stores generated keys and certs on disk.
|
29
|
-
|
30
|
-
To determine the target location, the default puppet.conf
|
31
|
-
is consulted for custom values. If using a custom puppet.conf
|
32
|
-
provide it with the --config flag
|
33
|
-
|
34
|
-
Options:
|
35
|
-
BANNER
|
36
|
-
def initialize(logger)
|
37
|
-
@logger = logger
|
38
|
-
end
|
39
|
-
|
40
|
-
def self.parser(parsed = {})
|
41
|
-
parsed['certnames'] = []
|
42
|
-
parsed['subject-alt-names'] = ''
|
43
|
-
OptionParser.new do |opts|
|
44
|
-
opts.banner = BANNER
|
45
|
-
opts.on('--certname FOO,BAR', Array,
|
46
|
-
'One or more comma separated certnames') do |certs|
|
47
|
-
parsed['certnames'] += certs
|
48
|
-
end
|
49
|
-
opts.on('--help', 'Display this create specific help output') do |help|
|
50
|
-
parsed['help'] = true
|
51
|
-
end
|
52
|
-
opts.on('--config CONF', 'Path to puppet.conf') do |conf|
|
53
|
-
parsed['config'] = conf
|
54
|
-
end
|
55
|
-
opts.on('--subject-alt-names NAME1[,NAME2]',
|
56
|
-
'Subject alternative names for the generated cert') do |sans|
|
57
|
-
parsed['subject-alt-names'] = sans
|
58
|
-
end
|
59
|
-
end
|
60
|
-
end
|
61
|
-
|
62
|
-
def parse(args)
|
63
|
-
results = {}
|
64
|
-
parser = self.class.parser(results)
|
65
|
-
|
66
|
-
errors = CliParsing.parse_with_errors(parser, args)
|
67
|
-
|
68
|
-
if results['certnames'].empty?
|
69
|
-
errors << ' At least one certname is required to create'
|
70
|
-
else
|
71
|
-
results['certnames'].each do |certname|
|
72
|
-
if CERTNAME_BLACKLIST.include?(certname)
|
73
|
-
errors << " Cannot manage cert named `#{certname}` from " +
|
74
|
-
"the CLI, if needed use the HTTP API directly"
|
75
|
-
end
|
76
|
-
|
77
|
-
if certname.match(/\p{Upper}/)
|
78
|
-
errors << " Certificate names must be lower case"
|
79
|
-
end
|
80
|
-
|
81
|
-
unless certname =~ VALID_CERTNAME
|
82
|
-
errors << " Certname #{certname} must not contain unprintable or non-ASCII characters"
|
83
|
-
end
|
84
|
-
end
|
85
|
-
end
|
86
|
-
|
87
|
-
errors_were_handled = CliParsing.handle_errors(@logger, errors, parser.help)
|
88
|
-
|
89
|
-
exit_code = errors_were_handled ? 1 : nil
|
90
|
-
|
91
|
-
return results, exit_code
|
92
|
-
end
|
93
|
-
|
94
|
-
def run(input)
|
95
|
-
certnames = input['certnames']
|
96
|
-
config_path = input['config']
|
97
|
-
|
98
|
-
# Validate config_path provided
|
99
|
-
if config_path
|
100
|
-
errors = FileSystem.validate_file_paths(config_path)
|
101
|
-
return 1 if CliParsing.handle_errors(@logger, errors)
|
102
|
-
end
|
103
|
-
|
104
|
-
# Load, resolve, and validate puppet config settings
|
105
|
-
settings_overrides = {}
|
106
|
-
# Since puppet expects the key to be called 'dns_alt_names', we need to use that here
|
107
|
-
# to ensure that the overriding works correctly.
|
108
|
-
settings_overrides[:dns_alt_names] = input['subject-alt-names'] unless input['subject-alt-names'].empty?
|
109
|
-
puppet = Config::Puppet.new(config_path)
|
110
|
-
puppet.load(settings_overrides)
|
111
|
-
return 1 if CliParsing.handle_errors(@logger, puppet.errors)
|
112
|
-
|
113
|
-
# Load most secure signing digest we can for csr signing.
|
114
|
-
signer = SigningDigest.new
|
115
|
-
return 1 if CliParsing.handle_errors(@logger, signer.errors)
|
116
|
-
|
117
|
-
# Generate and save certs and associated keys
|
118
|
-
all_passed = generate_certs(certnames, puppet.settings, signer.digest)
|
119
|
-
return all_passed ? 0 : 1
|
120
|
-
end
|
121
|
-
|
122
|
-
# Create csrs and keys, then submit them to CA, request for the CA to sign
|
123
|
-
# them, download the signed certificates from the CA, and finally save
|
124
|
-
# the signed certs and associated keys. Returns true if all certs were
|
125
|
-
# successfully created and saved.
|
126
|
-
def generate_certs(certnames, settings, digest)
|
127
|
-
# Make sure we have all the directories where we will be writing files
|
128
|
-
FileSystem.ensure_dirs([settings[:ssldir],
|
129
|
-
settings[:certdir],
|
130
|
-
settings[:privatekeydir],
|
131
|
-
settings[:publickeydir]])
|
132
|
-
|
133
|
-
ca = Puppetserver::Ca::CertificateAuthority.new(@logger, settings)
|
134
|
-
|
135
|
-
passed = certnames.map do |certname|
|
136
|
-
key, csr = generate_key_csr(certname, settings, digest)
|
137
|
-
return false unless csr
|
138
|
-
return false unless ca.submit_certificate_request(certname, csr)
|
139
|
-
return false unless ca.sign_certs([certname])
|
140
|
-
if result = ca.get_certificate(certname)
|
141
|
-
save_file(result.body, certname, settings[:certdir], "Certificate")
|
142
|
-
save_keys(certname, settings, key)
|
143
|
-
true
|
144
|
-
else
|
145
|
-
false
|
146
|
-
end
|
147
|
-
end
|
148
|
-
passed.all?
|
149
|
-
end
|
150
|
-
|
151
|
-
def generate_key_csr(certname, settings, digest)
|
152
|
-
host = Puppetserver::Ca::Host.new(digest)
|
153
|
-
private_key = host.create_private_key(settings[:keylength])
|
154
|
-
extensions = []
|
155
|
-
if !settings[:subject_alt_names].empty?
|
156
|
-
ef = OpenSSL::X509::ExtensionFactory.new
|
157
|
-
extensions << ef.create_extension("subjectAltName",
|
158
|
-
settings[:subject_alt_names],
|
159
|
-
false)
|
160
|
-
end
|
161
|
-
csr = host.create_csr(name: certname,
|
162
|
-
key: private_key,
|
163
|
-
cli_extensions: extensions,
|
164
|
-
csr_attributes_path: settings[:csr_attributes])
|
165
|
-
return if CliParsing.handle_errors(@logger, host.errors)
|
166
|
-
|
167
|
-
return private_key, csr
|
168
|
-
end
|
169
|
-
|
170
|
-
def save_keys(certname, settings, key)
|
171
|
-
public_key = key.public_key
|
172
|
-
save_file(key, certname, settings[:privatekeydir], "Private key")
|
173
|
-
save_file(public_key, certname, settings[:publickeydir], "Public key")
|
174
|
-
end
|
175
|
-
|
176
|
-
def save_file(content, certname, dir, type)
|
177
|
-
location = File.join(dir, "#{certname}.pem")
|
178
|
-
@logger.warn "#{type} #{certname}.pem already exists, overwriting" if File.exist?(location)
|
179
|
-
FileSystem.write_file(location, content, 0640)
|
180
|
-
@logger.inform "Successfully saved #{type.downcase} for #{certname} to #{location}"
|
181
|
-
end
|
182
|
-
end
|
183
|
-
end
|
184
|
-
end
|
185
|
-
end
|