puppetserver-ca 2.3.6 → 2.5.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 752967f94498efd749219a41bd66937700a3d9118847ab4bdccb3029abe993d9
4
- data.tar.gz: 3207d8be773911373ceec30ad9153416ff869060aec1dc9288340bd2bc2bb3d1
3
+ metadata.gz: 020665126fef9a8fe7bc63652247bf13cf909ab37e2a3336d43724bc63b26d32
4
+ data.tar.gz: c8d62416aaa1c4ded40daee19907798a13ed6fa3ea123436701adf88b9b497cd
5
5
  SHA512:
6
- metadata.gz: a60ee59b29cee51967a5a2e56bfd953d04826e09366e83a4246e6ab1f4965e47c41216975cef7e57f37d0506a94a12bcf9d09b78ec9cf13ef94c0377e3ba2292
7
- data.tar.gz: 44a29a8098048df7a1d6db75400194c567371ae8bdda5f791872e2db23b58bb7c68d075fd3863f09abbddbc50501cbab4b31fe4910f32384506832a44697bceb
6
+ metadata.gz: 89f506f4124c95b8b7029043ce46f216b837d3863875077476bfb835cbaf83399734d02dc7bd64ec39234d4d68735e98997ea81a724146ccef7b540e02d59849
7
+ data.tar.gz: 33b9fa7893650441e456e39edda8d5d4532bcd52fe42f0832769eafad982655d2eb26357b51080a90d1da313b4a69d02f01119ae9dc1e4af645641664e9f8699
@@ -0,0 +1,37 @@
1
+ name: mend_scan
2
+
3
+ on:
4
+ workflow_dispatch:
5
+ push:
6
+ branches:
7
+ - main
8
+ jobs:
9
+ build:
10
+ runs-on: ubuntu-latest
11
+ steps:
12
+ - name: checkout repo content
13
+ uses: actions/checkout@v2 # checkout the repository content to github runner.
14
+ with:
15
+ fetch-depth: 1
16
+ - name: setup ruby
17
+ uses: ruby/setup-ruby@v1
18
+ with:
19
+ ruby-version: 2.7
20
+ - name: create lock
21
+ run: bundle lock
22
+ # install java
23
+ - uses: actions/setup-java@v3
24
+ with:
25
+ distribution: 'temurin' # See 'Supported distributions' for available options
26
+ java-version: '17'
27
+ # download mend
28
+ - name: download_mend
29
+ run: curl -o wss-unified-agent.jar https://unified-agent.s3.amazonaws.com/wss-unified-agent.jar
30
+ - name: run mend
31
+ run: java -jar wss-unified-agent.jar
32
+ env:
33
+ WS_APIKEY: ${{ secrets.MEND_API_KEY }}
34
+ WS_WSS_URL: https://saas-eu.whitesourcesoftware.com/agent
35
+ WS_USERKEY: ${{ secrets.MEND_TOKEN }}
36
+ WS_PRODUCTNAME: Puppet Enterprise
37
+ WS_PROJECTNAME: ${{ github.event.repository.name }}
@@ -0,0 +1,286 @@
1
+ require 'openssl'
2
+ require 'optparse'
3
+ require 'time'
4
+ require 'puppetserver/ca/certificate_authority'
5
+ require 'puppetserver/ca/config/puppet'
6
+ require 'puppetserver/ca/errors'
7
+ require 'puppetserver/ca/utils/cli_parsing'
8
+ require 'puppetserver/ca/utils/file_system'
9
+ require 'puppetserver/ca/utils/inventory'
10
+ require 'puppetserver/ca/x509_loader'
11
+
12
+ module Puppetserver
13
+ module Ca
14
+ module Action
15
+ class Delete
16
+ include Puppetserver::Ca::Utils
17
+
18
+ CERTNAME_BLOCKLIST = %w{--config --expired --revoked --all}
19
+
20
+ SUMMARY = 'Delete signed certificate(s) from disk'
21
+ BANNER = <<-BANNER
22
+ Usage:
23
+ puppetserver ca delete [--help]
24
+ puppetserver ca delete [--config CONF] [--expired] [--revoked]
25
+ [--certname NAME[,NAME]] [--all]
26
+
27
+ Description:
28
+ Deletes signed certificates from disk. Once a certificate is
29
+ signed and delivered to a node, it no longer necessarily needs
30
+ to be stored on disk.
31
+
32
+ Options:
33
+ BANNER
34
+
35
+ def initialize(logger)
36
+ @logger = logger
37
+ end
38
+
39
+ def self.parser(parsed = {})
40
+ OptionParser.new do |opts|
41
+ opts.banner = BANNER
42
+ opts.on('--help', 'Display this command-specific help output') do |help|
43
+ parsed['help'] = true
44
+ end
45
+ opts.on('--config CONF', 'Path to puppet.conf') do |conf|
46
+ parsed['config'] = conf
47
+ end
48
+ opts.on('--expired', 'Delete expired signed certificates') do |expired|
49
+ parsed['expired'] = true
50
+ end
51
+ opts.on('--revoked', 'Delete signed certificates that have already been revoked') do |revoked|
52
+ parsed['revoked'] = true
53
+ end
54
+ opts.on('--certname NAME[,NAME]', Array,
55
+ 'One or more comma-separated certnames for which to delete signed certificates') do |certs|
56
+ parsed['certname'] = [certs].flatten
57
+ end
58
+ opts.on('--all', 'Delete all signed certificates on disk') do |all|
59
+ parsed['all'] = true
60
+ end
61
+ end
62
+ end
63
+
64
+ def parse(args)
65
+ results = {}
66
+ parser = self.class.parser(results)
67
+
68
+ errors = CliParsing.parse_with_errors(parser, args)
69
+
70
+ if results['certname']
71
+ results['certname'].each do |certname|
72
+ if CERTNAME_BLOCKLIST.include?(certname)
73
+ errors << " Cannot manage cert named `#{certname}` from "+
74
+ "the CLI. If needed, use the HTTP API directly."
75
+ end
76
+ end
77
+ end
78
+
79
+ unless results['help'] || results['expired'] || results['revoked'] || results['certname'] || results['all']
80
+ errors << ' Must pass one of the valid flags to determine which certs to delete'
81
+ end
82
+
83
+ if results['all'] && (results['expired'] || results['revoked'] || results['certname'])
84
+ errors << ' The --all flag must not be used with --expired, --revoked, or --certname'
85
+ end
86
+
87
+ errors_were_handled = Errors.handle_with_usage(@logger, errors, parser.help)
88
+
89
+ exit_code = errors_were_handled ? 1 : nil
90
+ return results, exit_code
91
+ end
92
+
93
+ def run(args)
94
+ config = args['config']
95
+
96
+ # Validate the config path
97
+ if config
98
+ errors = FileSystem.validate_file_paths(config)
99
+ return 1 if Errors.handle_with_usage(@logger, errors)
100
+ end
101
+
102
+ # Validate puppet config setting
103
+ puppet = Config::Puppet.parse(config, @logger)
104
+ settings = puppet.settings
105
+ return 1 if Errors.handle_with_usage(@logger, puppet.errors)
106
+
107
+ # Validate that we are offline
108
+ return 1 if HttpClient.check_server_online(settings, @logger)
109
+
110
+ # Perform the desired action, keeping track if any errors occurred
111
+ errored = false
112
+ deleted_count = 0
113
+ cadir = settings[:cadir]
114
+ inventory_file_path = File.join(cadir, 'inventory.txt')
115
+
116
+ # Because --revoke has a potentially fatal error it can throw,
117
+ # process it first.
118
+ if args['revoked']
119
+ loader = X509Loader.new(settings[:cacert], settings[:cakey], settings[:cacrl])
120
+ verified_crls = loader.crls.select { |crl| crl.verify(loader.key) }
121
+ unless verified_crls.length == 1
122
+ @logger.err("Could not identify Puppet's CRL. Aborting delete action.")
123
+ return 1
124
+ end
125
+ crl = verified_crls.first
126
+
127
+ # First, search the inventory for the revoked serial.
128
+ # If it matches the current serial for the cert, delete the cert.
129
+ # If it is an old serial for the certname, verify the file on disk
130
+ # is not the serial that was revoked, and then ignore it. If the
131
+ # file on disk does match that serial, delete it.
132
+ # If it isn't in the inventory, fall back to searching every cert on
133
+ # disk for the given serial.
134
+ inventory, err = Inventory.parse_inventory_file(inventory_file_path, @logger)
135
+ revoked_serials = crl.revoked.map { |r| r.serial.to_i }
136
+ to_delete = []
137
+ revoked_serials.each do |revoked_serial|
138
+ current_serial = inventory.find { |k,v| v[:serial] == revoked_serial }
139
+ old_serial = inventory.find { |k,v| v[:old_serials].include?(revoked_serial) }
140
+ if current_serial
141
+ @logger.debug("#{revoked_serial} is the current serial for #{current_serial.first}")
142
+ to_delete << current_serial.first
143
+ elsif old_serial
144
+ @logger.debug("#{revoked_serial} appears to be an old serial for #{old_serial.first}. Verifying cert on disk is not the revoked serial.")
145
+ begin
146
+ serial = get_cert_serial("#{cadir}/signed/#{old_serial.first}.pem")
147
+ # This should never happen unless someone has messed with
148
+ # the inventory.txt file or replaced the cert on disk with
149
+ # an old one.
150
+ to_delete << old_serial.first if serial == revoked_serial
151
+ rescue Exception => e
152
+ @logger.err("Error reading serial from certificate for #{old_serial.first} with exception #{e}")
153
+ errored = true
154
+ end
155
+ else
156
+ @logger.debug("Could not find #{revoked_serial} in inventory.txt. Searching certs on disk for this serial.")
157
+ begin
158
+ certname = find_cert_with_serial(cadir, revoked_serial)
159
+ if certname
160
+ to_delete << certname
161
+ else
162
+ @logger.err("Could not find serial #{revoked_serial} in inventory.txt or in any certificate file currently on disk.")
163
+ errored = true
164
+ end
165
+ rescue Exception => e
166
+ @logger.err("Error reading serial from certificates when trying to find certificate with serial #{revoked_serial} with exception #{e}")
167
+ errored = true
168
+ end
169
+ end
170
+ end
171
+ # Because the CRL will likely contain certs that no longer exist on disk,
172
+ # don't show an error if we can't find the file.
173
+ count, err = delete_certs(cadir, to_delete, false)
174
+ errored ||= err
175
+ deleted_count += count
176
+ end
177
+
178
+ if args['expired']
179
+ # Delete expired certs found in inventory first since this is cheaper.
180
+ # Then, look for any certs not in the inventory, check if they
181
+ # are expired, then delete those.
182
+ inventory, err = Inventory.parse_inventory_file(inventory_file_path, @logger)
183
+ errored ||= err
184
+ expired_in_inventory = inventory.select { |k,v| v[:not_after] < Time.now }.map(&:first)
185
+ # Don't print errors if the cert is not found, since the inventory
186
+ # file can contain old entries that have already been deleted.
187
+ count, err = delete_certs(cadir, expired_in_inventory, false)
188
+ deleted_count += count
189
+ errored ||= err
190
+ other_certs_to_check = find_certs_not_in_inventory(cadir, inventory.map(&:first))
191
+ count, err = delete_expired_certs(cadir, other_certs_to_check)
192
+ deleted_count += count
193
+ errored ||= err
194
+ end
195
+
196
+ if args['certname']
197
+ count, errored = delete_certs(cadir, args['certname'])
198
+ deleted_count += count
199
+ end
200
+
201
+ if args['all']
202
+ certnames = Dir.glob("#{cadir}/signed/*.pem").map{ |c| File.basename(c, '.pem') }
203
+ # Since we don't run this with any other flags, we can set these variables directly
204
+ deleted_count, errored = delete_certs(cadir, certnames)
205
+ end
206
+
207
+ plural = deleted_count == 1 ? "" : "s"
208
+ @logger.inform("#{deleted_count} certificate#{plural} deleted.")
209
+ # If encountered non-fatal errors (an invalid entry in inventory.txt, cert not existing on disk)
210
+ # return 24. Returning 1 should be for fatal errors where we could not do any part of the action.
211
+ return errored ? 24 : 0
212
+ end
213
+
214
+ def find_certs_not_in_inventory(cadir, inventory_certnames)
215
+ all_cert_files = Dir.glob("#{cadir}/signed/*.pem").map { |f| File.basename(f, '.pem') }
216
+ all_cert_files - inventory_certnames
217
+ end
218
+
219
+ def delete_certs(cadir, certnames, error_on_not_found = true)
220
+ deleted = 0
221
+ errored = false
222
+ certnames.each do |cert|
223
+ path = "#{cadir}/signed/#{cert}.pem"
224
+ if File.exist?(path)
225
+ @logger.inform("Deleting certificate at #{path}")
226
+ File.delete(path)
227
+ deleted += 1
228
+ else
229
+ if error_on_not_found
230
+ @logger.err("Could not find certificate file at #{path}")
231
+ errored = true
232
+ end
233
+ end
234
+ end
235
+ [deleted, errored]
236
+ end
237
+
238
+ def delete_expired_certs(cadir, certnames)
239
+ deleted = 0
240
+ errored = false
241
+ files = certnames.map { |c| "#{cadir}/signed/#{c}.pem" }
242
+ files.each do |f|
243
+ # Shouldn't really be possible since we look for certs on disk
244
+ # before calling this function, but just in case.
245
+ unless File.exist?(f)
246
+ @logger.err("Could not find certificate file at #{f}")
247
+ errored = true
248
+ next
249
+ end
250
+ begin
251
+ cert = OpenSSL::X509::Certificate.new(File.read(f))
252
+ rescue OpenSSL::X509::CertificateError
253
+ @logger.err("Error reading certificate at #{f}")
254
+ errored = true
255
+ next
256
+ end
257
+ if cert.not_after < Time.now
258
+ @logger.inform("Deleting certificate at #{f}")
259
+ File.delete(f)
260
+ deleted += 1
261
+ end
262
+ end
263
+ [deleted, errored]
264
+ end
265
+
266
+ def get_cert_serial(file)
267
+ cert = OpenSSL::X509::Certificate.new(File.read(file))
268
+ cert.serial.to_i
269
+ end
270
+
271
+ def find_cert_with_serial(cadir, serial)
272
+ files = Dir.glob("#{cadir}/signed/*.pem")
273
+ files.each do |f|
274
+ begin
275
+ s = get_cert_serial(f)
276
+ return File.basename(f, '.pem') if s == serial # Remove .pem
277
+ rescue Exception => e
278
+ @logger.debug("Error reading certificate at #{f} with exception #{e}. Skipping this file.")
279
+ end
280
+ end
281
+ return nil
282
+ end
283
+ end
284
+ end
285
+ end
286
+ end
@@ -6,6 +6,7 @@ require 'puppetserver/ca/utils/cli_parsing'
6
6
  require 'puppetserver/ca/utils/file_system'
7
7
  require 'puppetserver/ca/utils/config'
8
8
  require 'puppetserver/ca/x509_loader'
9
+ require 'puppetserver/ca/config/puppet'
9
10
 
10
11
  module Puppetserver
11
12
  module Ca
@@ -13,15 +14,22 @@ module Puppetserver
13
14
  class Prune
14
15
  include Puppetserver::Ca::Utils
15
16
 
16
- SUMMARY = "Prune the local CRL on disk to remove any duplicated certificates"
17
+ SUMMARY = "Prune the local CRL on disk to remove certificate entries"
17
18
  BANNER = <<-BANNER
18
19
  Usage:
19
20
  puppetserver ca prune [--help]
20
21
  puppetserver ca prune [--config]
22
+ puppetserver ca prune [--config] [--remove-duplicates]
23
+ puppetserver ca prune [--config] [--remove-expired]
24
+ puppetserver ca prune [--config] [--remove-entries] [--serial NUMBER[,NUMBER]] [--certname NAME[,NAME]]
21
25
 
22
26
  Description:
23
- Prune the list of revoked certificates of any duplication within it. This command
24
- will only prune the CRL issued by Puppet's CA cert.
27
+ Prune the list of revoked certificates. If no options are provided or
28
+ --remove-duplicates is specified, prune CRL of any duplicate entries.
29
+ If --remove-expired is specified, remove expired entries from CRL.
30
+ If --remove-entries is specified, remove matching entries provided by
31
+ --serial and/or --certname values. This command will only prune the CRL
32
+ issued by Puppet's CA cert.
25
33
 
26
34
  Options:
27
35
  BANNER
@@ -32,6 +40,11 @@ BANNER
32
40
 
33
41
  def run(inputs)
34
42
  config_path = inputs['config']
43
+ remove_duplicates = inputs['remove-duplicates']
44
+ remove_expired = inputs['remove-expired']
45
+ remove_entries = inputs['remove-entries']
46
+ serialnumbers = inputs['serial']
47
+ certnames = inputs['certname']
35
48
  exit_code = 0
36
49
 
37
50
  # Validate the config path.
@@ -45,31 +58,57 @@ BANNER
45
58
  puppet.load(logger: @logger)
46
59
  return 1 if Errors.handle_with_usage(@logger, puppet.errors)
47
60
 
61
+ # Validate arguments
62
+ if (remove_entries && (!serialnumbers && !certnames))
63
+ return 1 if Errors.handle_with_usage(@logger,["--remove-entries option require --serial or --certname values"])
64
+ end
65
+
48
66
  # Validate that we are offline
49
67
  return 1 if HttpClient.check_server_online(puppet.settings, @logger)
50
68
 
51
69
  # Getting the CRL(s)
52
70
  loader = X509Loader.new(puppet.settings[:cacert], puppet.settings[:cakey], puppet.settings[:cacrl])
71
+ inventory_file = puppet.settings[:cert_inventory]
72
+ cadir = puppet.settings[:cadir]
53
73
 
54
74
  verified_crls = loader.crls.select { |crl| crl.verify(loader.key) }
75
+ number_of_removed_duplicates = 0
76
+ number_of_removed_crl_entries = 0
55
77
 
56
78
  if verified_crls.length == 1
57
79
  puppet_crl = verified_crls.first
58
80
  @logger.inform("Total number of certificates found in Puppet's CRL is: #{puppet_crl.revoked.length}.")
59
- number_of_removed_duplicates = prune_CRL(puppet_crl)
60
81
 
61
- if number_of_removed_duplicates > 0
82
+ if remove_entries
83
+ if serialnumbers
84
+ number_of_removed_crl_entries += prune_using_serial(puppet_crl, loader.key, serialnumbers)
85
+ end
86
+ if certnames
87
+ number_of_removed_crl_entries += prune_using_certname(puppet_crl, loader.key, inventory_file, cadir, certnames)
88
+ end
89
+ end
90
+
91
+ if remove_expired
92
+ number_of_removed_crl_entries += prune_expired(puppet_crl, loader.key, inventory_file, cadir)
93
+ end
94
+
95
+ if (remove_duplicates || (!remove_entries && !remove_expired))
96
+ number_of_removed_duplicates += prune_CRL(puppet_crl)
97
+ end
98
+
99
+
100
+ if (number_of_removed_duplicates > 0 || number_of_removed_crl_entries > 0)
62
101
  update_pruned_CRL(puppet_crl, loader.key)
63
102
  FileSystem.write_file(puppet.settings[:cacrl], loader.crls, 0644)
64
- @logger.inform("Removed #{number_of_removed_duplicates} duplicated certs from Puppet's CRL.")
103
+ @logger.inform("Removed #{number_of_removed_duplicates} duplicated certs from Puppet's CRL.") if number_of_removed_duplicates > 0
104
+ @logger.inform("Removed #{number_of_removed_crl_entries} certs from Puppet's CRL.") if number_of_removed_crl_entries > 0
65
105
  else
66
- @logger.inform("No duplicate revocations found in the CRL.")
106
+ @logger.inform("No matching revocations found in the CRL for pruning")
67
107
  end
68
108
  else
69
109
  @logger.err("Could not identify Puppet's CRL. Aborting prune action.")
70
110
  exit_code = 1
71
111
  end
72
-
73
112
  return exit_code
74
113
  end
75
114
 
@@ -106,6 +145,89 @@ BANNER
106
145
  crl.sign(pkey, OpenSSL::Digest::SHA256.new)
107
146
  end
108
147
 
148
+ def prune_using_serial(crl, key, serialnumbers)
149
+ removed_serials = []
150
+ revoked_list = crl.revoked
151
+ @logger.debug("Removing entries in CRL for issuer " \
152
+ "#{crl.issuer.to_s(OpenSSL::X509::Name::RFC2253)}") if @logger.debug?
153
+ serialnumbers.each do |serial|
154
+ if serial.match(/^(?:0[xX])?[A-Fa-f0-9]+$/)
155
+ revoked_list.delete_if do |revoked|
156
+ if revoked.serial == OpenSSL::BN.new(serial.hex)
157
+ removed_serials.push(serial)
158
+ true
159
+ end
160
+ end
161
+ end
162
+ end
163
+ crl.revoked = (revoked_list)
164
+ @logger.debug("Removed these CRL entries : #{removed_serials}") if @logger.debug?
165
+ return removed_serials.length
166
+ end
167
+
168
+ def prune_using_certname(crl, key, inventory_file, cadir, certnames)
169
+ serialnumbers = []
170
+ @logger.debug("Checking inventory file #{inventory_file} for matching cert names") if @logger.debug?
171
+ errors = FileSystem.validate_file_paths(inventory_file)
172
+ if errors.empty?
173
+ File.open(inventory_file).each_line do |line|
174
+ certnames.each do |certname|
175
+ if line.match(/\/CN=#{certname}$/) && line.split.length == 4
176
+ serialnumbers.push(line.split.first)
177
+ certnames.delete(certname)
178
+ end
179
+ end
180
+ end
181
+ else
182
+ @logger.warn "Reading inventory file at #{inventory_file} failed with error #{errors}"
183
+ end
184
+ if certnames
185
+ @logger.debug("Checking CA dir #{cadir} for matching cert names")
186
+ certnames.each do |certname|
187
+ cert_file = "#{cadir}/signed/#{certname}.pem"
188
+ if File.file?(cert_file)
189
+ raw = File.read(cert_file)
190
+ certificate = OpenSSL::X509::Certificate.new(raw)
191
+ serial = certificate.serial
192
+ serialnumbers.push(serial.to_s(16))
193
+ end
194
+ end
195
+ prune_using_serial(crl, key, serialnumbers)
196
+ end
197
+ end
198
+
199
+ def prune_expired (crl, key, inventory_file, cadir)
200
+ serialnumbers = []
201
+ signed_dir = "#{cadir}/signed"
202
+ @logger.debug("Checking inventory file #{inventory_file} for expired entries") if @logger.debug?
203
+ errors = FileSystem.validate_file_paths(inventory_file)
204
+ if errors.empty?
205
+ File.open(inventory_file).each_line do |line|
206
+ if line.match(/\/CN=.*$/) && line.split.length == 4
207
+ not_after = line.split[2]
208
+ begin
209
+ not_after = Time.parse(line.split[2])
210
+ serialnumbers.push(line.split.first) if not_after < Time.now
211
+ rescue ArgumentError
212
+ @logger.warn "Invalid not_after time found in inventory.txt file at #{line}"
213
+ next
214
+ end
215
+ end
216
+ end
217
+ else
218
+ @logger.warn "Reading inventory file at #{inventory_file} failed with error #{errors}"
219
+ end
220
+ @logger.debug("Checking CA dir #{cadir} for expired certs")
221
+ Dir.foreach(signed_dir) do |filename|
222
+ if File.extname(filename) == '.pem'
223
+ raw = File.read("#{signed_dir}/#{filename}")
224
+ certificate = OpenSSL::X509::Certificate.new(raw)
225
+ serialnumbers.push(certificate.serial.to_s(16)) if certificate.not_after < Time.now
226
+ end
227
+ end
228
+ prune_using_serial(crl, key, serialnumbers)
229
+ end
230
+
109
231
  def self.parser(parsed = {})
110
232
  OptionParser.new do |opts|
111
233
  opts.banner = BANNER
@@ -115,6 +237,21 @@ BANNER
115
237
  opts.on('--config CONF', 'Path to the puppet.conf file on disk') do |conf|
116
238
  parsed['config'] = conf
117
239
  end
240
+ opts.on('--remove-duplicates', 'Remove duplicate entries from CRL(default)') do |remove_duplicates|
241
+ parsed['remove-duplicates'] = true
242
+ end
243
+ opts.on('--remove-expired', 'Remove expired entries from CRL') do |remove_expired|
244
+ parsed['remove-expired'] = true
245
+ end
246
+ opts.on('--remove-entries', 'Remove entries from CRL') do |remove_entries|
247
+ parsed['remove-entries'] = true
248
+ end
249
+ opts.on('--serial NUMBER[,NUMBER]', Array, 'Serial numbers(s) in HEX to be removed from CRL') do |serialnumbers|
250
+ parsed['serial'] = serialnumbers
251
+ end
252
+ opts.on('--certname NAME[,NAME]', Array, 'Name(s) of the cert(s) to be removed from CRL') do |certnames|
253
+ parsed['certname'] = certnames
254
+ end
118
255
  end
119
256
  end
120
257
 
@@ -134,4 +271,4 @@ BANNER
134
271
  end
135
272
  end
136
273
  end
137
- end
274
+ end
@@ -1,6 +1,7 @@
1
1
  require 'optparse'
2
2
 
3
3
  require 'puppetserver/ca/action/clean'
4
+ require 'puppetserver/ca/action/delete'
4
5
  require 'puppetserver/ca/action/generate'
5
6
  require 'puppetserver/ca/action/import'
6
7
  require 'puppetserver/ca/action/enable'
@@ -27,6 +28,7 @@ Puppet Server's built-in Certificate Authority
27
28
  BANNER
28
29
 
29
30
  ADMIN_ACTIONS = {
31
+ 'delete' => Action::Delete,
30
32
  'import' => Action::Import,
31
33
  'setup' => Action::Setup,
32
34
  'enable' => Action::Enable,
@@ -63,14 +63,14 @@ module Puppetserver
63
63
  # and if neither exist we generate a new key. This logic is necessary for
64
64
  # proper bootstrapping for certain server workflows.
65
65
  def create_private_key(keylength, private_path = '', public_path = '')
66
- if File.exists?(private_path) && File.exists?(public_path)
66
+ if File.exist?(private_path) && File.exist?(public_path)
67
67
  return OpenSSL::PKey.read(File.read(private_path))
68
- elsif !File.exists?(private_path) && !File.exists?(public_path)
68
+ elsif !File.exist?(private_path) && !File.exist?(public_path)
69
69
  return OpenSSL::PKey::RSA.new(keylength)
70
- elsif !File.exists?(private_path) && File.exists?(public_path)
70
+ elsif !File.exist?(private_path) && File.exist?(public_path)
71
71
  @errors << "Missing private key to match public key at #{public_path}"
72
72
  return nil
73
- elsif File.exists?(private_path) && !File.exists?(public_path)
73
+ elsif File.exist?(private_path) && !File.exist?(public_path)
74
74
  @errors << "Missing public key to match private key at #{private_path}"
75
75
  return nil
76
76
  end
@@ -0,0 +1,84 @@
1
+ require 'time'
2
+
3
+ module Puppetserver
4
+ module Ca
5
+ module Utils
6
+ module Inventory
7
+
8
+ # Note that the inventory file may have multiple entries for the same certname,
9
+ # so it should only provide the latest cert for the given certname.
10
+ def self.parse_inventory_file(path, logger)
11
+ unless File.exist?(path)
12
+ logger.err("Could not find inventory at #{path}")
13
+ return [{}, true]
14
+ end
15
+ inventory = {}
16
+ errored = false
17
+ File.readlines(path).each do |line|
18
+ # Shouldn't be any blank lines, but skip them if there are
19
+ next if line.strip.empty?
20
+
21
+ items = line.strip.split
22
+ if items.count != 4
23
+ logger.err("Invalid entry found in inventory.txt: #{line}")
24
+ errored = true
25
+ next
26
+ end
27
+ unless items[0].match(/^(?:0x)?[A-Fa-f0-9]+$/)
28
+ logger.err("Invalid serial found in inventory.txt line: #{line}")
29
+ errored = true
30
+ next
31
+ end
32
+ serial = items[0].hex
33
+ not_before = nil
34
+ not_after = nil
35
+ begin
36
+ not_before = Time.parse(items[1])
37
+ rescue ArgumentError
38
+ logger.err("Invalid not_before time found in inventory.txt line: #{line}")
39
+ errored = true
40
+ next
41
+ end
42
+ begin
43
+ not_after = Time.parse(items[2])
44
+ rescue ArgumentError
45
+ logger.err("Invalid not_after time found in inventory.txt line: #{line}")
46
+ errored = true
47
+ next
48
+ end
49
+ unless items[3].start_with?('/CN=')
50
+ logger.err("Invalid certname found in inventory.txt line: #{line}")
51
+ errored = true
52
+ next
53
+ end
54
+ certname = items[3][4..-1]
55
+
56
+ if !inventory.keys.include?(certname)
57
+ inventory[certname] = {
58
+ :serial => serial,
59
+ :old_serials => [],
60
+ :not_before => not_before,
61
+ :not_after => not_after,
62
+ }
63
+ else
64
+ if not_after >= inventory[certname][:not_after]
65
+ # This is a newer cert than the one we currently have recorded,
66
+ # so save the previous serial in :old_serials
67
+ inventory[certname][:old_serials] << inventory[certname][:serial]
68
+ inventory[certname][:serial] = serial
69
+ inventory[certname][:not_before] = not_before
70
+ inventory[certname][:not_after] = not_after
71
+ else
72
+ # This somehow is an older cert (shouldn't really be possible as we just append
73
+ # to the file with each new cert and we are reading it order)
74
+ inventory[certname][:old_serials] << serial
75
+ end
76
+ end
77
+ end
78
+ [inventory, errored]
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
84
+
@@ -1,5 +1,5 @@
1
1
  module Puppetserver
2
2
  module Ca
3
- VERSION = "2.3.6"
3
+ VERSION = "2.5.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: 2.3.6
4
+ version: 2.5.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: 2022-03-11 00:00:00.000000000 Z
11
+ date: 2023-03-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: facter
@@ -80,6 +80,7 @@ executables:
80
80
  extensions: []
81
81
  extra_rdoc_files: []
82
82
  files:
83
+ - ".github/workflows/mend.yaml"
83
84
  - ".gitignore"
84
85
  - ".rspec"
85
86
  - ".travis.yml"
@@ -95,6 +96,7 @@ files:
95
96
  - exe/puppetserver-ca
96
97
  - lib/puppetserver/ca.rb
97
98
  - lib/puppetserver/ca/action/clean.rb
99
+ - lib/puppetserver/ca/action/delete.rb
98
100
  - lib/puppetserver/ca/action/enable.rb
99
101
  - lib/puppetserver/ca/action/generate.rb
100
102
  - lib/puppetserver/ca/action/import.rb
@@ -117,6 +119,7 @@ files:
117
119
  - lib/puppetserver/ca/utils/config.rb
118
120
  - lib/puppetserver/ca/utils/file_system.rb
119
121
  - lib/puppetserver/ca/utils/http_client.rb
122
+ - lib/puppetserver/ca/utils/inventory.rb
120
123
  - lib/puppetserver/ca/utils/signing_digest.rb
121
124
  - lib/puppetserver/ca/version.rb
122
125
  - lib/puppetserver/ca/x509_loader.rb