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,153 +0,0 @@
1
- require 'optparse'
2
- require 'puppetserver/utils/file_utilities'
3
- require 'puppetserver/ca/x509_loader'
4
- require 'puppetserver/ca/puppet_config'
5
-
6
- module Puppetserver
7
- module Ca
8
- class ImportAction
9
-
10
- SUMMARY = "Import the CA's key, certs, and crls"
11
- BANNER = <<-BANNER
12
- Usage:
13
- puppetserver ca import [--help]
14
- puppetserver ca import [--config PATH]
15
- --private-key PATH --cert-bundle PATH --crl-chain PATH
16
-
17
- Description:
18
- Given a private key, cert bundle, and a crl chain,
19
- validate and import to the Puppet Server CA.
20
-
21
- To determine the target location the default puppet.conf
22
- is consulted for custom values. If using a custom puppet.conf
23
- provide it with the --config flag
24
-
25
- Options:
26
- BANNER
27
-
28
- def initialize(logger)
29
- @logger = logger
30
- end
31
-
32
- def run(input)
33
- bundle_path = input['cert-bundle']
34
- key_path = input['private-key']
35
- chain_path = input['crl-chain']
36
- config_path = input['config']
37
-
38
- files = [bundle_path, key_path, chain_path, config_path].compact
39
-
40
- errors = Puppetserver::Utils::FileUtilities.validate_file_paths(files)
41
- return 1 if log_possible_errors(errors)
42
-
43
- loader = X509Loader.new(bundle_path, key_path, chain_path)
44
- return 1 if log_possible_errors(loader.errors)
45
-
46
- puppet = PuppetConfig.parse(config_path)
47
- return 1 if log_possible_errors(puppet.errors)
48
-
49
- Puppetserver::Utils::FileUtilities.ensure_dir(puppet.settings[:cadir])
50
-
51
- Puppetserver::Utils::FileUtilities.write_file(puppet.settings[:cacert], loader.certs, 0640)
52
-
53
- Puppetserver::Utils::FileUtilities.write_file(puppet.settings[:cakey], loader.key, 0640)
54
-
55
- Puppetserver::Utils::FileUtilities.write_file(puppet.settings[:cacrl], loader.crls, 0640)
56
-
57
- # Puppet's internal CA expects these file to exist.
58
- Puppetserver::Utils::FileUtilities.ensure_file(puppet.settings[:serial], "001", 0640)
59
- Puppetserver::Utils::FileUtilities.ensure_file(puppet.settings[:cert_inventory], "", 0640)
60
-
61
- @logger.inform "Import succeeded. Find your files in #{puppet.settings[:cadir]}"
62
- return 0
63
- end
64
-
65
- def log_possible_errors(maybe_errors)
66
- errors = Array(maybe_errors).compact
67
- unless errors.empty?
68
- @logger.err "Error:"
69
- errors.each do |message|
70
- @logger.err " #{message}"
71
- end
72
- return true
73
- end
74
- end
75
-
76
- def parse(cli_args)
77
- parser, inputs, unparsed = parse_inputs(cli_args)
78
-
79
- if !unparsed.empty?
80
- @logger.err 'Error:'
81
- @logger.err 'Unknown arguments or flags:'
82
- unparsed.each do |arg|
83
- @logger.err " #{arg}"
84
- end
85
-
86
- @logger.err ''
87
- @logger.err parser.help
88
-
89
- exit_code = 1
90
- else
91
- exit_code = validate_inputs(inputs, parser.help)
92
- end
93
-
94
- return inputs, exit_code
95
- end
96
-
97
- def validate_inputs(input, usage)
98
- exit_code = nil
99
-
100
- if input.values_at('cert-bundle', 'private-key', 'crl-chain').any?(&:nil?)
101
- @logger.err 'Error:'
102
- @logger.err 'Missing required argument'
103
- @logger.err ' --cert-bundle, --private-key, --crl-chain are required'
104
- @logger.err ''
105
- @logger.err usage
106
- exit_code = 1
107
- end
108
-
109
- exit_code
110
- end
111
-
112
- def parse_inputs(inputs)
113
- parsed = {}
114
- unparsed = []
115
-
116
- parser = self.class.parser(parsed)
117
-
118
- begin
119
- parser.order!(inputs) do |nonopt|
120
- unparsed << nonopt
121
- end
122
- rescue OptionParser::ParseError => e
123
- unparsed += e.args
124
- unparsed << inputs.shift unless inputs.first =~ /^-{1,2}/
125
- retry
126
- end
127
-
128
- return parser, parsed, unparsed
129
- end
130
-
131
- def self.parser(parsed = {})
132
- OptionParser.new do |opts|
133
- opts.banner = BANNER
134
- opts.on('--help', 'Display this import specific help output') do |help|
135
- parsed['help'] = true
136
- end
137
- opts.on('--config CONF', 'Path to puppet.conf') do |conf|
138
- parsed['config'] = conf
139
- end
140
- opts.on('--private-key KEY', 'Path to PEM encoded key') do |key|
141
- parsed['private-key'] = key
142
- end
143
- opts.on('--cert-bundle BUNDLE', 'Path to PEM encoded bundle') do |bundle|
144
- parsed['cert-bundle'] = bundle
145
- end
146
- opts.on('--crl-chain CHAIN', 'Path to PEM encoded chain') do |chain|
147
- parsed['crl-chain'] = chain
148
- end
149
- end
150
- end
151
- end
152
- end
153
- end
@@ -1,153 +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 'json'
7
-
8
- module Puppetserver
9
- module Ca
10
- class ListAction
11
-
12
- include Puppetserver::Utils
13
-
14
- SUMMARY = 'List all certificate requests'
15
- BANNER = <<-BANNER
16
- Usage:
17
- puppetserver ca list [--help]
18
- puppetserver ca list [--config]
19
- puppetserver ca list [--all]
20
-
21
- Description:
22
- List outstanding certificate requests. If --all is specified, signed and revoked certificates will be listed as well.
23
-
24
- Options:
25
- BANNER
26
-
27
- BODY = JSON.dump({desired_state: 'signed'})
28
-
29
- def initialize(logger)
30
- @logger = logger
31
- end
32
-
33
- def self.parser(parsed = {})
34
- OptionParser.new do |opts|
35
- opts.banner = BANNER
36
- opts.on('--config 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', 'List all certificates') do |a|
43
- parsed['all'] = true
44
- end
45
- end
46
- end
47
-
48
- def run(input)
49
- config = input['config']
50
-
51
- if config
52
- errors = FileUtilities.validate_file_paths(config)
53
- return 1 if Utils.handle_errors(@logger, errors)
54
- end
55
-
56
- puppet = PuppetConfig.parse(config)
57
- return 1 if Utils.handle_errors(@logger, puppet.errors)
58
-
59
- all_certs = get_all_certs(puppet.settings)
60
- return 1 if all_certs.nil?
61
-
62
- requested, signed, revoked = separate_certs(all_certs)
63
- input['all'] ? output_certs_by_state(requested, signed, revoked) : output_certs_by_state(requested)
64
-
65
- return 0
66
- end
67
-
68
- def output_certs_by_state(requested, signed = [], revoked = [])
69
- if revoked.empty? && signed.empty? && requested.empty?
70
- @logger.inform "No certificates to list"
71
- return
72
- end
73
-
74
- unless requested.empty?
75
- @logger.inform "Requested Certificates:"
76
- output_certs(requested)
77
- end
78
-
79
- unless signed.empty?
80
- @logger.inform "Signed Certificates:"
81
- output_certs(signed)
82
- end
83
-
84
- unless revoked.empty?
85
- @logger.inform "Revoked Certificates:"
86
- output_certs(revoked)
87
- end
88
- end
89
-
90
- def output_certs(certs)
91
- padded = 0
92
- certs.each do |cert|
93
- cert_size = cert["name"].size
94
- padded = cert_size if cert_size > padded
95
- end
96
-
97
- certs.each do |cert|
98
- @logger.inform " #{cert["name"]}".ljust(padded + 6) + " (SHA256) " + " #{cert["fingerprints"]["SHA256"]}" +
99
- (cert["dns_alt_names"].empty? ? "" : "\talt names: #{cert["dns_alt_names"]}")
100
- end
101
- end
102
-
103
- def http_client(settings)
104
- @client ||= HttpClient.new(settings)
105
- end
106
-
107
- def get_certificate_statuses(settings)
108
- client = http_client(settings)
109
- url = client.make_ca_url(settings[:ca_server],
110
- settings[:ca_port],
111
- 'certificate_statuses',
112
- 'any_key')
113
- client.with_connection(url) do |connection|
114
- connection.get(url)
115
- end
116
- end
117
-
118
- def separate_certs(all_certs)
119
- certs = all_certs.group_by { |v| v["state"]}
120
- requested = certs.fetch("requested", [])
121
- signed = certs.fetch("signed", [])
122
- revoked = certs.fetch("revoked", [])
123
- return requested, signed, revoked
124
- end
125
-
126
- def get_all_certs(settings)
127
- result = get_certificate_statuses(settings)
128
-
129
- unless result.code == '200'
130
- @logger.err 'Error:'
131
- @logger.err " code: #{result.code}"
132
- @logger.err " body: #{result.body}" if result.body
133
- return nil
134
- end
135
-
136
- JSON.parse(result.body)
137
- end
138
-
139
- def parse(args)
140
- results = {}
141
- parser = self.class.parser(results)
142
-
143
- errors = Utils.parse_with_errors(parser, args)
144
-
145
- errors_were_handled = Utils.handle_errors(@logger, errors, parser.help)
146
-
147
- exit_code = errors_were_handled ? 1 : nil
148
-
149
- return results, exit_code
150
- end
151
- end
152
- end
153
- end
@@ -1,197 +0,0 @@
1
- require 'puppetserver/ca/config_utils'
2
- require 'puppetserver/settings/ttl_setting'
3
- require 'securerandom'
4
- require 'facter'
5
-
6
- module Puppetserver
7
- module Ca
8
- # Provides an interface for asking for Puppet settings w/o loading
9
- # Puppet. Includes a simple ini parser that will ignore Puppet's
10
- # more complicated conventions.
11
- class PuppetConfig
12
-
13
- include Puppetserver::Ca::ConfigUtils
14
-
15
- def self.parse(config_path = nil)
16
- instance = new(config_path)
17
- instance.load
18
-
19
- return instance
20
- end
21
-
22
- attr_reader :errors, :settings
23
-
24
- def initialize(supplied_config_path = nil)
25
- @using_default_location = !supplied_config_path
26
- @config_path = supplied_config_path || user_specific_conf_file
27
-
28
- @settings = nil
29
- @errors = []
30
- end
31
-
32
- # Return the correct confdir. We check for being root on *nix,
33
- # else the user path. We do not include a check for running
34
- # as Adminstrator since non-development scenarios for Puppet Server
35
- # on Windows are unsupported.
36
- # Note that Puppet Server runs as the [pe-]puppet user but to
37
- # start/stop it you must be root.
38
- def user_specific_conf_dir
39
- if running_as_root?
40
- '/etc/puppetlabs/puppet'
41
- else
42
- "#{ENV['HOME']}/.puppetlabs/etc/puppet"
43
- end
44
- end
45
-
46
- def user_specific_conf_file
47
- user_specific_conf_dir + '/puppet.conf'
48
- end
49
-
50
- def load
51
- if explicitly_given_config_file_or_default_config_exists?
52
- results = parse_text(File.read(@config_path))
53
- end
54
-
55
- @certname = default_certname
56
-
57
- results ||= {}
58
- results[:main] ||= {}
59
- results[:master] ||= {}
60
-
61
- overrides = results[:main].merge(results[:master])
62
-
63
- @settings = resolve_settings(overrides).freeze
64
- end
65
-
66
- def default_certname
67
- hostname = Facter.value(:hostname)
68
- domain = Facter.value(:domain)
69
- if domain and domain != ''
70
- fqdn = [hostname, domain].join('.')
71
- else
72
- fqdn = hostname
73
- end
74
- fqdn.chomp('.')
75
- end
76
-
77
- # Resolve settings from default values, with any overrides for the
78
- # specific settings or their dependent settings (ssldir, cadir) taken into account.
79
- def resolve_settings(overrides = {})
80
- unresolved_setting = /\$[a-z_]+/
81
-
82
- # Returning the key for unknown keys (rather than nil) is required to
83
- # keep unknown settings in the string for later verification.
84
- substitutions = Hash.new {|h, k| k }
85
- settings = {}
86
-
87
- confdir = user_specific_conf_dir
88
- settings[:confdir] = substitutions['$confdir'] = confdir
89
-
90
- ssldir = overrides.fetch(:ssldir, '$confdir/ssl')
91
- settings[:ssldir] = substitutions['$ssldir'] = ssldir.sub('$confdir', confdir)
92
-
93
- certdir = overrides.fetch(:certdir, '$ssldir/certs')
94
- settings[:certdir] = substitutions['$certdir'] = certdir.sub(unresolved_setting, substitutions)
95
-
96
- cadir = overrides.fetch(:cadir, '$ssldir/ca')
97
- settings[:cadir] = substitutions['$cadir'] = cadir.sub(unresolved_setting, substitutions)
98
-
99
- settings[:certname] = substitutions['$certname'] = overrides.fetch(:certname, @certname)
100
-
101
- server = overrides.fetch(:server, '$certname')
102
- settings[:server] = substitutions['$server'] = server.sub(unresolved_setting, substitutions)
103
-
104
- privatekeydir = overrides.fetch(:privatekeydir, '$ssldir/private_keys')
105
- settings[:privatekeydir] = substitutions['$privatekeydir'] = privatekeydir.sub(unresolved_setting, substitutions)
106
-
107
- settings[:masterport] = substitutions['$masterport'] = overrides.fetch(:masterport, '8140')
108
-
109
- settings[:ca_name] = overrides.fetch(:ca_name, 'Puppet CA: $certname')
110
- settings[:root_ca_name] = overrides.fetch(:root_ca_name, "Puppet Root CA: #{SecureRandom.hex(7)}")
111
-
112
- unmunged_ca_ttl = overrides.fetch(:ca_ttl, '15y')
113
- ttl_setting = Puppetserver::Settings::TTLSetting.new(:ca_ttl, unmunged_ca_ttl)
114
- if ttl_setting.errors
115
- ttl_setting.errors.each { |error| @errors << error }
116
- end
117
-
118
- settings[:ca_ttl] = ttl_setting.munged_value
119
- settings[:keylength] = overrides.fetch(:keylength, 4096)
120
- settings[:cacert] = overrides.fetch(:cacert, '$cadir/ca_crt.pem')
121
- settings[:cakey] = overrides.fetch(:cakey, '$cadir/ca_key.pem')
122
- settings[:rootkey] = overrides.fetch(:rootkey, '$cadir/root_key.pem')
123
- settings[:cacrl] = overrides.fetch(:cacrl, '$cadir/ca_crl.pem')
124
- settings[:serial] = overrides.fetch(:serial, '$cadir/serial')
125
- settings[:cert_inventory] = overrides.fetch(:cert_inventory, '$cadir/inventory.txt')
126
- settings[:ca_server] = overrides.fetch(:ca_server, '$server')
127
- settings[:ca_port] = overrides.fetch(:ca_port, '$masterport')
128
- settings[:localcacert] = overrides.fetch(:localcacert, '$certdir/ca.pem')
129
- settings[:hostcert] = overrides.fetch(:hostcert, '$certdir/$certname.pem')
130
- settings[:hostcrl] = overrides.fetch(:hostcrl, '$ssldir/crl.pem')
131
- settings[:hostprivkey] = overrides.fetch(:hostprivkey, '$privatekeydir/$certname.pem')
132
- settings[:publickeydir] = overrides.fetch(:publickeydir, '$ssldir/public_keys')
133
- settings[:certificate_revocation] = parse_crl_usage(overrides.fetch(:certificate_revocation, 'true'))
134
-
135
- settings.each_pair do |key, value|
136
- next unless value.is_a? String
137
-
138
- settings[key] = value.gsub(unresolved_setting, substitutions)
139
-
140
- if match = settings[key].match(unresolved_setting)
141
- @errors << "Could not parse #{match[0]} in #{value}, " +
142
- 'valid settings to be interpolated are ' +
143
- '$ssldir, $cadir, or $certname'
144
- end
145
- end
146
-
147
- return settings
148
- end
149
-
150
- # Parse an inifile formatted String. Only captures \word character
151
- # class keys/section names but nearly any character values (excluding
152
- # leading whitespace) up to one of whitespace, opening curly brace, or
153
- # hash sign (Our concern being to capture filesystem path values).
154
- # Put values without a section into :main.
155
- #
156
- # Return Hash of Symbol section names with Symbol setting keys and
157
- # String values.
158
- def parse_text(text)
159
- res = {}
160
- current_section = :main
161
- text.each_line do |line|
162
- case line
163
- when /^\s*\[(\w+)\].*/
164
- current_section = $1.to_sym
165
- when /^\s*(\w+)\s*=\s*([^\s{#]+).*$/
166
- # Using a Hash with a default key breaks RSpec expectations.
167
- res[current_section] ||= {}
168
- res[current_section][$1.to_sym] = $2
169
- end
170
- end
171
-
172
- res
173
- end
174
-
175
- private
176
-
177
- def explicitly_given_config_file_or_default_config_exists?
178
- !@using_default_location || File.exist?(@config_path)
179
- end
180
-
181
- def run(command)
182
- %x( #{command} )
183
- end
184
-
185
- def parse_crl_usage(setting)
186
- case setting.to_s
187
- when 'true', 'chain'
188
- :chain
189
- when 'leaf'
190
- :leaf
191
- when 'false'
192
- :ignore
193
- end
194
- end
195
- end
196
- end
197
- end