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
@@ -1,83 +0,0 @@
|
|
1
|
-
require 'hocon'
|
2
|
-
require 'puppetserver/ca/config_utils'
|
3
|
-
|
4
|
-
module Puppetserver
|
5
|
-
module Ca
|
6
|
-
# Provides an interface for querying Puppetserver settings w/o loading
|
7
|
-
# Puppetserver or any TK config service. Uses the ruby-hocon gem for parsing.
|
8
|
-
class PuppetserverConfig
|
9
|
-
|
10
|
-
include Puppetserver::Ca::ConfigUtils
|
11
|
-
|
12
|
-
def self.parse(config_path = nil)
|
13
|
-
instance = new(config_path)
|
14
|
-
instance.load
|
15
|
-
|
16
|
-
return instance
|
17
|
-
end
|
18
|
-
|
19
|
-
attr_reader :errors, :settings
|
20
|
-
|
21
|
-
def initialize(supplied_config_path = nil)
|
22
|
-
@using_default_location = !supplied_config_path
|
23
|
-
@config_path = supplied_config_path || "/etc/puppetlabs/puppetserver/conf.d/ca.conf"
|
24
|
-
|
25
|
-
@settings = nil
|
26
|
-
@errors = []
|
27
|
-
end
|
28
|
-
|
29
|
-
# Populate this config object with the CA-related settings
|
30
|
-
def load
|
31
|
-
if explicitly_given_config_file_or_default_config_exists?
|
32
|
-
begin
|
33
|
-
results = Hocon.load(@config_path)
|
34
|
-
rescue Hocon::ConfigError => e
|
35
|
-
errors << e.message
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
|
-
overrides = results || {}
|
40
|
-
@settings = supply_defaults(overrides).freeze
|
41
|
-
end
|
42
|
-
|
43
|
-
private
|
44
|
-
|
45
|
-
# Return the correct confdir. We check for being root on *nix,
|
46
|
-
# else the user path. We do not include a check for running
|
47
|
-
# as Adminstrator since non-development scenarios for Puppet Server
|
48
|
-
# on Windows are unsupported.
|
49
|
-
# Note that Puppet Server runs as the [pe-]puppet user but to
|
50
|
-
# start/stop it you must be root.
|
51
|
-
def user_specific_ca_dir
|
52
|
-
if running_as_root?
|
53
|
-
'/etc/puppetlabs/puppetserver/ca'
|
54
|
-
else
|
55
|
-
"#{ENV['HOME']}/.puppetlabs/etc/puppetserver/ca"
|
56
|
-
end
|
57
|
-
end
|
58
|
-
|
59
|
-
# Supply defaults for any CA settings not present in the config file
|
60
|
-
# @param [Hash] overrides setting names and values loaded from the config file,
|
61
|
-
# for overriding the defaults
|
62
|
-
# @return [Hash] CA-related settings
|
63
|
-
def supply_defaults(overrides = {})
|
64
|
-
ca_settings = overrides['certificate-authority'] || {}
|
65
|
-
settings = {}
|
66
|
-
|
67
|
-
cadir = settings[:cadir] = ca_settings.fetch('cadir', user_specific_ca_dir)
|
68
|
-
|
69
|
-
settings[:cacert] = ca_settings.fetch('cacert', "#{cadir}/ca_crt.pem")
|
70
|
-
settings[:cakey] = ca_settings.fetch('cakey', "#{cadir}/ca_key.pem")
|
71
|
-
settings[:cacrl] = ca_settings.fetch('cacrl', "#{cadir}/ca_crl.pem")
|
72
|
-
settings[:serial] = ca_settings.fetch('serial', "#{cadir}/serial")
|
73
|
-
settings[:cert_inventory] = ca_settings.fetch('cert-inventory', "#{cadir}/inventory.txt")
|
74
|
-
|
75
|
-
return settings
|
76
|
-
end
|
77
|
-
|
78
|
-
def explicitly_given_config_file_or_default_config_exists?
|
79
|
-
!@using_default_location || File.exist?(@config_path)
|
80
|
-
end
|
81
|
-
end
|
82
|
-
end
|
83
|
-
end
|
@@ -1,136 +0,0 @@
|
|
1
|
-
require 'puppetserver/ca/utils'
|
2
|
-
require 'puppetserver/utils/http_client'
|
3
|
-
require 'puppetserver/utils/file_utilities'
|
4
|
-
require 'puppetserver/ca/puppet_config'
|
5
|
-
|
6
|
-
require 'optparse'
|
7
|
-
require 'json'
|
8
|
-
|
9
|
-
module Puppetserver
|
10
|
-
module Ca
|
11
|
-
class RevokeAction
|
12
|
-
|
13
|
-
include Puppetserver::Utils
|
14
|
-
|
15
|
-
REQUEST_BODY = JSON.dump({ desired_state: 'revoked' })
|
16
|
-
CERTNAME_BLACKLIST = %w{--all --config}
|
17
|
-
|
18
|
-
SUMMARY = 'Revoke a given certificate'
|
19
|
-
BANNER = <<-BANNER
|
20
|
-
Usage:
|
21
|
-
puppetserver ca revoke [--help|--version]
|
22
|
-
puppetserver ca revoke [--config] --certname CERTNAME[,ADDLCERTNAME]
|
23
|
-
|
24
|
-
Description:
|
25
|
-
Given one or more valid certnames, instructs the CA to revoke them over
|
26
|
-
HTTPS using the local agent's PKI
|
27
|
-
|
28
|
-
Options:
|
29
|
-
BANNER
|
30
|
-
|
31
|
-
def self.parser(parsed = {})
|
32
|
-
parsed['certnames'] = []
|
33
|
-
OptionParser.new do |o|
|
34
|
-
o.banner = BANNER
|
35
|
-
o.on('--certname foo,bar', Array,
|
36
|
-
'One or more comma separated certnames') do |certs|
|
37
|
-
parsed['certnames'] += certs
|
38
|
-
end
|
39
|
-
o.on('--config PUPPET.CONF', 'Custom path to puppet.conf') do |conf|
|
40
|
-
parsed['config'] = conf
|
41
|
-
end
|
42
|
-
o.on('--help', 'Displays this revoke specific help output') do |help|
|
43
|
-
parsed['help'] = help
|
44
|
-
end
|
45
|
-
end
|
46
|
-
end
|
47
|
-
|
48
|
-
def initialize(logger)
|
49
|
-
@logger = logger
|
50
|
-
end
|
51
|
-
|
52
|
-
def parse(args)
|
53
|
-
results = {}
|
54
|
-
parser = self.class.parser(results)
|
55
|
-
|
56
|
-
errors = Utils.parse_with_errors(parser, args)
|
57
|
-
|
58
|
-
results['certnames'].each do |certname|
|
59
|
-
if CERTNAME_BLACKLIST.include?(certname)
|
60
|
-
errors << " Cannot manage cert named `#{certname}` from " +
|
61
|
-
"the CLI, if needed use the HTTP API directly"
|
62
|
-
end
|
63
|
-
end
|
64
|
-
|
65
|
-
if results['certnames'].empty?
|
66
|
-
errors << ' At least one certname is required to revoke'
|
67
|
-
end
|
68
|
-
|
69
|
-
errors_were_handled = Utils.handle_errors(@logger, errors, parser.help)
|
70
|
-
|
71
|
-
# if there is an exit_code then Cli will return it early, so we only
|
72
|
-
# return an exit_code if there's an error
|
73
|
-
exit_code = errors_were_handled ? 1 : nil
|
74
|
-
|
75
|
-
return results, exit_code
|
76
|
-
end
|
77
|
-
|
78
|
-
def run(args)
|
79
|
-
certnames = args['certnames']
|
80
|
-
config = args['config']
|
81
|
-
|
82
|
-
if config
|
83
|
-
errors = FileUtilities.validate_file_paths(config)
|
84
|
-
return 1 if Utils.handle_errors(@logger, errors)
|
85
|
-
end
|
86
|
-
|
87
|
-
puppet = PuppetConfig.parse(config)
|
88
|
-
return 1 if Utils.handle_errors(@logger, puppet.errors)
|
89
|
-
|
90
|
-
passed = revoke_certs(certnames, puppet.settings)
|
91
|
-
|
92
|
-
return passed ? 0 : 1
|
93
|
-
end
|
94
|
-
|
95
|
-
def revoke_certs(certnames, settings)
|
96
|
-
client = HttpClient.new(settings)
|
97
|
-
|
98
|
-
url = client.make_ca_url(settings[:ca_server],
|
99
|
-
settings[:ca_port],
|
100
|
-
'certificate_status')
|
101
|
-
|
102
|
-
# results will be a list of trues & falses based on the success
|
103
|
-
# of revocations
|
104
|
-
results = client.with_connection(url) do |connection|
|
105
|
-
certnames.map do |certname|
|
106
|
-
url.resource_name = certname
|
107
|
-
result = connection.put(REQUEST_BODY, url)
|
108
|
-
|
109
|
-
check_result(result, certname)
|
110
|
-
end
|
111
|
-
end
|
112
|
-
|
113
|
-
return results.all?
|
114
|
-
end
|
115
|
-
|
116
|
-
# logs the action and returns a boolean for success/failure
|
117
|
-
def check_result(result, certname)
|
118
|
-
case result.code
|
119
|
-
when '200', '204'
|
120
|
-
@logger.inform "Revoked certificate for #{certname}"
|
121
|
-
return true
|
122
|
-
when '404'
|
123
|
-
@logger.err 'Error:'
|
124
|
-
@logger.err " Could not find certificate for #{certname}"
|
125
|
-
return false
|
126
|
-
else
|
127
|
-
@logger.err 'Error:'
|
128
|
-
@logger.err " When revoking #{certname} received:"
|
129
|
-
@logger.err " code: #{result.code}"
|
130
|
-
@logger.err " body: #{result.body.to_s}" if result.body
|
131
|
-
return false
|
132
|
-
end
|
133
|
-
end
|
134
|
-
end
|
135
|
-
end
|
136
|
-
end
|
@@ -1,190 +0,0 @@
|
|
1
|
-
require 'puppetserver/ca/utils'
|
2
|
-
require 'puppetserver/utils/http_client'
|
3
|
-
require 'puppetserver/utils/file_utilities'
|
4
|
-
require 'puppetserver/ca/puppet_config'
|
5
|
-
require 'optparse'
|
6
|
-
require 'openssl'
|
7
|
-
require 'net/https'
|
8
|
-
require 'json'
|
9
|
-
|
10
|
-
module Puppetserver
|
11
|
-
module Ca
|
12
|
-
class SignAction
|
13
|
-
|
14
|
-
include Puppetserver::Utils
|
15
|
-
|
16
|
-
SUMMARY = 'Sign a given certificate'
|
17
|
-
BANNER = <<-BANNER
|
18
|
-
Usage:
|
19
|
-
puppetserver ca sign [--help|--version]
|
20
|
-
puppetserver ca sign [--config] --certname CERTNAME[,CERTNAME]
|
21
|
-
puppetserver ca sign --all
|
22
|
-
|
23
|
-
Description:
|
24
|
-
Given a comma-separated list of valid certnames, instructs the CA to sign each cert.
|
25
|
-
|
26
|
-
Options:
|
27
|
-
BANNER
|
28
|
-
BODY = JSON.dump({desired_state: 'signed'})
|
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('--version', 'Output the version') do |v|
|
43
|
-
parsed['version'] = true
|
44
|
-
end
|
45
|
-
opts.on('--all', 'Operate on all certnames') do |a|
|
46
|
-
parsed['all'] = true
|
47
|
-
end
|
48
|
-
end
|
49
|
-
end
|
50
|
-
|
51
|
-
def initialize(logger)
|
52
|
-
@logger = logger
|
53
|
-
end
|
54
|
-
|
55
|
-
def run(input)
|
56
|
-
config = input['config']
|
57
|
-
|
58
|
-
if config
|
59
|
-
errors = FileUtilities.validate_file_paths(config)
|
60
|
-
return 1 if Utils.handle_errors(@logger, errors)
|
61
|
-
end
|
62
|
-
|
63
|
-
puppet = PuppetConfig.parse(config)
|
64
|
-
return 1 if Utils.handle_errors(@logger, puppet.errors)
|
65
|
-
|
66
|
-
if input['all']
|
67
|
-
requested_certnames = get_all_pending_certs(puppet.settings)
|
68
|
-
if requested_certnames.nil?
|
69
|
-
return 1
|
70
|
-
end
|
71
|
-
else
|
72
|
-
requested_certnames = input['certname']
|
73
|
-
end
|
74
|
-
|
75
|
-
success = sign_requested_certs(requested_certnames, puppet.settings)
|
76
|
-
return success ? 0 : 1
|
77
|
-
end
|
78
|
-
|
79
|
-
def http_client(settings)
|
80
|
-
@client ||= HttpClient.new(settings)
|
81
|
-
end
|
82
|
-
|
83
|
-
def get_certificate_statuses(settings)
|
84
|
-
client = http_client(settings)
|
85
|
-
url = client.make_ca_url(settings[:ca_server],
|
86
|
-
settings[:ca_port],
|
87
|
-
'certificate_statuses',
|
88
|
-
'any_key')
|
89
|
-
client.with_connection(url) do |connection|
|
90
|
-
connection.get(url)
|
91
|
-
end
|
92
|
-
end
|
93
|
-
|
94
|
-
def sign_certs(certnames,settings)
|
95
|
-
results = {}
|
96
|
-
client = http_client(settings)
|
97
|
-
url = client.make_ca_url(settings[:ca_server],
|
98
|
-
settings[:ca_port],
|
99
|
-
'certificate_status')
|
100
|
-
client.with_connection(url) do |connection|
|
101
|
-
certnames.each do |certname|
|
102
|
-
url.resource_name = certname
|
103
|
-
results[certname] = connection.put(BODY, url)
|
104
|
-
end
|
105
|
-
end
|
106
|
-
return results
|
107
|
-
end
|
108
|
-
|
109
|
-
def get_all_certs(settings)
|
110
|
-
result = get_certificate_statuses(settings)
|
111
|
-
|
112
|
-
unless result.code == 200
|
113
|
-
@logger.err 'Error:'
|
114
|
-
@logger.err " #{result.inspect}"
|
115
|
-
return nil
|
116
|
-
end
|
117
|
-
return result
|
118
|
-
end
|
119
|
-
|
120
|
-
def select_pending_certs(get_result)
|
121
|
-
requested_certnames = JSON.parse(get_result).select{|e| e["state"] == "requested"}.map{|e| e["name"]}
|
122
|
-
|
123
|
-
if requested_certnames.empty?
|
124
|
-
@logger.err 'Error:'
|
125
|
-
@logger.err " No waiting certificate requests to sign"
|
126
|
-
return nil
|
127
|
-
end
|
128
|
-
|
129
|
-
return requested_certnames
|
130
|
-
end
|
131
|
-
|
132
|
-
def get_all_pending_certs(settings)
|
133
|
-
result = get_all_certs(settings)
|
134
|
-
if result
|
135
|
-
select_pending_certs(result.body)
|
136
|
-
end
|
137
|
-
end
|
138
|
-
|
139
|
-
def sign_requested_certs(certnames,settings)
|
140
|
-
success = true
|
141
|
-
results = sign_certs(certnames, settings)
|
142
|
-
results.each do |certname, result|
|
143
|
-
case result.code
|
144
|
-
when '204'
|
145
|
-
@logger.inform "Signed certificate for #{certname}"
|
146
|
-
when '404'
|
147
|
-
@logger.err 'Error:'
|
148
|
-
@logger.err " Could not find certificate for #{certname}"
|
149
|
-
success = false
|
150
|
-
else
|
151
|
-
@logger.err 'Error:'
|
152
|
-
@logger.err " When download requested for #{result.inspect}"
|
153
|
-
@logger.err " code: #{result.code}"
|
154
|
-
@logger.err " body: #{result.body.to_s}" if result.body
|
155
|
-
success = false
|
156
|
-
end
|
157
|
-
end
|
158
|
-
return success
|
159
|
-
end
|
160
|
-
|
161
|
-
def check_flag_usage(results)
|
162
|
-
if results['certname'] && results['all']
|
163
|
-
'--all and --certname cannot be used together'
|
164
|
-
elsif !results['certname'] && !results['all']
|
165
|
-
'No arguments given'
|
166
|
-
elsif results['certname'] && results['certname'].include?('--all')
|
167
|
-
'Cannot use --all with --certname. If you actually have a certificate request ' +
|
168
|
-
'for a certifcate named --all, you need to use the HTTP API.'
|
169
|
-
end
|
170
|
-
end
|
171
|
-
|
172
|
-
def parse(args)
|
173
|
-
results = {}
|
174
|
-
parser = self.class.parser(results)
|
175
|
-
|
176
|
-
errors = Utils.parse_with_errors(parser, args)
|
177
|
-
|
178
|
-
if check_flag_usage(results)
|
179
|
-
errors << check_flag_usage(results)
|
180
|
-
end
|
181
|
-
|
182
|
-
errors_were_handled = Utils.handle_errors(@logger, errors, parser.help)
|
183
|
-
|
184
|
-
exit_code = errors_were_handled ? 1 : nil
|
185
|
-
|
186
|
-
return results, exit_code
|
187
|
-
end
|
188
|
-
end
|
189
|
-
end
|
190
|
-
end
|
@@ -1,80 +0,0 @@
|
|
1
|
-
module Puppetserver
|
2
|
-
module Ca
|
3
|
-
module Utils
|
4
|
-
def self.parse_without_raising(parser, args)
|
5
|
-
all, not_flags, malformed_flags, unknown_flags = [], [], [], []
|
6
|
-
|
7
|
-
begin
|
8
|
-
# OptionParser calls this block when it finds a value that doesn't
|
9
|
-
# start with one or two dashes and doesn't follow a flag that
|
10
|
-
# consumes a value.
|
11
|
-
parser.order!(args) do |not_flag|
|
12
|
-
not_flags << not_flag
|
13
|
-
all << not_flag
|
14
|
-
end
|
15
|
-
rescue OptionParser::MissingArgument => e
|
16
|
-
malformed_flags += e.args
|
17
|
-
all += e.args
|
18
|
-
|
19
|
-
retry
|
20
|
-
rescue OptionParser::ParseError => e
|
21
|
-
flag = e.args.first
|
22
|
-
unknown_flags << flag
|
23
|
-
all << flag
|
24
|
-
|
25
|
-
if does_not_contain_argument(flag) &&
|
26
|
-
args.first &&
|
27
|
-
next_arg_is_not_another_flag(args.first)
|
28
|
-
|
29
|
-
value = args.shift
|
30
|
-
unknown_flags << value
|
31
|
-
all << value
|
32
|
-
end
|
33
|
-
|
34
|
-
retry
|
35
|
-
end
|
36
|
-
|
37
|
-
return all, not_flags, malformed_flags, unknown_flags
|
38
|
-
end
|
39
|
-
|
40
|
-
def self.parse_with_errors(parser, args)
|
41
|
-
errors = []
|
42
|
-
|
43
|
-
_, non_flags, malformed_flags, unknown_flags = parse_without_raising(parser, args)
|
44
|
-
|
45
|
-
malformed_flags.each {|f| errors << " Missing argument to flag `#{f}`" }
|
46
|
-
unknown_flags.each {|f| errors << " Unknown flag or argument `#{f}`" }
|
47
|
-
non_flags.each {|f| errors << " Unknown input `#{f}`" }
|
48
|
-
|
49
|
-
errors
|
50
|
-
end
|
51
|
-
|
52
|
-
def self.handle_errors(log, errors, usage = nil)
|
53
|
-
unless errors.empty?
|
54
|
-
log.err 'Error:'
|
55
|
-
errors.each {|e| log.err e }
|
56
|
-
|
57
|
-
if usage
|
58
|
-
log.err ''
|
59
|
-
log.err usage
|
60
|
-
end
|
61
|
-
|
62
|
-
return true
|
63
|
-
else
|
64
|
-
return false
|
65
|
-
end
|
66
|
-
end
|
67
|
-
|
68
|
-
private
|
69
|
-
|
70
|
-
# eg. --flag=argument-to-flag
|
71
|
-
def self.does_not_contain_argument(flag)
|
72
|
-
!flag.include?('=')
|
73
|
-
end
|
74
|
-
|
75
|
-
def self.next_arg_is_not_another_flag(maybe_an_arg)
|
76
|
-
!maybe_an_arg.start_with?('-')
|
77
|
-
end
|
78
|
-
end
|
79
|
-
end
|
80
|
-
end
|