puppetserver-ca 0.3.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/puppetserver/ca/action/clean.rb +102 -0
- data/lib/puppetserver/ca/action/create.rb +161 -0
- data/lib/puppetserver/ca/action/generate.rb +313 -0
- data/lib/puppetserver/ca/action/import.rb +132 -0
- data/lib/puppetserver/ca/action/list.rb +132 -0
- data/lib/puppetserver/ca/action/revoke.rb +101 -0
- data/lib/puppetserver/ca/action/sign.rb +126 -0
- data/lib/puppetserver/ca/certificate_authority.rb +224 -0
- data/lib/puppetserver/ca/cli.rb +17 -16
- data/lib/puppetserver/ca/config/puppet.rb +242 -0
- data/lib/puppetserver/ca/config/puppetserver.rb +85 -0
- data/lib/puppetserver/ca/utils/cli_parsing.rb +82 -0
- data/lib/puppetserver/ca/utils/config.rb +13 -0
- data/lib/puppetserver/ca/utils/file_system.rb +90 -0
- data/lib/puppetserver/ca/utils/http_client.rb +129 -0
- data/lib/puppetserver/ca/utils/signing_digest.rb +27 -0
- data/lib/puppetserver/ca/version.rb +1 -1
- metadata +17 -17
- data/lib/puppetserver/ca/clean_action.rb +0 -157
- data/lib/puppetserver/ca/config_utils.rb +0 -11
- data/lib/puppetserver/ca/create_action.rb +0 -265
- data/lib/puppetserver/ca/generate_action.rb +0 -227
- data/lib/puppetserver/ca/import_action.rb +0 -153
- data/lib/puppetserver/ca/list_action.rb +0 -153
- data/lib/puppetserver/ca/puppet_config.rb +0 -197
- data/lib/puppetserver/ca/puppetserver_config.rb +0 -83
- data/lib/puppetserver/ca/revoke_action.rb +0 -136
- data/lib/puppetserver/ca/sign_action.rb +0 -190
- data/lib/puppetserver/ca/utils.rb +0 -80
- data/lib/puppetserver/settings/ttl_setting.rb +0 -48
- data/lib/puppetserver/utils/file_utilities.rb +0 -78
- data/lib/puppetserver/utils/http_client.rb +0 -129
- data/lib/puppetserver/utils/signing_digest.rb +0 -25
@@ -0,0 +1,132 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
require 'puppetserver/ca/utils/file_system'
|
3
|
+
require 'puppetserver/ca/x509_loader'
|
4
|
+
require 'puppetserver/ca/config/puppet'
|
5
|
+
require 'puppetserver/ca/utils/cli_parsing'
|
6
|
+
|
7
|
+
module Puppetserver
|
8
|
+
module Ca
|
9
|
+
module Action
|
10
|
+
class Import
|
11
|
+
include Puppetserver::Ca::Utils
|
12
|
+
|
13
|
+
SUMMARY = "Import the CA's key, certs, and crls"
|
14
|
+
BANNER = <<-BANNER
|
15
|
+
Usage:
|
16
|
+
puppetserver ca import [--help]
|
17
|
+
puppetserver ca import [--config PATH]
|
18
|
+
--private-key PATH --cert-bundle PATH --crl-chain PATH
|
19
|
+
|
20
|
+
Description:
|
21
|
+
Given a private key, cert bundle, and a crl chain,
|
22
|
+
validate and import to the Puppet Server CA.
|
23
|
+
|
24
|
+
To determine the target location the default puppet.conf
|
25
|
+
is consulted for custom values. If using a custom puppet.conf
|
26
|
+
provide it with the --config flag
|
27
|
+
|
28
|
+
Options:
|
29
|
+
BANNER
|
30
|
+
|
31
|
+
def initialize(logger)
|
32
|
+
@logger = logger
|
33
|
+
end
|
34
|
+
|
35
|
+
def run(input)
|
36
|
+
bundle_path = input['cert-bundle']
|
37
|
+
key_path = input['private-key']
|
38
|
+
chain_path = input['crl-chain']
|
39
|
+
config_path = input['config']
|
40
|
+
|
41
|
+
files = [bundle_path, key_path, chain_path, config_path].compact
|
42
|
+
|
43
|
+
errors = FileSystem.validate_file_paths(files)
|
44
|
+
return 1 if CliParsing.handle_errors(@logger, errors)
|
45
|
+
|
46
|
+
loader = X509Loader.new(bundle_path, key_path, chain_path)
|
47
|
+
return 1 if CliParsing.handle_errors(@logger, loader.errors)
|
48
|
+
|
49
|
+
puppet = Config::Puppet.parse(config_path)
|
50
|
+
return 1 if CliParsing.handle_errors(@logger, puppet.errors)
|
51
|
+
|
52
|
+
target_locations = [puppet.settings[:cacert],
|
53
|
+
puppet.settings[:cakey],
|
54
|
+
puppet.settings[:cacrl],
|
55
|
+
puppet.settings[:serial],
|
56
|
+
puppet.settings[:cert_inventory]]
|
57
|
+
errors = FileSystem.check_for_existing_files(target_locations)
|
58
|
+
if !errors.empty?
|
59
|
+
instructions = <<-ERR
|
60
|
+
If you would really like to replace your CA, please delete the existing files first.
|
61
|
+
Note that any certificates that were issued by this CA will become invalid if you
|
62
|
+
replace it!
|
63
|
+
ERR
|
64
|
+
errors << instructions
|
65
|
+
CliParsing.handle_errors(@logger, errors)
|
66
|
+
return 1
|
67
|
+
end
|
68
|
+
|
69
|
+
FileSystem.ensure_dir(puppet.settings[:cadir])
|
70
|
+
|
71
|
+
FileSystem.write_file(puppet.settings[:cacert], loader.certs, 0640)
|
72
|
+
|
73
|
+
FileSystem.write_file(puppet.settings[:cakey], loader.key, 0640)
|
74
|
+
|
75
|
+
FileSystem.write_file(puppet.settings[:cacrl], loader.crls, 0640)
|
76
|
+
|
77
|
+
# Puppet's internal CA expects these file to exist.
|
78
|
+
FileSystem.ensure_file(puppet.settings[:serial], "0x0001", 0640)
|
79
|
+
FileSystem.ensure_file(puppet.settings[:cert_inventory], "", 0640)
|
80
|
+
|
81
|
+
@logger.inform "Import succeeded. Find your files in #{puppet.settings[:cadir]}"
|
82
|
+
return 0
|
83
|
+
end
|
84
|
+
|
85
|
+
def check_flag_usage(results)
|
86
|
+
if results['cert-bundle'].nil? || results['private-key'].nil? || results['crl-chain'].nil?
|
87
|
+
' Missing required argument' + "\n" +
|
88
|
+
' --cert-bundle, --private-key, --crl-chain are required'
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def parse(args)
|
93
|
+
results = {}
|
94
|
+
parser = self.class.parser(results)
|
95
|
+
|
96
|
+
errors = CliParsing.parse_with_errors(parser, args)
|
97
|
+
|
98
|
+
if err = check_flag_usage(results)
|
99
|
+
errors << err
|
100
|
+
end
|
101
|
+
|
102
|
+
errors_were_handled = CliParsing.handle_errors(@logger, errors, parser.help)
|
103
|
+
|
104
|
+
exit_code = errors_were_handled ? 1 : nil
|
105
|
+
|
106
|
+
return results, exit_code
|
107
|
+
end
|
108
|
+
|
109
|
+
def self.parser(parsed = {})
|
110
|
+
OptionParser.new do |opts|
|
111
|
+
opts.banner = BANNER
|
112
|
+
opts.on('--help', 'Display this import specific help output') do |help|
|
113
|
+
parsed['help'] = true
|
114
|
+
end
|
115
|
+
opts.on('--config CONF', 'Path to puppet.conf') do |conf|
|
116
|
+
parsed['config'] = conf
|
117
|
+
end
|
118
|
+
opts.on('--private-key KEY', 'Path to PEM encoded key') do |key|
|
119
|
+
parsed['private-key'] = key
|
120
|
+
end
|
121
|
+
opts.on('--cert-bundle BUNDLE', 'Path to PEM encoded bundle') do |bundle|
|
122
|
+
parsed['cert-bundle'] = bundle
|
123
|
+
end
|
124
|
+
opts.on('--crl-chain CHAIN', 'Path to PEM encoded chain') do |chain|
|
125
|
+
parsed['crl-chain'] = chain
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
@@ -0,0 +1,132 @@
|
|
1
|
+
require 'puppetserver/ca/utils/cli_parsing'
|
2
|
+
require 'puppetserver/ca/utils/file_system'
|
3
|
+
require 'puppetserver/ca/certificate_authority'
|
4
|
+
require 'puppetserver/ca/config/puppet'
|
5
|
+
require 'optparse'
|
6
|
+
require 'json'
|
7
|
+
|
8
|
+
module Puppetserver
|
9
|
+
module Ca
|
10
|
+
module Action
|
11
|
+
class List
|
12
|
+
|
13
|
+
include Puppetserver::Ca::Utils
|
14
|
+
|
15
|
+
SUMMARY = 'List all certificate requests'
|
16
|
+
BANNER = <<-BANNER
|
17
|
+
Usage:
|
18
|
+
puppetserver ca list [--help]
|
19
|
+
puppetserver ca list [--config]
|
20
|
+
puppetserver ca list [--all]
|
21
|
+
|
22
|
+
Description:
|
23
|
+
List outstanding certificate requests. If --all is specified, signed and revoked certificates will be listed as well.
|
24
|
+
|
25
|
+
Options:
|
26
|
+
BANNER
|
27
|
+
|
28
|
+
BODY = JSON.dump({desired_state: 'signed'})
|
29
|
+
|
30
|
+
def initialize(logger)
|
31
|
+
@logger = logger
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.parser(parsed = {})
|
35
|
+
OptionParser.new do |opts|
|
36
|
+
opts.banner = BANNER
|
37
|
+
opts.on('--config CONF', 'Custom path to Puppet\'s config file') do |conf|
|
38
|
+
parsed['config'] = conf
|
39
|
+
end
|
40
|
+
opts.on('--help', 'Display this command specific help output') do |help|
|
41
|
+
parsed['help'] = true
|
42
|
+
end
|
43
|
+
opts.on('--all', 'List all certificates') do |a|
|
44
|
+
parsed['all'] = true
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def run(input)
|
50
|
+
config = input['config']
|
51
|
+
|
52
|
+
if config
|
53
|
+
errors = FileSystem.validate_file_paths(config)
|
54
|
+
return 1 if CliParsing.handle_errors(@logger, errors)
|
55
|
+
end
|
56
|
+
|
57
|
+
puppet = Config::Puppet.parse(config)
|
58
|
+
return 1 if CliParsing.handle_errors(@logger, puppet.errors)
|
59
|
+
|
60
|
+
all_certs = get_all_certs(puppet.settings)
|
61
|
+
return 1 if all_certs.nil?
|
62
|
+
|
63
|
+
requested, signed, revoked = separate_certs(all_certs)
|
64
|
+
input['all'] ? output_certs_by_state(requested, signed, revoked) : output_certs_by_state(requested)
|
65
|
+
|
66
|
+
return 0
|
67
|
+
end
|
68
|
+
|
69
|
+
def output_certs_by_state(requested, signed = [], revoked = [])
|
70
|
+
if revoked.empty? && signed.empty? && requested.empty?
|
71
|
+
@logger.inform "No certificates to list"
|
72
|
+
return
|
73
|
+
end
|
74
|
+
|
75
|
+
unless requested.empty?
|
76
|
+
@logger.inform "Requested Certificates:"
|
77
|
+
output_certs(requested)
|
78
|
+
end
|
79
|
+
|
80
|
+
unless signed.empty?
|
81
|
+
@logger.inform "Signed Certificates:"
|
82
|
+
output_certs(signed)
|
83
|
+
end
|
84
|
+
|
85
|
+
unless revoked.empty?
|
86
|
+
@logger.inform "Revoked Certificates:"
|
87
|
+
output_certs(revoked)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def output_certs(certs)
|
92
|
+
padded = 0
|
93
|
+
certs.each do |cert|
|
94
|
+
cert_size = cert["name"].size
|
95
|
+
padded = cert_size if cert_size > padded
|
96
|
+
end
|
97
|
+
|
98
|
+
certs.each do |cert|
|
99
|
+
@logger.inform " #{cert["name"]}".ljust(padded + 6) + " (SHA256) " + " #{cert["fingerprints"]["SHA256"]}" +
|
100
|
+
(cert["dns_alt_names"].empty? ? "" : "\talt names: #{cert["dns_alt_names"]}")
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def separate_certs(all_certs)
|
105
|
+
certs = all_certs.group_by { |v| v["state"]}
|
106
|
+
requested = certs.fetch("requested", [])
|
107
|
+
signed = certs.fetch("signed", [])
|
108
|
+
revoked = certs.fetch("revoked", [])
|
109
|
+
return requested, signed, revoked
|
110
|
+
end
|
111
|
+
|
112
|
+
def get_all_certs(settings)
|
113
|
+
result = Puppetserver::Ca::CertificateAuthority.new(@logger, settings).get_certificate_statuses
|
114
|
+
JSON.parse(result.body)
|
115
|
+
end
|
116
|
+
|
117
|
+
def parse(args)
|
118
|
+
results = {}
|
119
|
+
parser = self.class.parser(results)
|
120
|
+
|
121
|
+
errors = CliParsing.parse_with_errors(parser, args)
|
122
|
+
|
123
|
+
errors_were_handled = CliParsing.handle_errors(@logger, errors, parser.help)
|
124
|
+
|
125
|
+
exit_code = errors_were_handled ? 1 : nil
|
126
|
+
|
127
|
+
return results, exit_code
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
require 'puppetserver/ca/utils/cli_parsing'
|
2
|
+
require 'puppetserver/ca/utils/file_system'
|
3
|
+
require 'puppetserver/ca/config/puppet'
|
4
|
+
require 'puppetserver/ca/certificate_authority'
|
5
|
+
|
6
|
+
require 'optparse'
|
7
|
+
|
8
|
+
module Puppetserver
|
9
|
+
module Ca
|
10
|
+
module Action
|
11
|
+
class Revoke
|
12
|
+
|
13
|
+
include Puppetserver::Ca::Utils
|
14
|
+
|
15
|
+
CERTNAME_BLACKLIST = %w{--all --config}
|
16
|
+
|
17
|
+
SUMMARY = 'Revoke a given certificate'
|
18
|
+
BANNER = <<-BANNER
|
19
|
+
Usage:
|
20
|
+
puppetserver ca revoke [--help]
|
21
|
+
puppetserver ca revoke [--config] --certname CERTNAME[,ADDLCERTNAME]
|
22
|
+
|
23
|
+
Description:
|
24
|
+
Given one or more valid certnames, instructs the CA to revoke them over
|
25
|
+
HTTPS using the local agent's PKI
|
26
|
+
|
27
|
+
Options:
|
28
|
+
BANNER
|
29
|
+
|
30
|
+
def self.parser(parsed = {})
|
31
|
+
parsed['certnames'] = []
|
32
|
+
OptionParser.new do |o|
|
33
|
+
o.banner = BANNER
|
34
|
+
o.on('--certname foo,bar', Array,
|
35
|
+
'One or more comma separated certnames') do |certs|
|
36
|
+
parsed['certnames'] += certs
|
37
|
+
end
|
38
|
+
o.on('--config PUPPET.CONF', 'Custom path to puppet.conf') do |conf|
|
39
|
+
parsed['config'] = conf
|
40
|
+
end
|
41
|
+
o.on('--help', 'Displays this revoke specific help output') do |help|
|
42
|
+
parsed['help'] = true
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def initialize(logger)
|
48
|
+
@logger = logger
|
49
|
+
end
|
50
|
+
|
51
|
+
def parse(args)
|
52
|
+
results = {}
|
53
|
+
parser = self.class.parser(results)
|
54
|
+
|
55
|
+
errors = CliParsing.parse_with_errors(parser, args)
|
56
|
+
|
57
|
+
results['certnames'].each do |certname|
|
58
|
+
if CERTNAME_BLACKLIST.include?(certname)
|
59
|
+
errors << " Cannot manage cert named `#{certname}` from " +
|
60
|
+
"the CLI, if needed use the HTTP API directly"
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
if results['certnames'].empty?
|
65
|
+
errors << ' At least one certname is required to revoke'
|
66
|
+
end
|
67
|
+
|
68
|
+
errors_were_handled = CliParsing.handle_errors(@logger, errors, parser.help)
|
69
|
+
|
70
|
+
# if there is an exit_code then Cli will return it early, so we only
|
71
|
+
# return an exit_code if there's an error
|
72
|
+
exit_code = errors_were_handled ? 1 : nil
|
73
|
+
|
74
|
+
return results, exit_code
|
75
|
+
end
|
76
|
+
|
77
|
+
def run(args)
|
78
|
+
certnames = args['certnames']
|
79
|
+
config = args['config']
|
80
|
+
|
81
|
+
if config
|
82
|
+
errors = FileSystem.validate_file_paths(config)
|
83
|
+
return 1 if CliParsing.handle_errors(@logger, errors)
|
84
|
+
end
|
85
|
+
|
86
|
+
puppet = Config::Puppet.parse(config)
|
87
|
+
return 1 if CliParsing.handle_errors(@logger, puppet.errors)
|
88
|
+
|
89
|
+
passed = revoke_certs(certnames, puppet.settings)
|
90
|
+
|
91
|
+
return passed ? 0 : 1
|
92
|
+
end
|
93
|
+
|
94
|
+
def revoke_certs(certnames, settings)
|
95
|
+
ca = Puppetserver::Ca::CertificateAuthority.new(@logger, settings)
|
96
|
+
ca.revoke_certs(certnames)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,126 @@
|
|
1
|
+
require 'puppetserver/ca/utils/cli_parsing'
|
2
|
+
require 'puppetserver/ca/utils/file_system'
|
3
|
+
require 'puppetserver/ca/config/puppet'
|
4
|
+
require 'puppetserver/ca/certificate_authority'
|
5
|
+
|
6
|
+
require 'optparse'
|
7
|
+
require 'openssl'
|
8
|
+
require 'net/https'
|
9
|
+
|
10
|
+
module Puppetserver
|
11
|
+
module Ca
|
12
|
+
module Action
|
13
|
+
class Sign
|
14
|
+
|
15
|
+
include Puppetserver::Ca::Utils
|
16
|
+
|
17
|
+
SUMMARY = 'Sign a given certificate'
|
18
|
+
BANNER = <<-BANNER
|
19
|
+
Usage:
|
20
|
+
puppetserver ca sign [--help]
|
21
|
+
puppetserver ca sign [--config] --certname CERTNAME[,CERTNAME]
|
22
|
+
puppetserver ca sign --all
|
23
|
+
|
24
|
+
Description:
|
25
|
+
Given a comma-separated list of valid certnames, instructs the CA to sign each cert.
|
26
|
+
|
27
|
+
Options:
|
28
|
+
BANNER
|
29
|
+
|
30
|
+
def self.parser(parsed = {})
|
31
|
+
OptionParser.new do |opts|
|
32
|
+
opts.banner = BANNER
|
33
|
+
opts.on('--certname x,y,z', Array, 'the name(s) of the cert(s) to be signed') do |cert|
|
34
|
+
parsed['certname'] = cert
|
35
|
+
end
|
36
|
+
opts.on('--config PUPPET.CONF', 'Custom path to Puppet\'s config file') do |conf|
|
37
|
+
parsed['config'] = conf
|
38
|
+
end
|
39
|
+
opts.on('--help', 'Display this command specific help output') do |help|
|
40
|
+
parsed['help'] = true
|
41
|
+
end
|
42
|
+
opts.on('--all', 'Operate on all certnames') do |a|
|
43
|
+
parsed['all'] = true
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def initialize(logger)
|
49
|
+
@logger = logger
|
50
|
+
end
|
51
|
+
|
52
|
+
def run(input)
|
53
|
+
config = input['config']
|
54
|
+
|
55
|
+
if config
|
56
|
+
errors = FileSystem.validate_file_paths(config)
|
57
|
+
return 1 if CliParsing.handle_errors(@logger, errors)
|
58
|
+
end
|
59
|
+
|
60
|
+
puppet = Config::Puppet.parse(config)
|
61
|
+
return 1 if CliParsing.handle_errors(@logger, puppet.errors)
|
62
|
+
|
63
|
+
ca = Puppetserver::Ca::CertificateAuthority.new(@logger, puppet.settings)
|
64
|
+
|
65
|
+
if input['all']
|
66
|
+
requested_certnames = get_all_pending_certs(ca)
|
67
|
+
if requested_certnames.nil?
|
68
|
+
return 1
|
69
|
+
end
|
70
|
+
else
|
71
|
+
requested_certnames = input['certname']
|
72
|
+
end
|
73
|
+
|
74
|
+
success = ca.sign_certs(requested_certnames)
|
75
|
+
return success ? 0 : 1
|
76
|
+
end
|
77
|
+
|
78
|
+
def get_all_pending_certs(ca)
|
79
|
+
if result = ca.get_certificate_statuses
|
80
|
+
select_pending_certs(result.body)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def select_pending_certs(get_result)
|
85
|
+
requested_certnames = JSON.parse(get_result).select{|e| e["state"] == "requested"}.map{|e| e["name"]}
|
86
|
+
|
87
|
+
if requested_certnames.empty?
|
88
|
+
@logger.err 'Error:'
|
89
|
+
@logger.err " No waiting certificate requests to sign"
|
90
|
+
return nil
|
91
|
+
end
|
92
|
+
|
93
|
+
return requested_certnames
|
94
|
+
end
|
95
|
+
|
96
|
+
def check_flag_usage(results)
|
97
|
+
if results['certname'] && results['all']
|
98
|
+
'--all and --certname cannot be used together'
|
99
|
+
elsif !results['certname'] && !results['all']
|
100
|
+
'No arguments given'
|
101
|
+
elsif results['certname'] && results['certname'].include?('--all')
|
102
|
+
'Cannot use --all with --certname. If you actually have a certificate request ' +
|
103
|
+
'for a certifcate named --all, you need to use the HTTP API.'
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def parse(args)
|
108
|
+
results = {}
|
109
|
+
parser = self.class.parser(results)
|
110
|
+
|
111
|
+
errors = CliParsing.parse_with_errors(parser, args)
|
112
|
+
|
113
|
+
if err = check_flag_usage(results)
|
114
|
+
errors << err
|
115
|
+
end
|
116
|
+
|
117
|
+
errors_were_handled = CliParsing.handle_errors(@logger, errors, parser.help)
|
118
|
+
|
119
|
+
exit_code = errors_were_handled ? 1 : nil
|
120
|
+
|
121
|
+
return results, exit_code
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|