puppetserver-ca 0.3.1 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|