puppetserver-ca 1.9.3 → 1.11.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/README.md +15 -5
- data/lib/puppetserver/ca/action/clean.rb +2 -2
- data/lib/puppetserver/ca/action/generate.rb +3 -3
- data/lib/puppetserver/ca/action/import.rb +11 -11
- data/lib/puppetserver/ca/action/list.rb +68 -14
- data/lib/puppetserver/ca/action/prune.rb +116 -0
- data/lib/puppetserver/ca/action/revoke.rb +2 -2
- data/lib/puppetserver/ca/action/setup.rb +12 -12
- data/lib/puppetserver/ca/certificate_authority.rb +1 -1
- data/lib/puppetserver/ca/cli.rb +14 -7
- data/lib/puppetserver/ca/config/puppet.rb +25 -4
- data/lib/puppetserver/ca/host.rb +2 -2
- data/lib/puppetserver/ca/local_certificate_authority.rb +9 -9
- data/lib/puppetserver/ca/logger.rb +9 -1
- data/lib/puppetserver/ca/utils/config.rb +19 -0
- data/lib/puppetserver/ca/utils/http_client.rb +15 -6
- data/lib/puppetserver/ca/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 813b9f6f9913cd98abd4b81219cfb4882e7c9c1ace77f024218706be07411d7a
|
4
|
+
data.tar.gz: 8f7bd49d04fc6f23f4674006f1d159756286f9b3ae50a686cb4faa5b7b71832d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a2dbfac65155e9dbd593931305b9f99079f2610bd02d64c20e1be00f8f61058f85bfcf410f5987439de892b6adf9e46beadb60d85a4f970c07a4eec2643c54d9
|
7
|
+
data.tar.gz: 26964af97f64152d3d5e1bfbcc931daa102734b1facf2ef588890552aee36791be3a6091e5e5563d328254149133035597773486c53e67ddd8e1624327f289bb
|
data/README.md
CHANGED
@@ -55,20 +55,30 @@ To create a new keypair and certificate for a certname:
|
|
55
55
|
puppetserver ca generate --certname foo.example.com
|
56
56
|
```
|
57
57
|
|
58
|
+
To remove duplicated entries from Puppet's CRL:
|
59
|
+
```
|
60
|
+
puppetserver ca prune
|
61
|
+
```
|
62
|
+
|
63
|
+
To enable verbose mode:
|
64
|
+
```
|
65
|
+
puppetserver ca --verbose <action>
|
66
|
+
```
|
67
|
+
|
58
68
|
For more details, see the help output:
|
59
69
|
```
|
60
70
|
puppetserver ca --help
|
61
71
|
```
|
62
72
|
|
63
73
|
This code in this project is licensed under the Apache Software License v2,
|
64
|
-
please see the included [License](https://github.com/puppetlabs/puppetserver-ca-cli/blob/
|
74
|
+
please see the included [License](https://github.com/puppetlabs/puppetserver-ca-cli/blob/main/LICENSE.md)
|
65
75
|
for more details.
|
66
76
|
|
67
77
|
|
68
78
|
## Development
|
69
79
|
|
70
80
|
After checking out the repo, run `bin/setup` to install dependencies. Then,
|
71
|
-
run `rake spec` to run the tests. You can also run `bin/console` for an
|
81
|
+
run `bundle exec rake spec` to run the tests. You can also run `bin/console` for an
|
72
82
|
interactive prompt that will allow you to experiment.
|
73
83
|
|
74
84
|
To install this gem onto your local machine, run `bundle exec rake install`.
|
@@ -92,7 +102,7 @@ To test your changes on a VM:
|
|
92
102
|
1. To confirm that installation was successful, run `puppetserver ca --help`
|
93
103
|
|
94
104
|
### Releasing
|
95
|
-
To release a new version, run the [release pipeline](https://jenkins-
|
105
|
+
To release a new version, run the [release pipeline](https://jenkins-platform.delivery.puppetlabs.net/job/platform_puppetserver-ca_init-multijob_1.x/), which will bump the version, tag, build, and release the gem.
|
96
106
|
|
97
107
|
|
98
108
|
## Contributing & Support
|
@@ -105,9 +115,9 @@ Freenode, or the Puppet Community Slack channel.
|
|
105
115
|
|
106
116
|
Contributions are welcome at https://github.com/puppetlabs/puppetserver-ca-cli/pulls.
|
107
117
|
Contributors should both be sure to read the
|
108
|
-
[contributing document](https://github.com/puppetlabs/puppetserver-ca-cli/blob/
|
118
|
+
[contributing document](https://github.com/puppetlabs/puppetserver-ca-cli/blob/main/CONTRIBUTING.md)
|
109
119
|
and sign the [contributor license agreement](https://cla.puppet.com/).
|
110
120
|
|
111
121
|
Everyone interacting with the project’s codebase, issue tracker, etc is expected
|
112
122
|
to follow the
|
113
|
-
[code of conduct](https://github.com/puppetlabs/puppetserver-ca-cli/blob/
|
123
|
+
[code of conduct](https://github.com/puppetlabs/puppetserver-ca-cli/blob/main/CODE_OF_CONDUCT.md).
|
@@ -14,7 +14,7 @@ module Puppetserver
|
|
14
14
|
|
15
15
|
include Puppetserver::Ca::Utils
|
16
16
|
|
17
|
-
|
17
|
+
CERTNAME_BLOCKLIST = %w{--all --config}
|
18
18
|
|
19
19
|
SUMMARY = 'Revoke cert(s) and remove related files from CA'
|
20
20
|
BANNER = <<-BANNER
|
@@ -59,7 +59,7 @@ BANNER
|
|
59
59
|
errors = CliParsing.parse_with_errors(parser, args)
|
60
60
|
|
61
61
|
results['certnames'].each do |certname|
|
62
|
-
if
|
62
|
+
if CERTNAME_BLOCKLIST.include?(certname)
|
63
63
|
errors << " Cannot manage cert named `#{certname}` from " +
|
64
64
|
"the CLI, if needed use the HTTP API directly"
|
65
65
|
end
|
@@ -18,7 +18,7 @@ module Puppetserver
|
|
18
18
|
|
19
19
|
# Only allow printing ascii characters, excluding /
|
20
20
|
VALID_CERTNAME = /\A[ -.0-~]+\Z/
|
21
|
-
|
21
|
+
CERTNAME_BLOCKLIST = %w{--all --config}
|
22
22
|
|
23
23
|
SUMMARY = "Generate a new certificate signed by the CA"
|
24
24
|
BANNER = <<-BANNER
|
@@ -35,7 +35,7 @@ Description:
|
|
35
35
|
If the `--ca-client` flag is passed, the cert will be generated
|
36
36
|
offline, without using Puppet Server's signing code, and will add
|
37
37
|
a special extension authorizing it to talk to the CA API. This can
|
38
|
-
be used for regenerating the
|
38
|
+
be used for regenerating the server's host cert, or for manually
|
39
39
|
setting up other nodes to be CA clients. Do not distribute certs
|
40
40
|
generated this way to any node that you do not intend to have
|
41
41
|
administrative access to the CA (e.g. the ability to sign a cert).
|
@@ -91,7 +91,7 @@ BANNER
|
|
91
91
|
errors << ' At least one certname is required to generate'
|
92
92
|
else
|
93
93
|
results['certnames'].each do |certname|
|
94
|
-
if
|
94
|
+
if CERTNAME_BLOCKLIST.include?(certname)
|
95
95
|
errors << " Cannot manage cert named `#{certname}` from " +
|
96
96
|
"the CLI, if needed use the HTTP API directly"
|
97
97
|
end
|
@@ -14,7 +14,7 @@ module Puppetserver
|
|
14
14
|
class Import
|
15
15
|
include Puppetserver::Ca::Utils
|
16
16
|
|
17
|
-
SUMMARY = "Import an external CA chain and generate
|
17
|
+
SUMMARY = "Import an external CA chain and generate server PKI"
|
18
18
|
BANNER = <<-BANNER
|
19
19
|
Usage:
|
20
20
|
puppetserver ca import [--help]
|
@@ -72,7 +72,7 @@ BANNER
|
|
72
72
|
def import(loader, settings, signing_digest)
|
73
73
|
ca = Puppetserver::Ca::LocalCertificateAuthority.new(signing_digest, settings)
|
74
74
|
ca.initialize_ssl_components(loader)
|
75
|
-
|
75
|
+
server_key, server_cert = ca.create_server_cert
|
76
76
|
return ca.errors if ca.errors.any?
|
77
77
|
|
78
78
|
FileSystem.ensure_dirs([settings[:ssldir],
|
@@ -88,25 +88,25 @@ BANNER
|
|
88
88
|
[settings[:cadir] + '/infra_crl.pem', loader.crls],
|
89
89
|
[settings[:localcacert], loader.certs],
|
90
90
|
[settings[:hostcrl], loader.crls],
|
91
|
-
[settings[:hostpubkey],
|
92
|
-
[settings[:hostcert],
|
93
|
-
[settings[:cert_inventory], ca.inventory_entry(
|
91
|
+
[settings[:hostpubkey], server_key.public_key],
|
92
|
+
[settings[:hostcert], server_cert],
|
93
|
+
[settings[:cert_inventory], ca.inventory_entry(server_cert)],
|
94
94
|
[settings[:capub], loader.key.public_key],
|
95
95
|
[settings[:cadir] + '/infra_inventory.txt', ''],
|
96
96
|
[settings[:cadir] + '/infra_serials', ''],
|
97
97
|
[settings[:serial], "002"],
|
98
|
-
[File.join(settings[:signeddir], "#{settings[:certname]}.pem"),
|
98
|
+
[File.join(settings[:signeddir], "#{settings[:certname]}.pem"), server_cert]
|
99
99
|
]
|
100
100
|
|
101
101
|
private_files = [
|
102
|
-
[settings[:hostprivkey],
|
102
|
+
[settings[:hostprivkey], server_key],
|
103
103
|
[settings[:cakey], loader.key],
|
104
104
|
]
|
105
105
|
|
106
106
|
files_to_check = public_files + private_files
|
107
|
-
# We don't want to error if
|
107
|
+
# We don't want to error if server's keys exist. Certain workflows
|
108
108
|
# allow the agent to have already be installed with keys and then
|
109
|
-
# upgraded to be a
|
109
|
+
# upgraded to be a server. The host class will honor keys, if both
|
110
110
|
# public and private exist, and error if only one exists - as is
|
111
111
|
# previous behavior.
|
112
112
|
files_to_check = files_to_check.map(&:first) - [settings[:hostpubkey], settings[:hostprivkey]]
|
@@ -178,11 +178,11 @@ ERR
|
|
178
178
|
parsed['crl-chain'] = chain
|
179
179
|
end
|
180
180
|
opts.on('--certname NAME',
|
181
|
-
'Common name to use for the
|
181
|
+
'Common name to use for the server cert') do |name|
|
182
182
|
parsed['certname'] = name
|
183
183
|
end
|
184
184
|
opts.on('--subject-alt-names NAME[,NAME]',
|
185
|
-
'Subject alternative names for the
|
185
|
+
'Subject alternative names for the server cert') do |sans|
|
186
186
|
parsed['subject-alt-names'] = sans
|
187
187
|
end
|
188
188
|
end
|
@@ -30,6 +30,7 @@ Options:
|
|
30
30
|
BANNER
|
31
31
|
|
32
32
|
BODY = JSON.dump({desired_state: 'signed'})
|
33
|
+
VALID_FORMAT = ['text', 'json']
|
33
34
|
|
34
35
|
def initialize(logger)
|
35
36
|
@logger = logger
|
@@ -47,6 +48,9 @@ Options:
|
|
47
48
|
opts.on('--all', 'List all certificates') do |a|
|
48
49
|
parsed['all'] = true
|
49
50
|
end
|
51
|
+
opts.on('--format FORMAT', "Valid formats are: 'text' (default), 'json'") do |f|
|
52
|
+
parsed['format'] = f
|
53
|
+
end
|
50
54
|
opts.on('--certname NAME[,NAME]', Array, 'List the specified cert(s)') do |cert|
|
51
55
|
parsed['certname'] = cert
|
52
56
|
end
|
@@ -57,9 +61,15 @@ Options:
|
|
57
61
|
config = input['config']
|
58
62
|
certnames = input['certname'] || []
|
59
63
|
all = input['all']
|
64
|
+
output_format = input['format'] || "text"
|
65
|
+
|
66
|
+
unless VALID_FORMAT.include?(output_format)
|
67
|
+
Errors.handle_with_usage(@logger, ["Unknown format flag '#{output_format}'. Valid formats are '#{VALID_FORMAT.join("', '")}'."])
|
68
|
+
return 1
|
69
|
+
end
|
60
70
|
|
61
71
|
if all && certnames.any?
|
62
|
-
Errors.handle_with_usage(@logger, ['Cannot combine use of --all and --certname'])
|
72
|
+
Errors.handle_with_usage(@logger, ['Cannot combine use of --all and --certname.'])
|
63
73
|
return 1
|
64
74
|
end
|
65
75
|
|
@@ -71,24 +81,60 @@ Options:
|
|
71
81
|
puppet = Config::Puppet.parse(config)
|
72
82
|
return 1 if Errors.handle_with_usage(@logger, puppet.errors)
|
73
83
|
|
74
|
-
|
75
|
-
|
76
|
-
|
84
|
+
if certnames.any?
|
85
|
+
filter_names = lambda { |x| certnames.include?(x['name']) }
|
86
|
+
else
|
87
|
+
filter_names = lambda { |x| true }
|
88
|
+
end
|
77
89
|
|
78
90
|
all_certs = get_all_certs(puppet.settings).select { |cert| filter_names.call(cert) }
|
79
91
|
requested, signed, revoked = separate_certs(all_certs)
|
80
92
|
missing = certnames - all_certs.map { |cert| cert['name'] }
|
81
93
|
|
82
|
-
(all || certnames.any?)
|
83
|
-
|
84
|
-
|
94
|
+
if (all || certnames.any?)
|
95
|
+
output_certs_by_state(all, output_format, requested, signed, revoked, missing)
|
96
|
+
else
|
97
|
+
output_certs_by_state(all, output_format, requested)
|
98
|
+
end
|
99
|
+
|
100
|
+
return missing.any? ? 1 : 0
|
101
|
+
end
|
85
102
|
|
86
|
-
|
87
|
-
|
88
|
-
|
103
|
+
def output_certs_by_state(all, output_format, requested, signed = [], revoked = [], missing = [])
|
104
|
+
if output_format == 'json'
|
105
|
+
output_certs_json_format(all, requested, signed, revoked, missing)
|
106
|
+
else
|
107
|
+
output_certs_text_format(requested, signed, revoked, missing)
|
108
|
+
end
|
89
109
|
end
|
90
110
|
|
91
|
-
def
|
111
|
+
def output_certs_json_format(all, requested, signed, revoked, missing)
|
112
|
+
grouped_cert = {}
|
113
|
+
|
114
|
+
if all
|
115
|
+
grouped_cert = { "requested" => requested,
|
116
|
+
"signed" => signed,
|
117
|
+
"revoked" => revoked }.to_json
|
118
|
+
@logger.inform(grouped_cert)
|
119
|
+
else
|
120
|
+
grouped_cert["requested"] = requested unless requested.empty?
|
121
|
+
grouped_cert["signed"] = signed unless signed.empty?
|
122
|
+
grouped_cert["revoked"] = revoked unless revoked.empty?
|
123
|
+
grouped_cert["missing"] = missing unless missing.empty?
|
124
|
+
|
125
|
+
# If neither the '--all' flag or the '--certname' flag was passed in
|
126
|
+
# and the requested cert array is empty, we output a JSON object
|
127
|
+
# with an empty 'requested' key. Otherwise, we display
|
128
|
+
# any of the classes that are currently in grouped_cert
|
129
|
+
if grouped_cert.empty?
|
130
|
+
@logger.inform({ "requested" => requested }.to_json)
|
131
|
+
else
|
132
|
+
@logger.inform(grouped_cert.to_json)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
def output_certs_text_format(requested, signed, revoked, missing)
|
92
138
|
if revoked.empty? && signed.empty? && requested.empty? && missing.empty?
|
93
139
|
@logger.inform "No certificates to list"
|
94
140
|
return
|
@@ -165,7 +211,12 @@ Options:
|
|
165
211
|
|
166
212
|
def get_all_certs(settings)
|
167
213
|
result = Puppetserver::Ca::CertificateAuthority.new(@logger, settings).get_certificate_statuses
|
168
|
-
|
214
|
+
|
215
|
+
if result
|
216
|
+
return JSON.parse(result.body)
|
217
|
+
else
|
218
|
+
return []
|
219
|
+
end
|
169
220
|
end
|
170
221
|
|
171
222
|
def parse(args)
|
@@ -176,8 +227,11 @@ Options:
|
|
176
227
|
|
177
228
|
errors_were_handled = Errors.handle_with_usage(@logger, errors, parser.help)
|
178
229
|
|
179
|
-
|
180
|
-
|
230
|
+
if errors_were_handled
|
231
|
+
exit_code = 1
|
232
|
+
else
|
233
|
+
exit_code = nil
|
234
|
+
end
|
181
235
|
return results, exit_code
|
182
236
|
end
|
183
237
|
end
|
@@ -0,0 +1,116 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
require 'openssl'
|
3
|
+
require 'puppetserver/ca/errors'
|
4
|
+
require 'puppetserver/ca/utils/cli_parsing'
|
5
|
+
require 'puppetserver/ca/utils/file_system'
|
6
|
+
require 'puppetserver/ca/utils/config'
|
7
|
+
require 'puppetserver/ca/x509_loader'
|
8
|
+
|
9
|
+
module Puppetserver
|
10
|
+
module Ca
|
11
|
+
module Action
|
12
|
+
class Prune
|
13
|
+
include Puppetserver::Ca::Utils
|
14
|
+
|
15
|
+
SUMMARY = "Prune the local CRL on disk to remove any duplicated certificates"
|
16
|
+
BANNER = <<-BANNER
|
17
|
+
Usage:
|
18
|
+
puppetserver ca prune [--help]
|
19
|
+
puppetserver ca prune [--config]
|
20
|
+
|
21
|
+
Description:
|
22
|
+
Prune the list of revoked certificates of any duplication within it. This command
|
23
|
+
will only prune the CRL issued by Puppet's CA cert.
|
24
|
+
|
25
|
+
Options:
|
26
|
+
BANNER
|
27
|
+
|
28
|
+
def initialize(logger)
|
29
|
+
@logger = logger
|
30
|
+
end
|
31
|
+
|
32
|
+
def run(inputs)
|
33
|
+
config_path = inputs['config']
|
34
|
+
|
35
|
+
# Validate the config path.
|
36
|
+
if config_path
|
37
|
+
errors = FileSystem.validate_file_paths(config_path)
|
38
|
+
return 1 if Errors.handle_with_usage(@logger, errors)
|
39
|
+
end
|
40
|
+
|
41
|
+
# Validate puppet config setting.
|
42
|
+
puppet = Config::Puppet.new(config_path)
|
43
|
+
puppet.load(logger: @logger)
|
44
|
+
return 1 if Errors.handle_with_usage(@logger, puppet.errors)
|
45
|
+
|
46
|
+
# Validate that we are offline
|
47
|
+
return 1 if HttpClient.check_server_online(puppet.settings, @logger)
|
48
|
+
|
49
|
+
# Getting the CRL(s)
|
50
|
+
loader = X509Loader.new(puppet.settings[:cacert], puppet.settings[:cakey], puppet.settings[:cacrl])
|
51
|
+
|
52
|
+
puppet_crl = loader.crls.select { |crl| crl.verify(loader.key) }
|
53
|
+
prune_CRLs(puppet_crl)
|
54
|
+
update_pruned_CRL(puppet_crl, loader.key)
|
55
|
+
FileSystem.write_file(puppet.settings[:cacrl], loader.crls, 0644)
|
56
|
+
|
57
|
+
@logger.inform("Finished pruning Puppet's CRL")
|
58
|
+
return 0
|
59
|
+
end
|
60
|
+
|
61
|
+
def prune_CRLs(crl_list)
|
62
|
+
crl_list.each do |crl|
|
63
|
+
existed_serial_number = Set.new()
|
64
|
+
revoked_list = crl.revoked
|
65
|
+
@logger.debug("Pruning duplicate entries in CRL for issuer " \
|
66
|
+
"#{crl.issuer.to_s(OpenSSL::X509::Name::RFC2253)}") if @logger.debug?
|
67
|
+
|
68
|
+
revoked_list.delete_if do |revoked|
|
69
|
+
if existed_serial_number.add?(revoked.serial)
|
70
|
+
false
|
71
|
+
else
|
72
|
+
@logger.debug("Removing duplicate of #{revoked.serial}, " \
|
73
|
+
"revoked on #{revoked.time}\n") if @logger.debug?
|
74
|
+
true
|
75
|
+
end
|
76
|
+
end
|
77
|
+
crl.revoked=(revoked_list)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def update_pruned_CRL(crl_list, pkey)
|
82
|
+
crl_list.each do |crl|
|
83
|
+
crl.version=(crl.version + 1)
|
84
|
+
crl.sign(pkey, OpenSSL::Digest::SHA256.new)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def self.parser(parsed = {})
|
89
|
+
OptionParser.new do |opts|
|
90
|
+
opts.banner = BANNER
|
91
|
+
opts.on('--help', 'Display this command-specific help output') do |help|
|
92
|
+
parsed['help'] = true
|
93
|
+
end
|
94
|
+
opts.on('--config CONF', 'Path to the puppet.conf file on disk') do |conf|
|
95
|
+
parsed['config'] = conf
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def parse(args)
|
101
|
+
results = {}
|
102
|
+
parser = self.class.parser(results)
|
103
|
+
errors = CliParsing.parse_with_errors(parser, args)
|
104
|
+
errors_were_handled = Errors.handle_with_usage(@logger, errors, parser.help)
|
105
|
+
|
106
|
+
if errors_were_handled
|
107
|
+
exit_code = 1
|
108
|
+
else
|
109
|
+
exit_code = nil
|
110
|
+
end
|
111
|
+
return results, exit_code
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
@@ -12,7 +12,7 @@ module Puppetserver
|
|
12
12
|
|
13
13
|
include Puppetserver::Ca::Utils
|
14
14
|
|
15
|
-
|
15
|
+
CERTNAME_BLOCKLIST = %w{--all --config}
|
16
16
|
|
17
17
|
SUMMARY = 'Revoke certificate(s)'
|
18
18
|
BANNER = <<-BANNER
|
@@ -55,7 +55,7 @@ BANNER
|
|
55
55
|
errors = CliParsing.parse_with_errors(parser, args)
|
56
56
|
|
57
57
|
results['certnames'].each do |certname|
|
58
|
-
if
|
58
|
+
if CERTNAME_BLOCKLIST.include?(certname)
|
59
59
|
errors << " Cannot manage cert named `#{certname}` from " +
|
60
60
|
"the CLI, if needed use the HTTP API directly"
|
61
61
|
end
|
@@ -23,10 +23,10 @@ Usage:
|
|
23
23
|
Description:
|
24
24
|
Setup a root and intermediate signing CA for Puppet Server
|
25
25
|
and store generated CA keys, certs, crls, and associated
|
26
|
-
|
26
|
+
server related files on disk.
|
27
27
|
|
28
28
|
The `--subject-alt-names` flag can be used to add SANs to the
|
29
|
-
certificate generated for the Puppet
|
29
|
+
certificate generated for the Puppet server. Multiple names can be
|
30
30
|
listed as a comma separated string. These can be either DNS names or
|
31
31
|
IP addresses, differentiated by prefixes: `DNS:foo.bar.com,IP:123.456.789`.
|
32
32
|
Names with no prefix will be treated as DNS names.
|
@@ -76,7 +76,7 @@ BANNER
|
|
76
76
|
|
77
77
|
root_key, root_cert, root_crl = ca.create_root_cert
|
78
78
|
ca.create_intermediate_cert(root_key, root_cert)
|
79
|
-
|
79
|
+
server_key, server_cert = ca.create_server_cert
|
80
80
|
return ca.errors if ca.errors.any?
|
81
81
|
|
82
82
|
FileSystem.ensure_dirs([settings[:ssldir],
|
@@ -90,28 +90,28 @@ BANNER
|
|
90
90
|
[settings[:cacert], [ca.cert, root_cert]],
|
91
91
|
[settings[:cacrl], [ca.crl, root_crl]],
|
92
92
|
[settings[:cadir] + '/infra_crl.pem', [ca.crl, root_crl]],
|
93
|
-
[settings[:hostcert],
|
93
|
+
[settings[:hostcert], server_cert],
|
94
94
|
[settings[:localcacert], [ca.cert, root_cert]],
|
95
95
|
[settings[:hostcrl], [ca.crl, root_crl]],
|
96
|
-
[settings[:hostpubkey],
|
96
|
+
[settings[:hostpubkey], server_key.public_key],
|
97
97
|
[settings[:capub], ca.key.public_key],
|
98
|
-
[settings[:cert_inventory], ca.inventory_entry(
|
98
|
+
[settings[:cert_inventory], ca.inventory_entry(server_cert)],
|
99
99
|
[settings[:cadir] + '/infra_inventory.txt', ''],
|
100
100
|
[settings[:cadir] + '/infra_serials', ''],
|
101
101
|
[settings[:serial], "002"],
|
102
|
-
[File.join(settings[:signeddir], "#{settings[:certname]}.pem"),
|
102
|
+
[File.join(settings[:signeddir], "#{settings[:certname]}.pem"), server_cert],
|
103
103
|
]
|
104
104
|
|
105
105
|
private_files = [
|
106
|
-
[settings[:hostprivkey],
|
106
|
+
[settings[:hostprivkey], server_key],
|
107
107
|
[settings[:rootkey], root_key],
|
108
108
|
[settings[:cakey], ca.key],
|
109
109
|
]
|
110
110
|
|
111
111
|
files_to_check = public_files + private_files
|
112
|
-
# We don't want to error if
|
112
|
+
# We don't want to error if server's keys exist. Certain workflows
|
113
113
|
# allow the agent to have already be installed with keys and then
|
114
|
-
# upgraded to be a
|
114
|
+
# upgraded to be a server. The host class will honor keys, if both
|
115
115
|
# public and private exist, and error if only one exists - as is
|
116
116
|
# previous behavior.
|
117
117
|
files_to_check = files_to_check.map(&:first) - [settings[:hostpubkey], settings[:hostprivkey]]
|
@@ -160,7 +160,7 @@ ERR
|
|
160
160
|
parsed['config'] = conf
|
161
161
|
end
|
162
162
|
opts.on('--subject-alt-names NAME[,NAME]',
|
163
|
-
'Subject alternative names for the
|
163
|
+
'Subject alternative names for the server cert') do |sans|
|
164
164
|
parsed['subject-alt-names'] = sans
|
165
165
|
end
|
166
166
|
opts.on('--ca-name NAME',
|
@@ -168,7 +168,7 @@ ERR
|
|
168
168
|
parsed['ca-name'] = name
|
169
169
|
end
|
170
170
|
opts.on('--certname NAME',
|
171
|
-
'Common name to use for the
|
171
|
+
'Common name to use for the server cert') do |name|
|
172
172
|
parsed['certname'] = name
|
173
173
|
end
|
174
174
|
end
|
data/lib/puppetserver/ca/cli.rb
CHANGED
@@ -8,6 +8,7 @@ require 'puppetserver/ca/action/list'
|
|
8
8
|
require 'puppetserver/ca/action/revoke'
|
9
9
|
require 'puppetserver/ca/action/setup'
|
10
10
|
require 'puppetserver/ca/action/sign'
|
11
|
+
require 'puppetserver/ca/action/prune'
|
11
12
|
require 'puppetserver/ca/action/migrate'
|
12
13
|
require 'puppetserver/ca/errors'
|
13
14
|
require 'puppetserver/ca/logger'
|
@@ -25,11 +26,12 @@ Manage the Private Key Infrastructure for
|
|
25
26
|
Puppet Server's built-in Certificate Authority
|
26
27
|
BANNER
|
27
28
|
|
28
|
-
|
29
|
+
ADMIN_ACTIONS = {
|
29
30
|
'import' => Action::Import,
|
30
31
|
'setup' => Action::Setup,
|
31
|
-
'enable'
|
32
|
-
'migrate'
|
32
|
+
'enable' => Action::Enable,
|
33
|
+
'migrate' => Action::Migrate,
|
34
|
+
'prune' => Action::Prune
|
33
35
|
}
|
34
36
|
|
35
37
|
MAINT_ACTIONS = {
|
@@ -40,15 +42,15 @@ BANNER
|
|
40
42
|
'sign' => Action::Sign
|
41
43
|
}
|
42
44
|
|
43
|
-
VALID_ACTIONS =
|
45
|
+
VALID_ACTIONS = ADMIN_ACTIONS.merge(MAINT_ACTIONS).sort.to_h
|
44
46
|
|
45
47
|
ACTION_LIST = "\nAvailable Actions:\n\n" +
|
46
48
|
" Certificate Actions (requires a running Puppet Server):\n\n" +
|
47
49
|
MAINT_ACTIONS.map do |action, cls|
|
48
50
|
" #{action}\t#{cls::SUMMARY}"
|
49
51
|
end.join("\n") + "\n\n" +
|
50
|
-
"
|
51
|
-
|
52
|
+
" Administrative Actions (requires Puppet Server to be stopped):\n\n" +
|
53
|
+
ADMIN_ACTIONS.map do |action, cls|
|
52
54
|
" #{action}\t#{cls::SUMMARY}"
|
53
55
|
end.join("\n")
|
54
56
|
|
@@ -64,8 +66,10 @@ BANNER
|
|
64
66
|
|
65
67
|
|
66
68
|
def self.run(cli_args = ARGV, out = STDOUT, err = STDERR)
|
67
|
-
logger = Puppetserver::Ca::Logger.new(:info, out, err)
|
68
69
|
parser, general_options, unparsed = parse_general_inputs(cli_args)
|
70
|
+
level = general_options.delete('verbose') ? :debug : :info
|
71
|
+
|
72
|
+
logger = Puppetserver::Ca::Logger.new(level, out, err)
|
69
73
|
|
70
74
|
if general_options['version']
|
71
75
|
logger.inform Puppetserver::Ca::VERSION
|
@@ -121,6 +125,9 @@ BANNER
|
|
121
125
|
opts.on('--version', 'Display the version') do |v|
|
122
126
|
parsed['version'] = true
|
123
127
|
end
|
128
|
+
opts.on('--verbose', 'Display low-level information') do |verbose|
|
129
|
+
parsed['verbose'] = true
|
130
|
+
end
|
124
131
|
|
125
132
|
opts.separator ACTION_OPTIONS
|
126
133
|
opts.separator "\nSee `puppetserver ca <action> --help` for detailed info"
|
@@ -74,6 +74,9 @@ module Puppetserver
|
|
74
74
|
|
75
75
|
overrides = results[:agent].merge(results[:main]).merge(results[:master]).merge(results[:server])
|
76
76
|
overrides.merge!(cli_overrides)
|
77
|
+
if overrides[:masterport]
|
78
|
+
overrides[:serverport] ||= overrides.delete(:masterport)
|
79
|
+
end
|
77
80
|
|
78
81
|
@settings = resolve_settings(overrides).freeze
|
79
82
|
end
|
@@ -105,11 +108,10 @@ module Puppetserver
|
|
105
108
|
base_defaults = [
|
106
109
|
[:confdir, user_specific_conf_dir],
|
107
110
|
[:ssldir,'$confdir/ssl'],
|
108
|
-
[:cadir, '$ssldir/ca'],
|
109
111
|
[:certdir, '$ssldir/certs'],
|
110
112
|
[:certname, default_certname],
|
111
113
|
[:server, 'puppet'],
|
112
|
-
[:
|
114
|
+
[:serverport, '8140'],
|
113
115
|
[:privatekeydir, '$ssldir/private_keys'],
|
114
116
|
[:publickeydir, '$ssldir/public_keys'],
|
115
117
|
]
|
@@ -127,7 +129,7 @@ module Puppetserver
|
|
127
129
|
:serial => '$cadir/serial',
|
128
130
|
:cert_inventory => '$cadir/inventory.txt',
|
129
131
|
:ca_server => '$server',
|
130
|
-
:ca_port => '$
|
132
|
+
:ca_port => '$serverport',
|
131
133
|
:localcacert => '$certdir/ca.pem',
|
132
134
|
:hostcrl => '$ssldir/crl.pem',
|
133
135
|
:hostcert => '$certdir/$certname.pem',
|
@@ -150,6 +152,10 @@ module Puppetserver
|
|
150
152
|
settings[setting_name] = substitutions[substitution_name] = subbed_value
|
151
153
|
end
|
152
154
|
|
155
|
+
cadir = find_cadir(overrides.fetch(:cadir, false),
|
156
|
+
settings[:confdir])
|
157
|
+
settings[:cadir] = substitutions['$cadir'] = cadir
|
158
|
+
|
153
159
|
dependent_defaults.each do |setting_name, default_value|
|
154
160
|
setting_value = overrides.fetch(setting_name, default_value)
|
155
161
|
settings[setting_name] = setting_value
|
@@ -212,6 +218,21 @@ module Puppetserver
|
|
212
218
|
|
213
219
|
private
|
214
220
|
|
221
|
+
def find_cadir(configured_cadir, confdir)
|
222
|
+
if configured_cadir
|
223
|
+
configured_cadir
|
224
|
+
else
|
225
|
+
old_cadir = Puppetserver::Ca::Utils::Config.old_default_cadir(confdir)
|
226
|
+
new_cadir = Puppetserver::Ca::Utils::Config.new_default_cadir(confdir)
|
227
|
+
|
228
|
+
if File.exist?("#{new_cadir}/ca_crt.pem")
|
229
|
+
new_cadir
|
230
|
+
else
|
231
|
+
old_cadir
|
232
|
+
end
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
215
236
|
def explicitly_given_config_file_or_default_config_exists?
|
216
237
|
!@using_default_location || File.exist?(@config_path)
|
217
238
|
end
|
@@ -258,7 +279,7 @@ module Puppetserver
|
|
258
279
|
end
|
259
280
|
|
260
281
|
if settings.dig(:server_list, 0, 1) &&
|
261
|
-
settings[:ca_port] == '$
|
282
|
+
settings[:ca_port] == '$serverport'
|
262
283
|
|
263
284
|
settings[:ca_port] = settings.dig(:server_list, 0, 1)
|
264
285
|
end
|
data/lib/puppetserver/ca/host.rb
CHANGED
@@ -58,10 +58,10 @@ module Puppetserver
|
|
58
58
|
@errors = []
|
59
59
|
end
|
60
60
|
|
61
|
-
# If both the private and public keys exist for a
|
61
|
+
# If both the private and public keys exist for a server then we want
|
62
62
|
# to honor them here, if only one key exists we want to surface an error,
|
63
63
|
# and if neither exist we generate a new key. This logic is necessary for
|
64
|
-
# proper bootstrapping for certain
|
64
|
+
# proper bootstrapping for certain server workflows.
|
65
65
|
def create_private_key(keylength, private_path = '', public_path = '')
|
66
66
|
if File.exists?(private_path) && File.exists?(public_path)
|
67
67
|
return OpenSSL::PKey.read(File.read(private_path))
|
@@ -20,7 +20,7 @@ module Puppetserver
|
|
20
20
|
|
21
21
|
CLI_AUTH_EXT_OID = "1.3.6.1.4.1.34380.1.3.39"
|
22
22
|
|
23
|
-
|
23
|
+
SERVER_EXTENSIONS = [
|
24
24
|
["basicConstraints", "CA:FALSE", true],
|
25
25
|
["nsComment", "Puppet Server Internal Certificate", false],
|
26
26
|
["authorityKeyIdentifier", "keyid:always", false],
|
@@ -132,23 +132,23 @@ module Puppetserver
|
|
132
132
|
time.strftime('%Y-%m-%dT%H:%M:%S%Z')
|
133
133
|
end
|
134
134
|
|
135
|
-
def
|
136
|
-
|
137
|
-
|
135
|
+
def create_server_cert
|
136
|
+
server_cert = nil
|
137
|
+
server_key = @host.create_private_key(@settings[:keylength],
|
138
138
|
@settings[:hostprivkey],
|
139
139
|
@settings[:hostpubkey])
|
140
|
-
if
|
141
|
-
|
140
|
+
if server_key
|
141
|
+
server_csr = @host.create_csr(name: @settings[:certname], key: server_key)
|
142
142
|
if @settings[:subject_alt_names].empty?
|
143
143
|
alt_names = "DNS:puppet, DNS:#{@settings[:certname]}"
|
144
144
|
else
|
145
145
|
alt_names = @settings[:subject_alt_names]
|
146
146
|
end
|
147
147
|
|
148
|
-
|
148
|
+
server_cert = sign_authorized_cert(server_csr, alt_names)
|
149
149
|
end
|
150
150
|
|
151
|
-
return
|
151
|
+
return server_key, server_cert
|
152
152
|
end
|
153
153
|
|
154
154
|
def sign_authorized_cert(csr, alt_names = '')
|
@@ -176,7 +176,7 @@ module Puppetserver
|
|
176
176
|
end
|
177
177
|
|
178
178
|
def add_authorized_extensions(cert, ef)
|
179
|
-
|
179
|
+
SERVER_EXTENSIONS.each do |ext|
|
180
180
|
extension = ef.create_extension(*ext)
|
181
181
|
cert.add_extension(extension)
|
182
182
|
end
|
@@ -19,6 +19,25 @@ module Puppetserver
|
|
19
19
|
end.sort.uniq.join(", ")
|
20
20
|
end
|
21
21
|
|
22
|
+
def self.puppet_confdir
|
23
|
+
if running_as_root?
|
24
|
+
'/etc/puppetlabs/puppet'
|
25
|
+
else
|
26
|
+
"#{ENV['HOME']}/.puppetlabs/etc/puppet"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.puppetserver_confdir(puppet_confdir)
|
31
|
+
File.join(File.dirname(puppet_confdir), 'puppetserver')
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.old_default_cadir(confdir = puppet_confdir)
|
35
|
+
File.join(confdir, 'ssl', 'ca')
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.new_default_cadir(confdir = puppet_confdir)
|
39
|
+
File.join(puppetserver_confdir(confdir), 'ca')
|
40
|
+
end
|
22
41
|
end
|
23
42
|
end
|
24
43
|
end
|
@@ -19,7 +19,8 @@ module Puppetserver
|
|
19
19
|
|
20
20
|
# Not all connections require a client cert to be present.
|
21
21
|
# For example, when querying the status endpoint.
|
22
|
-
def initialize(settings, with_client_cert: true)
|
22
|
+
def initialize(logger, settings, with_client_cert: true)
|
23
|
+
@logger = logger
|
23
24
|
@store = make_store(settings[:localcacert],
|
24
25
|
settings[:certificate_revocation],
|
25
26
|
settings[:hostcrl])
|
@@ -50,7 +51,7 @@ module Puppetserver
|
|
50
51
|
# The Connection object should have HTTP verbs defined on it that take
|
51
52
|
# a body (and optional overrides). Returns whatever the block given returned.
|
52
53
|
def with_connection(url, &block)
|
53
|
-
request = ->(conn) { block.call(Connection.new(conn, url)) }
|
54
|
+
request = ->(conn) { block.call(Connection.new(conn, url, @logger)) }
|
54
55
|
|
55
56
|
begin
|
56
57
|
Net::HTTP.start(url.host, url.port,
|
@@ -85,29 +86,35 @@ module Puppetserver
|
|
85
86
|
# and defines methods named after HTTP verbs that are called on the
|
86
87
|
# saved connection, returning a Result.
|
87
88
|
class Connection
|
88
|
-
def initialize(net_http_connection, url_struct)
|
89
|
+
def initialize(net_http_connection, url_struct, logger)
|
89
90
|
@conn = net_http_connection
|
90
91
|
@url = url_struct
|
92
|
+
@logger = logger
|
91
93
|
end
|
92
94
|
|
93
95
|
def get(url_overide = nil, headers = {})
|
94
96
|
url = url_overide || @url
|
95
97
|
headers = DEFAULT_HEADERS.merge(headers)
|
96
98
|
|
99
|
+
@logger.debug("Making a GET request at #{url.full_url}")
|
100
|
+
|
97
101
|
request = Net::HTTP::Get.new(url.to_uri, headers)
|
98
102
|
result = @conn.request(request)
|
99
|
-
|
100
103
|
Result.new(result.code, result.body)
|
104
|
+
|
101
105
|
end
|
102
106
|
|
103
107
|
def put(body, url_override = nil, headers = {})
|
104
108
|
url = url_override || @url
|
105
109
|
headers = DEFAULT_HEADERS.merge(headers)
|
106
110
|
|
111
|
+
@logger.debug("Making a PUT request at #{url.full_url}")
|
112
|
+
|
107
113
|
request = Net::HTTP::Put.new(url.to_uri, headers)
|
108
114
|
request.body = body
|
109
115
|
result = @conn.request(request)
|
110
116
|
|
117
|
+
|
111
118
|
Result.new(result.code, result.body)
|
112
119
|
end
|
113
120
|
|
@@ -115,6 +122,8 @@ module Puppetserver
|
|
115
122
|
url = url_override || @url
|
116
123
|
headers = DEFAULT_HEADERS.merge(headers)
|
117
124
|
|
125
|
+
@logger.debug("Making a DELETE request at #{url.full_url}")
|
126
|
+
|
118
127
|
result = @conn.request(Net::HTTP::Delete.new(url.to_uri, headers))
|
119
128
|
|
120
129
|
Result.new(result.code, result.body)
|
@@ -166,12 +175,12 @@ module Puppetserver
|
|
166
175
|
def self.check_server_online(settings, logger)
|
167
176
|
status_url = URL.new('https', settings[:ca_server], settings[:ca_port], 'status', 'v1', 'simple', 'ca')
|
168
177
|
begin
|
169
|
-
# Generating certs offline is necessary if the
|
178
|
+
# Generating certs offline is necessary if the server cert has been destroyed
|
170
179
|
# or compromised. Since querying the status endpoint does not require a client cert, and
|
171
180
|
# we commonly won't have one, don't require one for creating the connection.
|
172
181
|
# Additionally, we want to ensure the server is stopped before migrating the CA dir to
|
173
182
|
# avoid issues with writing to the CA dir and moving it.
|
174
|
-
self.new(settings, with_client_cert: false).with_connection(status_url) do |conn|
|
183
|
+
self.new(logger, settings, with_client_cert: false).with_connection(status_url) do |conn|
|
175
184
|
result = conn.get
|
176
185
|
if result.body == "running"
|
177
186
|
logger.err "Puppetserver service is running. Please stop it before attempting to run this command."
|
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: 1.
|
4
|
+
version: 1.11.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Puppet, Inc.
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-
|
11
|
+
date: 2021-07-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: facter
|
@@ -100,6 +100,7 @@ files:
|
|
100
100
|
- lib/puppetserver/ca/action/import.rb
|
101
101
|
- lib/puppetserver/ca/action/list.rb
|
102
102
|
- lib/puppetserver/ca/action/migrate.rb
|
103
|
+
- lib/puppetserver/ca/action/prune.rb
|
103
104
|
- lib/puppetserver/ca/action/revoke.rb
|
104
105
|
- lib/puppetserver/ca/action/setup.rb
|
105
106
|
- lib/puppetserver/ca/action/sign.rb
|