puppetserver-ca 2.0.2 → 2.3.1
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 -6
- 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/migrate.rb +3 -2
- data/lib/puppetserver/ca/action/prune.rb +131 -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 +6 -3
- 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/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: 9443656cd200fac958b84f3b7444a402146344105b25022eef46dbe1d24201a1
|
|
4
|
+
data.tar.gz: aa4c2fbcae1976a25346c6cfe16d792099b6ccc722f24770535e58367bc4128d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 34e5662cbb5d7f92bf9f1b3f91f69d37149986c4ba998e42655cf5440349a242116f04c3abfc50244140f4de681fdffed77fa9c56128286bfe3e96d49ebc2939
|
|
7
|
+
data.tar.gz: 24740e26cceae352f4def897433bc4971b78c8d51c94e0692b19868a10a73dec29c266c1f9f09281b46299dfd9f795cba00c57b00726556529faf0ef820bd664
|
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,8 +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-
|
|
96
|
-
|
|
105
|
+
To release a new version, run the [release pipeline](https://jenkins-platform.delivery.puppetlabs.net/job/platform_puppetserver-ca_init-multijob_main/), which will bump the version, tag, build, and release the gem.
|
|
97
106
|
|
|
98
107
|
## Contributing & Support
|
|
99
108
|
|
|
@@ -105,9 +114,9 @@ Freenode, or the Puppet Community Slack channel.
|
|
|
105
114
|
|
|
106
115
|
Contributions are welcome at https://github.com/puppetlabs/puppetserver-ca-cli/pulls.
|
|
107
116
|
Contributors should both be sure to read the
|
|
108
|
-
[contributing document](https://github.com/puppetlabs/puppetserver-ca-cli/blob/
|
|
117
|
+
[contributing document](https://github.com/puppetlabs/puppetserver-ca-cli/blob/main/CONTRIBUTING.md)
|
|
109
118
|
and sign the [contributor license agreement](https://cla.puppet.com/).
|
|
110
119
|
|
|
111
120
|
Everyone interacting with the project’s codebase, issue tracker, etc is expected
|
|
112
121
|
to follow the
|
|
113
|
-
[code of conduct](https://github.com/puppetlabs/puppetserver-ca-cli/blob/
|
|
122
|
+
[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
|
|
@@ -15,7 +15,7 @@ module Puppetserver
|
|
|
15
15
|
class Import
|
|
16
16
|
include Puppetserver::Ca::Utils
|
|
17
17
|
|
|
18
|
-
SUMMARY = "Import an external CA chain and generate
|
|
18
|
+
SUMMARY = "Import an external CA chain and generate server PKI"
|
|
19
19
|
BANNER = <<-BANNER
|
|
20
20
|
Usage:
|
|
21
21
|
puppetserver ca import [--help]
|
|
@@ -73,7 +73,7 @@ BANNER
|
|
|
73
73
|
def import(loader, settings, signing_digest)
|
|
74
74
|
ca = Puppetserver::Ca::LocalCertificateAuthority.new(signing_digest, settings)
|
|
75
75
|
ca.initialize_ssl_components(loader)
|
|
76
|
-
|
|
76
|
+
server_key, server_cert = ca.create_server_cert
|
|
77
77
|
return ca.errors if ca.errors.any?
|
|
78
78
|
|
|
79
79
|
FileSystem.ensure_dirs([settings[:ssldir],
|
|
@@ -89,25 +89,25 @@ BANNER
|
|
|
89
89
|
[settings[:cadir] + '/infra_crl.pem', loader.crls],
|
|
90
90
|
[settings[:localcacert], loader.certs],
|
|
91
91
|
[settings[:hostcrl], loader.crls],
|
|
92
|
-
[settings[:hostpubkey],
|
|
93
|
-
[settings[:hostcert],
|
|
94
|
-
[settings[:cert_inventory], ca.inventory_entry(
|
|
92
|
+
[settings[:hostpubkey], server_key.public_key],
|
|
93
|
+
[settings[:hostcert], server_cert],
|
|
94
|
+
[settings[:cert_inventory], ca.inventory_entry(server_cert)],
|
|
95
95
|
[settings[:capub], loader.key.public_key],
|
|
96
96
|
[settings[:cadir] + '/infra_inventory.txt', ''],
|
|
97
97
|
[settings[:cadir] + '/infra_serials', ''],
|
|
98
98
|
[settings[:serial], "002"],
|
|
99
|
-
[File.join(settings[:signeddir], "#{settings[:certname]}.pem"),
|
|
99
|
+
[File.join(settings[:signeddir], "#{settings[:certname]}.pem"), server_cert]
|
|
100
100
|
]
|
|
101
101
|
|
|
102
102
|
private_files = [
|
|
103
|
-
[settings[:hostprivkey],
|
|
103
|
+
[settings[:hostprivkey], server_key],
|
|
104
104
|
[settings[:cakey], loader.key],
|
|
105
105
|
]
|
|
106
106
|
|
|
107
107
|
files_to_check = public_files + private_files
|
|
108
|
-
# We don't want to error if
|
|
108
|
+
# We don't want to error if server's keys exist. Certain workflows
|
|
109
109
|
# allow the agent to have already be installed with keys and then
|
|
110
|
-
# upgraded to be a
|
|
110
|
+
# upgraded to be a server. The host class will honor keys, if both
|
|
111
111
|
# public and private exist, and error if only one exists - as is
|
|
112
112
|
# previous behavior.
|
|
113
113
|
files_to_check = files_to_check.map(&:first) - [settings[:hostpubkey], settings[:hostprivkey]]
|
|
@@ -181,11 +181,11 @@ ERR
|
|
|
181
181
|
parsed['crl-chain'] = chain
|
|
182
182
|
end
|
|
183
183
|
opts.on('--certname NAME',
|
|
184
|
-
'Common name to use for the
|
|
184
|
+
'Common name to use for the server cert') do |name|
|
|
185
185
|
parsed['certname'] = name
|
|
186
186
|
end
|
|
187
187
|
opts.on('--subject-alt-names NAME[,NAME]',
|
|
188
|
-
'Subject alternative names for the
|
|
188
|
+
'Subject alternative names for the server cert') do |sans|
|
|
189
189
|
parsed['subject-alt-names'] = sans
|
|
190
190
|
end
|
|
191
191
|
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, @logger)
|
|
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
|
|
@@ -18,8 +18,9 @@ Usage:
|
|
|
18
18
|
|
|
19
19
|
Description:
|
|
20
20
|
Migrate an existing CA directory to #{PUPPETSERVER_CA_DIR}. This is for
|
|
21
|
-
upgrading from Puppet Platform 6.x to Puppet 7.
|
|
22
|
-
|
|
21
|
+
upgrading from Puppet Platform 6.x to Puppet 7. Uses the default puppet.conf
|
|
22
|
+
in your installation, or use a different config by supplying the `--config` flag.
|
|
23
|
+
|
|
23
24
|
Options:
|
|
24
25
|
BANNER
|
|
25
26
|
|
|
@@ -0,0 +1,131 @@
|
|
|
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
|
+
number_of_removed_duplicates = prune_CRLs(puppet_crl)
|
|
54
|
+
|
|
55
|
+
if number_of_removed_duplicates > 0
|
|
56
|
+
update_pruned_CRL(puppet_crl, loader.key)
|
|
57
|
+
FileSystem.write_file(puppet.settings[:cacrl], loader.crls, 0644)
|
|
58
|
+
@logger.inform("Removed #{number_of_removed_duplicates} duplicated certs from Puppet's CRL.")
|
|
59
|
+
else
|
|
60
|
+
@logger.inform("No duplicate revocations found in the CRL.")
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
return 0
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def prune_CRLs(crl_list)
|
|
67
|
+
number_of_removed_duplicates = 0
|
|
68
|
+
|
|
69
|
+
crl_list.each do |crl|
|
|
70
|
+
existed_serial_number = Set.new()
|
|
71
|
+
revoked_list = crl.revoked
|
|
72
|
+
@logger.debug("Pruning duplicate entries in CRL for issuer " \
|
|
73
|
+
"#{crl.issuer.to_s(OpenSSL::X509::Name::RFC2253)}") if @logger.debug?
|
|
74
|
+
|
|
75
|
+
revoked_list.delete_if do |revoked|
|
|
76
|
+
if existed_serial_number.add?(revoked.serial)
|
|
77
|
+
false
|
|
78
|
+
else
|
|
79
|
+
number_of_removed_duplicates += 1
|
|
80
|
+
@logger.debug("Removing duplicate of #{revoked.serial}, " \
|
|
81
|
+
"revoked on #{revoked.time}\n") if @logger.debug?
|
|
82
|
+
true
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
crl.revoked=(revoked_list)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
return number_of_removed_duplicates
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def update_pruned_CRL(crl_list, pkey)
|
|
92
|
+
crl_list.each do |crl|
|
|
93
|
+
number_ext, other_ext = crl.extensions.partition{ |ext| ext.oid == "crlNumber" }
|
|
94
|
+
number_ext.each do |crl_number|
|
|
95
|
+
updated_crl_number = OpenSSL::BN.new(crl_number.value) + OpenSSL::BN.new(1)
|
|
96
|
+
crl_number.value=(OpenSSL::ASN1::Integer(updated_crl_number))
|
|
97
|
+
end
|
|
98
|
+
crl.extensions=(number_ext + other_ext)
|
|
99
|
+
crl.sign(pkey, OpenSSL::Digest::SHA256.new)
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def self.parser(parsed = {})
|
|
104
|
+
OptionParser.new do |opts|
|
|
105
|
+
opts.banner = BANNER
|
|
106
|
+
opts.on('--help', 'Display this command-specific help output') do |help|
|
|
107
|
+
parsed['help'] = true
|
|
108
|
+
end
|
|
109
|
+
opts.on('--config CONF', 'Path to the puppet.conf file on disk') do |conf|
|
|
110
|
+
parsed['config'] = conf
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def parse(args)
|
|
116
|
+
results = {}
|
|
117
|
+
parser = self.class.parser(results)
|
|
118
|
+
errors = CliParsing.parse_with_errors(parser, args)
|
|
119
|
+
errors_were_handled = Errors.handle_with_usage(@logger, errors, parser.help)
|
|
120
|
+
|
|
121
|
+
if errors_were_handled
|
|
122
|
+
exit_code = 1
|
|
123
|
+
else
|
|
124
|
+
exit_code = nil
|
|
125
|
+
end
|
|
126
|
+
return results, exit_code
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
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
|
|
@@ -24,10 +24,10 @@ Usage:
|
|
|
24
24
|
Description:
|
|
25
25
|
Setup a root and intermediate signing CA for Puppet Server
|
|
26
26
|
and store generated CA keys, certs, crls, and associated
|
|
27
|
-
|
|
27
|
+
server related files on disk.
|
|
28
28
|
|
|
29
29
|
The `--subject-alt-names` flag can be used to add SANs to the
|
|
30
|
-
certificate generated for the Puppet
|
|
30
|
+
certificate generated for the Puppet server. Multiple names can be
|
|
31
31
|
listed as a comma separated string. These can be either DNS names or
|
|
32
32
|
IP addresses, differentiated by prefixes: `DNS:foo.bar.com,IP:123.456.789`.
|
|
33
33
|
Names with no prefix will be treated as DNS names.
|
|
@@ -77,7 +77,7 @@ BANNER
|
|
|
77
77
|
|
|
78
78
|
root_key, root_cert, root_crl = ca.create_root_cert
|
|
79
79
|
ca.create_intermediate_cert(root_key, root_cert)
|
|
80
|
-
|
|
80
|
+
server_key, server_cert = ca.create_server_cert
|
|
81
81
|
return ca.errors if ca.errors.any?
|
|
82
82
|
|
|
83
83
|
FileSystem.ensure_dirs([settings[:ssldir],
|
|
@@ -91,28 +91,28 @@ BANNER
|
|
|
91
91
|
[settings[:cacert], [ca.cert, root_cert]],
|
|
92
92
|
[settings[:cacrl], [ca.crl, root_crl]],
|
|
93
93
|
[settings[:cadir] + '/infra_crl.pem', [ca.crl, root_crl]],
|
|
94
|
-
[settings[:hostcert],
|
|
94
|
+
[settings[:hostcert], server_cert],
|
|
95
95
|
[settings[:localcacert], [ca.cert, root_cert]],
|
|
96
96
|
[settings[:hostcrl], [ca.crl, root_crl]],
|
|
97
|
-
[settings[:hostpubkey],
|
|
97
|
+
[settings[:hostpubkey], server_key.public_key],
|
|
98
98
|
[settings[:capub], ca.key.public_key],
|
|
99
|
-
[settings[:cert_inventory], ca.inventory_entry(
|
|
99
|
+
[settings[:cert_inventory], ca.inventory_entry(server_cert)],
|
|
100
100
|
[settings[:cadir] + '/infra_inventory.txt', ''],
|
|
101
101
|
[settings[:cadir] + '/infra_serials', ''],
|
|
102
102
|
[settings[:serial], "002"],
|
|
103
|
-
[File.join(settings[:signeddir], "#{settings[:certname]}.pem"),
|
|
103
|
+
[File.join(settings[:signeddir], "#{settings[:certname]}.pem"), server_cert],
|
|
104
104
|
]
|
|
105
105
|
|
|
106
106
|
private_files = [
|
|
107
|
-
[settings[:hostprivkey],
|
|
107
|
+
[settings[:hostprivkey], server_key],
|
|
108
108
|
[settings[:rootkey], root_key],
|
|
109
109
|
[settings[:cakey], ca.key],
|
|
110
110
|
]
|
|
111
111
|
|
|
112
112
|
files_to_check = public_files + private_files
|
|
113
|
-
# We don't want to error if
|
|
113
|
+
# We don't want to error if server's keys exist. Certain workflows
|
|
114
114
|
# allow the agent to have already be installed with keys and then
|
|
115
|
-
# upgraded to be a
|
|
115
|
+
# upgraded to be a server. The host class will honor keys, if both
|
|
116
116
|
# public and private exist, and error if only one exists - as is
|
|
117
117
|
# previous behavior.
|
|
118
118
|
files_to_check = files_to_check.map(&:first) - [settings[:hostpubkey], settings[:hostprivkey]]
|
|
@@ -163,7 +163,7 @@ ERR
|
|
|
163
163
|
parsed['config'] = conf
|
|
164
164
|
end
|
|
165
165
|
opts.on('--subject-alt-names NAME[,NAME]',
|
|
166
|
-
'Subject alternative names for the
|
|
166
|
+
'Subject alternative names for the server cert') do |sans|
|
|
167
167
|
parsed['subject-alt-names'] = sans
|
|
168
168
|
end
|
|
169
169
|
opts.on('--ca-name NAME',
|
|
@@ -171,7 +171,7 @@ ERR
|
|
|
171
171
|
parsed['ca-name'] = name
|
|
172
172
|
end
|
|
173
173
|
opts.on('--certname NAME',
|
|
174
|
-
'Common name to use for the
|
|
174
|
+
'Common name to use for the server cert') do |name|
|
|
175
175
|
parsed['certname'] = name
|
|
176
176
|
end
|
|
177
177
|
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"
|
|
@@ -69,6 +69,9 @@ module Puppetserver
|
|
|
69
69
|
|
|
70
70
|
overrides = results[:agent].merge(results[:main]).merge(results[:master]).merge(results[:server])
|
|
71
71
|
overrides.merge!(cli_overrides)
|
|
72
|
+
if overrides[:masterport]
|
|
73
|
+
overrides[:serverport] ||= overrides.delete(:masterport)
|
|
74
|
+
end
|
|
72
75
|
|
|
73
76
|
@settings = resolve_settings(overrides, logger, ca_dir_warn: ca_dir_warn).freeze
|
|
74
77
|
end
|
|
@@ -103,7 +106,7 @@ module Puppetserver
|
|
|
103
106
|
[:certdir, '$ssldir/certs'],
|
|
104
107
|
[:certname, default_certname],
|
|
105
108
|
[:server, 'puppet'],
|
|
106
|
-
[:
|
|
109
|
+
[:serverport, '8140'],
|
|
107
110
|
[:privatekeydir, '$ssldir/private_keys'],
|
|
108
111
|
[:publickeydir, '$ssldir/public_keys'],
|
|
109
112
|
]
|
|
@@ -121,7 +124,7 @@ module Puppetserver
|
|
|
121
124
|
:serial => '$cadir/serial',
|
|
122
125
|
:cert_inventory => '$cadir/inventory.txt',
|
|
123
126
|
:ca_server => '$server',
|
|
124
|
-
:ca_port => '$
|
|
127
|
+
:ca_port => '$serverport',
|
|
125
128
|
:localcacert => '$certdir/ca.pem',
|
|
126
129
|
:hostcrl => '$ssldir/crl.pem',
|
|
127
130
|
:hostcert => '$certdir/$certname.pem',
|
|
@@ -287,7 +290,7 @@ module Puppetserver
|
|
|
287
290
|
end
|
|
288
291
|
|
|
289
292
|
if settings.dig(:server_list, 0, 1) &&
|
|
290
|
-
settings[:ca_port] == '$
|
|
293
|
+
settings[:ca_port] == '$serverport'
|
|
291
294
|
|
|
292
295
|
settings[:ca_port] = settings.dig(:server_list, 0, 1)
|
|
293
296
|
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,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: 2.
|
|
4
|
+
version: 2.3.1
|
|
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-28 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
|