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