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.
Files changed (34) hide show
  1. checksums.yaml +4 -4
  2. data/lib/puppetserver/ca/action/clean.rb +102 -0
  3. data/lib/puppetserver/ca/action/create.rb +161 -0
  4. data/lib/puppetserver/ca/action/generate.rb +313 -0
  5. data/lib/puppetserver/ca/action/import.rb +132 -0
  6. data/lib/puppetserver/ca/action/list.rb +132 -0
  7. data/lib/puppetserver/ca/action/revoke.rb +101 -0
  8. data/lib/puppetserver/ca/action/sign.rb +126 -0
  9. data/lib/puppetserver/ca/certificate_authority.rb +224 -0
  10. data/lib/puppetserver/ca/cli.rb +17 -16
  11. data/lib/puppetserver/ca/config/puppet.rb +242 -0
  12. data/lib/puppetserver/ca/config/puppetserver.rb +85 -0
  13. data/lib/puppetserver/ca/utils/cli_parsing.rb +82 -0
  14. data/lib/puppetserver/ca/utils/config.rb +13 -0
  15. data/lib/puppetserver/ca/utils/file_system.rb +90 -0
  16. data/lib/puppetserver/ca/utils/http_client.rb +129 -0
  17. data/lib/puppetserver/ca/utils/signing_digest.rb +27 -0
  18. data/lib/puppetserver/ca/version.rb +1 -1
  19. metadata +17 -17
  20. data/lib/puppetserver/ca/clean_action.rb +0 -157
  21. data/lib/puppetserver/ca/config_utils.rb +0 -11
  22. data/lib/puppetserver/ca/create_action.rb +0 -265
  23. data/lib/puppetserver/ca/generate_action.rb +0 -227
  24. data/lib/puppetserver/ca/import_action.rb +0 -153
  25. data/lib/puppetserver/ca/list_action.rb +0 -153
  26. data/lib/puppetserver/ca/puppet_config.rb +0 -197
  27. data/lib/puppetserver/ca/puppetserver_config.rb +0 -83
  28. data/lib/puppetserver/ca/revoke_action.rb +0 -136
  29. data/lib/puppetserver/ca/sign_action.rb +0 -190
  30. data/lib/puppetserver/ca/utils.rb +0 -80
  31. data/lib/puppetserver/settings/ttl_setting.rb +0 -48
  32. data/lib/puppetserver/utils/file_utilities.rb +0 -78
  33. data/lib/puppetserver/utils/http_client.rb +0 -129
  34. 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