openvoxserver-ca 3.0.0.pre.rc1
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 +7 -0
- data/.github/dependabot.yml +17 -0
- data/.github/release.yml +41 -0
- data/.github/workflows/gem_release.yaml +106 -0
- data/.github/workflows/prepare_release.yml +28 -0
- data/.github/workflows/release.yml +28 -0
- data/.github/workflows/unit_tests.yaml +45 -0
- data/.gitignore +14 -0
- data/.rspec +2 -0
- data/.travis.yml +16 -0
- data/CHANGELOG.md +15 -0
- data/CODEOWNERS +4 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/CONTRIBUTING.md +15 -0
- data/Gemfile +20 -0
- data/LICENSE +202 -0
- data/README.md +118 -0
- data/Rakefile +30 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/exe/puppetserver-ca +10 -0
- data/lib/puppetserver/ca/action/clean.rb +109 -0
- data/lib/puppetserver/ca/action/delete.rb +286 -0
- data/lib/puppetserver/ca/action/enable.rb +140 -0
- data/lib/puppetserver/ca/action/generate.rb +330 -0
- data/lib/puppetserver/ca/action/import.rb +196 -0
- data/lib/puppetserver/ca/action/list.rb +253 -0
- data/lib/puppetserver/ca/action/migrate.rb +97 -0
- data/lib/puppetserver/ca/action/prune.rb +289 -0
- data/lib/puppetserver/ca/action/revoke.rb +108 -0
- data/lib/puppetserver/ca/action/setup.rb +188 -0
- data/lib/puppetserver/ca/action/sign.rb +146 -0
- data/lib/puppetserver/ca/certificate_authority.rb +418 -0
- data/lib/puppetserver/ca/cli.rb +145 -0
- data/lib/puppetserver/ca/config/puppet.rb +309 -0
- data/lib/puppetserver/ca/config/puppetserver.rb +84 -0
- data/lib/puppetserver/ca/errors.rb +40 -0
- data/lib/puppetserver/ca/host.rb +176 -0
- data/lib/puppetserver/ca/local_certificate_authority.rb +304 -0
- data/lib/puppetserver/ca/logger.rb +49 -0
- data/lib/puppetserver/ca/stub.rb +17 -0
- data/lib/puppetserver/ca/utils/cli_parsing.rb +67 -0
- data/lib/puppetserver/ca/utils/config.rb +61 -0
- data/lib/puppetserver/ca/utils/file_system.rb +109 -0
- data/lib/puppetserver/ca/utils/http_client.rb +232 -0
- data/lib/puppetserver/ca/utils/inventory.rb +84 -0
- data/lib/puppetserver/ca/utils/signing_digest.rb +27 -0
- data/lib/puppetserver/ca/version.rb +5 -0
- data/lib/puppetserver/ca/x509_loader.rb +170 -0
- data/lib/puppetserver/ca.rb +7 -0
- data/openvoxserver-ca.gemspec +31 -0
- data/tasks/spec.rake +15 -0
- data/tasks/vox.rake +19 -0
- metadata +154 -0
data/README.md
ADDED
@@ -0,0 +1,118 @@
|
|
1
|
+
# OpenVox Server's CA CLI Library
|
2
|
+
|
3
|
+
This gem provides the functionality behind the OpenVox Server CA interactions.
|
4
|
+
The actual CLI executable lives within the OpenVox Server project.
|
5
|
+
This is a community implementation of the `puppetserver-ca` gem.
|
6
|
+
|
7
|
+
|
8
|
+
## Installation
|
9
|
+
|
10
|
+
You may install it yourself with:
|
11
|
+
|
12
|
+
$ gem install openvoxserver-ca
|
13
|
+
|
14
|
+
|
15
|
+
## Usage
|
16
|
+
|
17
|
+
For initial CA setup, we provide two options. These need to be run before starting
|
18
|
+
Puppet Server for the first time.
|
19
|
+
|
20
|
+
To set up a default CA, with a self-signed root cert and an intermediate signing cert:
|
21
|
+
```
|
22
|
+
puppetserver ca setup
|
23
|
+
```
|
24
|
+
|
25
|
+
To import a custom CA:
|
26
|
+
```
|
27
|
+
puppetserver ca import --cert-bundle certs.pem --crl-chain crls.pem --private-key ca_key.pem
|
28
|
+
```
|
29
|
+
|
30
|
+
The remaining actions provided by this gem require a running OpenVox Server (Puppet Server), since
|
31
|
+
it primarily uses the CA's API endpoints to do its work. The following examples
|
32
|
+
assume that you are using the gem packaged within OpenVox Server.
|
33
|
+
|
34
|
+
To sign a pending certificate request:
|
35
|
+
```
|
36
|
+
puppetserver ca sign --certname foo.example.com
|
37
|
+
```
|
38
|
+
|
39
|
+
To list certificates and CSRs:
|
40
|
+
```
|
41
|
+
puppetserver ca list --all
|
42
|
+
```
|
43
|
+
|
44
|
+
To revoke a signed certificate:
|
45
|
+
```
|
46
|
+
puppetserver ca revoke --certname foo.example.com
|
47
|
+
```
|
48
|
+
|
49
|
+
To revoke the cert and clean up all SSL files for a given certname:
|
50
|
+
```
|
51
|
+
puppetserver ca clean --certname foo.example.com
|
52
|
+
```
|
53
|
+
|
54
|
+
To create a new keypair and certificate for a certname:
|
55
|
+
```
|
56
|
+
puppetserver ca generate --certname foo.example.com
|
57
|
+
```
|
58
|
+
|
59
|
+
To remove duplicated entries from Puppet's CRL:
|
60
|
+
```
|
61
|
+
puppetserver ca prune
|
62
|
+
```
|
63
|
+
|
64
|
+
To enable verbose mode:
|
65
|
+
```
|
66
|
+
puppetserver ca --verbose <action>
|
67
|
+
```
|
68
|
+
|
69
|
+
For more details, see the help output:
|
70
|
+
```
|
71
|
+
puppetserver ca --help
|
72
|
+
```
|
73
|
+
|
74
|
+
This code in this project is licensed under the Apache Software License v2,
|
75
|
+
please see the included [License](https://github.com/OpenVoxProject/openvoxserver-ca-cli/blob/main/LICENSE.md)
|
76
|
+
for more details.
|
77
|
+
|
78
|
+
|
79
|
+
## Development
|
80
|
+
|
81
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then,
|
82
|
+
run `bundle exec rake spec` to run the tests. You can also run `bin/console` for an
|
83
|
+
interactive prompt that will allow you to experiment.
|
84
|
+
|
85
|
+
To install this gem onto your local machine, run `bundle exec rake install`.
|
86
|
+
|
87
|
+
### Testing
|
88
|
+
To test your changes on a VM:
|
89
|
+
1. Build the gem with your changes: `gem build openvoxserver-ca.gemspec`
|
90
|
+
1. Copy the gem to your VM: `scp openvoxserver-ca-<version>.gem <your-vm>:.`
|
91
|
+
1. Install openvox-server by installing the relevant release package and then installing the openvox-server package. For example:
|
92
|
+
```
|
93
|
+
$ wget https://yum.voxpupuli.org/openvox8-release-el-9.noarch.rpm
|
94
|
+
$ rpm -i openvox8-release-el-9.noarch.rpm
|
95
|
+
$ yum update
|
96
|
+
$ yum install -y openvox-server
|
97
|
+
```
|
98
|
+
1. Restart your shell so that puppet's bin dir is on your $PATH: `exec bash`
|
99
|
+
1. Install the gem into puppet's gem directory using puppet's gem command:
|
100
|
+
```
|
101
|
+
$ /opt/puppetlabs/puppet/bin/gem install --install-dir "/opt/puppetlabs/puppet/lib/ruby/vendor_gems" openvoxserver-ca-<version>.gem
|
102
|
+
```
|
103
|
+
1. To confirm that installation was successful, run `puppetserver ca --help`
|
104
|
+
|
105
|
+
## Contributing & Support
|
106
|
+
|
107
|
+
Bug reports and feature requests are welcome via GitHub issues.
|
108
|
+
|
109
|
+
For interactive questions feel free to post to #puppet or #puppet-dev on the Puppet Community Slack channel.
|
110
|
+
|
111
|
+
Contributions are welcome at https://github.com/OpenVoxProject/openvoxserver-ca-cli/pulls.
|
112
|
+
Contributors should both be sure to read the
|
113
|
+
[contributing document](https://github.com/OpenVoxProject/openvoxserver-ca-cli/blob/main/CONTRIBUTING.md)
|
114
|
+
and sign the [contributor license agreement](https://cla.puppet.com/).
|
115
|
+
|
116
|
+
Everyone interacting with the project’s codebase, issue tracker, etc is expected
|
117
|
+
to follow the
|
118
|
+
[code of conduct](https://github.com/OpenVoxProject/openvoxserver-ca-cli/blob/main/CODE_OF_CONDUCT.md).
|
data/Rakefile
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
2
|
+
|
3
|
+
Dir.glob(File.join('tasks/**/*.rake')).each { |file| load file }
|
4
|
+
|
5
|
+
task default: :spec
|
6
|
+
|
7
|
+
begin
|
8
|
+
require 'github_changelog_generator/task'
|
9
|
+
require_relative 'lib/puppetserver/ca/version'
|
10
|
+
|
11
|
+
GitHubChangelogGenerator::RakeTask.new :changelog do |config|
|
12
|
+
config.header = <<~HEADER.chomp
|
13
|
+
# Changelog
|
14
|
+
|
15
|
+
All notable changes to this project will be documented in this file.
|
16
|
+
HEADER
|
17
|
+
config.user = 'openvoxproject'
|
18
|
+
config.project = 'openvoxserver-ca'
|
19
|
+
config.exclude_labels = %w[dependencies duplicate question invalid wontfix wont-fix modulesync skip-changelog]
|
20
|
+
config.future_release = Puppetserver::Ca::VERSION
|
21
|
+
config.since_tag = '2.7.0'
|
22
|
+
end
|
23
|
+
rescue LoadError
|
24
|
+
task :changelog do
|
25
|
+
abort("Run `bundle install --with release` to install the `github_changelog_generator` gem.")
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
desc 'Prepare for a release'
|
30
|
+
task 'release:prepare' => [:changelog]
|
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "puppetserver/ca"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
data/exe/puppetserver-ca
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# This requires everything in our Gemfile, so when functionally testing
|
4
|
+
# our debugging tools are available without having to require them
|
5
|
+
require 'bundler/setup'
|
6
|
+
Bundler.require(:default)
|
7
|
+
|
8
|
+
require 'puppetserver/ca/cli'
|
9
|
+
|
10
|
+
exit Puppetserver::Ca::Cli.run(ARGV)
|
@@ -0,0 +1,109 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
|
3
|
+
require 'puppetserver/ca/action/revoke'
|
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
|
+
|
10
|
+
module Puppetserver
|
11
|
+
module Ca
|
12
|
+
module Action
|
13
|
+
class Clean
|
14
|
+
|
15
|
+
include Puppetserver::Ca::Utils
|
16
|
+
|
17
|
+
CERTNAME_BLOCKLIST = %w{--all --config}
|
18
|
+
|
19
|
+
SUMMARY = 'Revoke cert(s) and remove related files from CA'
|
20
|
+
BANNER = <<-BANNER
|
21
|
+
Usage:
|
22
|
+
puppetserver ca clean [--help]
|
23
|
+
puppetserver ca clean [--config] --certname NAME[,NAME]
|
24
|
+
|
25
|
+
Description:
|
26
|
+
Given one or more valid certnames, instructs the CA to revoke certificates
|
27
|
+
matching the given certnames if they exist, and then remove files pertaining
|
28
|
+
to them (keys, cert, and certificate request) over HTTPS using the local
|
29
|
+
agent's PKI
|
30
|
+
|
31
|
+
Options:
|
32
|
+
BANNER
|
33
|
+
|
34
|
+
def self.parser(parsed = {})
|
35
|
+
parsed['certnames'] = []
|
36
|
+
OptionParser.new do |o|
|
37
|
+
o.banner = BANNER
|
38
|
+
o.on('--certname NAME[,NAME]', Array,
|
39
|
+
'One or more comma separated certnames') do |certs|
|
40
|
+
parsed['certnames'] += certs
|
41
|
+
end
|
42
|
+
o.on('--config CONF', 'Custom path to puppet.conf') do |conf|
|
43
|
+
parsed['config'] = conf
|
44
|
+
end
|
45
|
+
o.on('--help', 'Display this command-specific help output') do |help|
|
46
|
+
parsed['help'] = true
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def initialize(logger)
|
52
|
+
@logger = logger
|
53
|
+
end
|
54
|
+
|
55
|
+
def parse(args)
|
56
|
+
results = {}
|
57
|
+
parser = self.class.parser(results)
|
58
|
+
|
59
|
+
errors = CliParsing.parse_with_errors(parser, args)
|
60
|
+
|
61
|
+
results['certnames'].each do |certname|
|
62
|
+
if CERTNAME_BLOCKLIST.include?(certname)
|
63
|
+
errors << " Cannot manage cert named `#{certname}` from " +
|
64
|
+
"the CLI, if needed use the HTTP API directly"
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
if results['certnames'].empty?
|
69
|
+
errors << ' At least one certname is required to clean'
|
70
|
+
end
|
71
|
+
|
72
|
+
errors_were_handled = Errors.handle_with_usage(@logger, errors, parser.help)
|
73
|
+
|
74
|
+
exit_code = errors_were_handled ? 1 : nil
|
75
|
+
|
76
|
+
return results, exit_code
|
77
|
+
end
|
78
|
+
|
79
|
+
def run(args)
|
80
|
+
certnames = args['certnames']
|
81
|
+
config = args['config']
|
82
|
+
|
83
|
+
if config
|
84
|
+
errors = FileSystem.validate_file_paths(config)
|
85
|
+
return 1 if Errors.handle_with_usage(@logger, errors)
|
86
|
+
end
|
87
|
+
|
88
|
+
puppet = Config::Puppet.parse(config, @logger)
|
89
|
+
return 1 if Errors.handle_with_usage(@logger, puppet.errors)
|
90
|
+
|
91
|
+
result = clean_certs(certnames, puppet.settings)
|
92
|
+
case result
|
93
|
+
when :success
|
94
|
+
return 0
|
95
|
+
when :invalid
|
96
|
+
return 24
|
97
|
+
when :not_found, :error
|
98
|
+
return 1
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def clean_certs(certnames, settings)
|
103
|
+
ca = Puppetserver::Ca::CertificateAuthority.new(@logger, settings)
|
104
|
+
ca.clean_certs(certnames)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
@@ -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
|