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
@@ -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
|