puppetserver-ca 1.9.3 → 1.11.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2425e37368e436ac075108565392b12b76bfc4979211314f9546b28d91c4436c
4
- data.tar.gz: c4be371798d5a7baed4d9eeac5c96d2dad2ec822f94f243500b1e647248a4c89
3
+ metadata.gz: 813b9f6f9913cd98abd4b81219cfb4882e7c9c1ace77f024218706be07411d7a
4
+ data.tar.gz: 8f7bd49d04fc6f23f4674006f1d159756286f9b3ae50a686cb4faa5b7b71832d
5
5
  SHA512:
6
- metadata.gz: fb89a14f1cf7f137cf970854ab1f7604c3d60e8bb6c3d2b45035a2567a1606f490e1f8ab5386d84b270121abe1278de4777aeddeda039f4597125e3297933d14
7
- data.tar.gz: 0f66e2871e19442d382b23c035546affe368990df2ba051672b55c549cf01fe267f930b4aae4ec465f2e4e0b82ee73ccc30708df04c680a0d2c1b2dac8f7cf4d
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/master/LICENSE.md)
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-master-prod-1.delivery.puppetlabs.net/job/platform_puppetserver-ca_init-multijob_1.x/), which will bump the version, tag, build, and release the gem.
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/master/CONTRIBUTING.md)
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/master/CODE_OF_CONDUCT.md).
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
- CERTNAME_BLACKLIST = %w{--all --config}
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 CERTNAME_BLACKLIST.include?(certname)
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
- CERTNAME_BLACKLIST = %w{--all --config}
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 master's host cert, or for manually
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 CERTNAME_BLACKLIST.include?(certname)
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 master PKI"
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
- master_key, master_cert = ca.create_master_cert
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], master_key.public_key],
92
- [settings[:hostcert], master_cert],
93
- [settings[:cert_inventory], ca.inventory_entry(master_cert)],
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"), master_cert]
98
+ [File.join(settings[:signeddir], "#{settings[:certname]}.pem"), server_cert]
99
99
  ]
100
100
 
101
101
  private_files = [
102
- [settings[:hostprivkey], master_key],
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 master's keys exist. Certain workflows
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 master. The host class will honor keys, if both
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 master cert') do |name|
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 master cert') do |sans|
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
- filter_names = certnames.any? \
75
- ? lambda { |x| certnames.include?(x['name']) }
76
- : lambda { |x| true }
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
- ? output_certs_by_state(requested, signed, revoked, missing)
84
- : output_certs_by_state(requested)
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
- return missing.any? \
87
- ? 1
88
- : 0
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 output_certs_by_state(requested, signed = [], revoked = [], missing = [])
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
- result ? JSON.parse(result.body) : []
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
- exit_code = errors_were_handled ? 1 : nil
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
- CERTNAME_BLACKLIST = %w{--all --config}
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 CERTNAME_BLACKLIST.include?(certname)
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
- master related files on disk.
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 master. Multiple names can be
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
- master_key, master_cert = ca.create_master_cert
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], master_cert],
93
+ [settings[:hostcert], server_cert],
94
94
  [settings[:localcacert], [ca.cert, root_cert]],
95
95
  [settings[:hostcrl], [ca.crl, root_crl]],
96
- [settings[:hostpubkey], master_key.public_key],
96
+ [settings[:hostpubkey], server_key.public_key],
97
97
  [settings[:capub], ca.key.public_key],
98
- [settings[:cert_inventory], ca.inventory_entry(master_cert)],
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"), master_cert],
102
+ [File.join(settings[:signeddir], "#{settings[:certname]}.pem"), server_cert],
103
103
  ]
104
104
 
105
105
  private_files = [
106
- [settings[:hostprivkey], master_key],
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 master's keys exist. Certain workflows
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 master. The host class will honor keys, if both
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 master cert') do |sans|
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 master cert') do |name|
171
+ 'Common name to use for the server cert') do |name|
172
172
  parsed['certname'] = name
173
173
  end
174
174
  end
@@ -23,7 +23,7 @@ module Puppetserver
23
23
 
24
24
  def initialize(logger, settings)
25
25
  @logger = logger
26
- @client = HttpClient.new(settings)
26
+ @client = HttpClient.new(@logger, settings)
27
27
  @ca_server = settings[:ca_server]
28
28
  @ca_port = settings[:ca_port]
29
29
  end
@@ -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
- INIT_ACTIONS = {
29
+ ADMIN_ACTIONS = {
29
30
  'import' => Action::Import,
30
31
  'setup' => Action::Setup,
31
- 'enable' => Action::Enable,
32
- 'migrate' => Action::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 = INIT_ACTIONS.merge(MAINT_ACTIONS).sort.to_h
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
- " Initialization Actions (requires Puppet Server to be stopped):\n\n" +
51
- INIT_ACTIONS.map do |action, cls|
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
- [:masterport, '8140'],
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 => '$masterport',
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] == '$masterport'
282
+ settings[:ca_port] == '$serverport'
262
283
 
263
284
  settings[:ca_port] = settings.dig(:server_list, 0, 1)
264
285
  end
@@ -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 master then we want
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 master workflows.
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
- MASTER_EXTENSIONS = [
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 create_master_cert
136
- master_cert = nil
137
- master_key = @host.create_private_key(@settings[:keylength],
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 master_key
141
- master_csr = @host.create_csr(name: @settings[:certname], key: master_key)
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
- master_cert = sign_authorized_cert(master_csr, alt_names)
148
+ server_cert = sign_authorized_cert(server_csr, alt_names)
149
149
  end
150
150
 
151
- return master_key, master_cert
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
- MASTER_EXTENSIONS.each do |ext|
179
+ SERVER_EXTENSIONS.each do |ext|
180
180
  extension = ef.create_extension(*ext)
181
181
  cert.add_extension(extension)
182
182
  end
@@ -13,8 +13,16 @@ module Puppetserver
13
13
  @err = err
14
14
  end
15
15
 
16
+ def level
17
+ @level
18
+ end
19
+
20
+ def debug?
21
+ return @level >= LEVELS[:debug]
22
+ end
23
+
16
24
  def debug(text)
17
- if @level >= LEVELS[:debug]
25
+ if debug?
18
26
  @out.puts(text)
19
27
  end
20
28
  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 master cert has been destroyed
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."
@@ -1,5 +1,5 @@
1
1
  module Puppetserver
2
2
  module Ca
3
- VERSION = "1.9.3"
3
+ VERSION = "1.11.0"
4
4
  end
5
5
  end
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.9.3
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-03-25 00:00:00.000000000 Z
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