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
@@ -0,0 +1,132 @@
1
+ require 'optparse'
2
+ require 'puppetserver/ca/utils/file_system'
3
+ require 'puppetserver/ca/x509_loader'
4
+ require 'puppetserver/ca/config/puppet'
5
+ require 'puppetserver/ca/utils/cli_parsing'
6
+
7
+ module Puppetserver
8
+ module Ca
9
+ module Action
10
+ class Import
11
+ include Puppetserver::Ca::Utils
12
+
13
+ SUMMARY = "Import the CA's key, certs, and crls"
14
+ BANNER = <<-BANNER
15
+ Usage:
16
+ puppetserver ca import [--help]
17
+ puppetserver ca import [--config PATH]
18
+ --private-key PATH --cert-bundle PATH --crl-chain PATH
19
+
20
+ Description:
21
+ Given a private key, cert bundle, and a crl chain,
22
+ validate and import to the Puppet Server CA.
23
+
24
+ To determine the target location the default puppet.conf
25
+ is consulted for custom values. If using a custom puppet.conf
26
+ provide it with the --config flag
27
+
28
+ Options:
29
+ BANNER
30
+
31
+ def initialize(logger)
32
+ @logger = logger
33
+ end
34
+
35
+ def run(input)
36
+ bundle_path = input['cert-bundle']
37
+ key_path = input['private-key']
38
+ chain_path = input['crl-chain']
39
+ config_path = input['config']
40
+
41
+ files = [bundle_path, key_path, chain_path, config_path].compact
42
+
43
+ errors = FileSystem.validate_file_paths(files)
44
+ return 1 if CliParsing.handle_errors(@logger, errors)
45
+
46
+ loader = X509Loader.new(bundle_path, key_path, chain_path)
47
+ return 1 if CliParsing.handle_errors(@logger, loader.errors)
48
+
49
+ puppet = Config::Puppet.parse(config_path)
50
+ return 1 if CliParsing.handle_errors(@logger, puppet.errors)
51
+
52
+ target_locations = [puppet.settings[:cacert],
53
+ puppet.settings[:cakey],
54
+ puppet.settings[:cacrl],
55
+ puppet.settings[:serial],
56
+ puppet.settings[:cert_inventory]]
57
+ errors = FileSystem.check_for_existing_files(target_locations)
58
+ if !errors.empty?
59
+ instructions = <<-ERR
60
+ If you would really like to replace your CA, please delete the existing files first.
61
+ Note that any certificates that were issued by this CA will become invalid if you
62
+ replace it!
63
+ ERR
64
+ errors << instructions
65
+ CliParsing.handle_errors(@logger, errors)
66
+ return 1
67
+ end
68
+
69
+ FileSystem.ensure_dir(puppet.settings[:cadir])
70
+
71
+ FileSystem.write_file(puppet.settings[:cacert], loader.certs, 0640)
72
+
73
+ FileSystem.write_file(puppet.settings[:cakey], loader.key, 0640)
74
+
75
+ FileSystem.write_file(puppet.settings[:cacrl], loader.crls, 0640)
76
+
77
+ # Puppet's internal CA expects these file to exist.
78
+ FileSystem.ensure_file(puppet.settings[:serial], "0x0001", 0640)
79
+ FileSystem.ensure_file(puppet.settings[:cert_inventory], "", 0640)
80
+
81
+ @logger.inform "Import succeeded. Find your files in #{puppet.settings[:cadir]}"
82
+ return 0
83
+ end
84
+
85
+ def check_flag_usage(results)
86
+ if results['cert-bundle'].nil? || results['private-key'].nil? || results['crl-chain'].nil?
87
+ ' Missing required argument' + "\n" +
88
+ ' --cert-bundle, --private-key, --crl-chain are required'
89
+ end
90
+ end
91
+
92
+ def parse(args)
93
+ results = {}
94
+ parser = self.class.parser(results)
95
+
96
+ errors = CliParsing.parse_with_errors(parser, args)
97
+
98
+ if err = check_flag_usage(results)
99
+ errors << err
100
+ end
101
+
102
+ errors_were_handled = CliParsing.handle_errors(@logger, errors, parser.help)
103
+
104
+ exit_code = errors_were_handled ? 1 : nil
105
+
106
+ return results, exit_code
107
+ end
108
+
109
+ def self.parser(parsed = {})
110
+ OptionParser.new do |opts|
111
+ opts.banner = BANNER
112
+ opts.on('--help', 'Display this import specific help output') do |help|
113
+ parsed['help'] = true
114
+ end
115
+ opts.on('--config CONF', 'Path to puppet.conf') do |conf|
116
+ parsed['config'] = conf
117
+ end
118
+ opts.on('--private-key KEY', 'Path to PEM encoded key') do |key|
119
+ parsed['private-key'] = key
120
+ end
121
+ opts.on('--cert-bundle BUNDLE', 'Path to PEM encoded bundle') do |bundle|
122
+ parsed['cert-bundle'] = bundle
123
+ end
124
+ opts.on('--crl-chain CHAIN', 'Path to PEM encoded chain') do |chain|
125
+ parsed['crl-chain'] = chain
126
+ end
127
+ end
128
+ end
129
+ end
130
+ end
131
+ end
132
+ end
@@ -0,0 +1,132 @@
1
+ require 'puppetserver/ca/utils/cli_parsing'
2
+ require 'puppetserver/ca/utils/file_system'
3
+ require 'puppetserver/ca/certificate_authority'
4
+ require 'puppetserver/ca/config/puppet'
5
+ require 'optparse'
6
+ require 'json'
7
+
8
+ module Puppetserver
9
+ module Ca
10
+ module Action
11
+ class List
12
+
13
+ include Puppetserver::Ca::Utils
14
+
15
+ SUMMARY = 'List all certificate requests'
16
+ BANNER = <<-BANNER
17
+ Usage:
18
+ puppetserver ca list [--help]
19
+ puppetserver ca list [--config]
20
+ puppetserver ca list [--all]
21
+
22
+ Description:
23
+ List outstanding certificate requests. If --all is specified, signed and revoked certificates will be listed as well.
24
+
25
+ Options:
26
+ BANNER
27
+
28
+ BODY = JSON.dump({desired_state: 'signed'})
29
+
30
+ def initialize(logger)
31
+ @logger = logger
32
+ end
33
+
34
+ def self.parser(parsed = {})
35
+ OptionParser.new do |opts|
36
+ opts.banner = BANNER
37
+ opts.on('--config CONF', 'Custom path to Puppet\'s config file') do |conf|
38
+ parsed['config'] = conf
39
+ end
40
+ opts.on('--help', 'Display this command specific help output') do |help|
41
+ parsed['help'] = true
42
+ end
43
+ opts.on('--all', 'List all certificates') do |a|
44
+ parsed['all'] = true
45
+ end
46
+ end
47
+ end
48
+
49
+ def run(input)
50
+ config = input['config']
51
+
52
+ if config
53
+ errors = FileSystem.validate_file_paths(config)
54
+ return 1 if CliParsing.handle_errors(@logger, errors)
55
+ end
56
+
57
+ puppet = Config::Puppet.parse(config)
58
+ return 1 if CliParsing.handle_errors(@logger, puppet.errors)
59
+
60
+ all_certs = get_all_certs(puppet.settings)
61
+ return 1 if all_certs.nil?
62
+
63
+ requested, signed, revoked = separate_certs(all_certs)
64
+ input['all'] ? output_certs_by_state(requested, signed, revoked) : output_certs_by_state(requested)
65
+
66
+ return 0
67
+ end
68
+
69
+ def output_certs_by_state(requested, signed = [], revoked = [])
70
+ if revoked.empty? && signed.empty? && requested.empty?
71
+ @logger.inform "No certificates to list"
72
+ return
73
+ end
74
+
75
+ unless requested.empty?
76
+ @logger.inform "Requested Certificates:"
77
+ output_certs(requested)
78
+ end
79
+
80
+ unless signed.empty?
81
+ @logger.inform "Signed Certificates:"
82
+ output_certs(signed)
83
+ end
84
+
85
+ unless revoked.empty?
86
+ @logger.inform "Revoked Certificates:"
87
+ output_certs(revoked)
88
+ end
89
+ end
90
+
91
+ def output_certs(certs)
92
+ padded = 0
93
+ certs.each do |cert|
94
+ cert_size = cert["name"].size
95
+ padded = cert_size if cert_size > padded
96
+ end
97
+
98
+ certs.each do |cert|
99
+ @logger.inform " #{cert["name"]}".ljust(padded + 6) + " (SHA256) " + " #{cert["fingerprints"]["SHA256"]}" +
100
+ (cert["dns_alt_names"].empty? ? "" : "\talt names: #{cert["dns_alt_names"]}")
101
+ end
102
+ end
103
+
104
+ def separate_certs(all_certs)
105
+ certs = all_certs.group_by { |v| v["state"]}
106
+ requested = certs.fetch("requested", [])
107
+ signed = certs.fetch("signed", [])
108
+ revoked = certs.fetch("revoked", [])
109
+ return requested, signed, revoked
110
+ end
111
+
112
+ def get_all_certs(settings)
113
+ result = Puppetserver::Ca::CertificateAuthority.new(@logger, settings).get_certificate_statuses
114
+ JSON.parse(result.body)
115
+ end
116
+
117
+ def parse(args)
118
+ results = {}
119
+ parser = self.class.parser(results)
120
+
121
+ errors = CliParsing.parse_with_errors(parser, args)
122
+
123
+ errors_were_handled = CliParsing.handle_errors(@logger, errors, parser.help)
124
+
125
+ exit_code = errors_were_handled ? 1 : nil
126
+
127
+ return results, exit_code
128
+ end
129
+ end
130
+ end
131
+ end
132
+ end
@@ -0,0 +1,101 @@
1
+ require 'puppetserver/ca/utils/cli_parsing'
2
+ require 'puppetserver/ca/utils/file_system'
3
+ require 'puppetserver/ca/config/puppet'
4
+ require 'puppetserver/ca/certificate_authority'
5
+
6
+ require 'optparse'
7
+
8
+ module Puppetserver
9
+ module Ca
10
+ module Action
11
+ class Revoke
12
+
13
+ include Puppetserver::Ca::Utils
14
+
15
+ CERTNAME_BLACKLIST = %w{--all --config}
16
+
17
+ SUMMARY = 'Revoke a given certificate'
18
+ BANNER = <<-BANNER
19
+ Usage:
20
+ puppetserver ca revoke [--help]
21
+ puppetserver ca revoke [--config] --certname CERTNAME[,ADDLCERTNAME]
22
+
23
+ Description:
24
+ Given one or more valid certnames, instructs the CA to revoke them over
25
+ HTTPS using the local agent's PKI
26
+
27
+ Options:
28
+ BANNER
29
+
30
+ def self.parser(parsed = {})
31
+ parsed['certnames'] = []
32
+ OptionParser.new do |o|
33
+ o.banner = BANNER
34
+ o.on('--certname foo,bar', Array,
35
+ 'One or more comma separated certnames') do |certs|
36
+ parsed['certnames'] += certs
37
+ end
38
+ o.on('--config PUPPET.CONF', 'Custom path to puppet.conf') do |conf|
39
+ parsed['config'] = conf
40
+ end
41
+ o.on('--help', 'Displays this revoke specific help output') do |help|
42
+ parsed['help'] = true
43
+ end
44
+ end
45
+ end
46
+
47
+ def initialize(logger)
48
+ @logger = logger
49
+ end
50
+
51
+ def parse(args)
52
+ results = {}
53
+ parser = self.class.parser(results)
54
+
55
+ errors = CliParsing.parse_with_errors(parser, args)
56
+
57
+ results['certnames'].each do |certname|
58
+ if CERTNAME_BLACKLIST.include?(certname)
59
+ errors << " Cannot manage cert named `#{certname}` from " +
60
+ "the CLI, if needed use the HTTP API directly"
61
+ end
62
+ end
63
+
64
+ if results['certnames'].empty?
65
+ errors << ' At least one certname is required to revoke'
66
+ end
67
+
68
+ errors_were_handled = CliParsing.handle_errors(@logger, errors, parser.help)
69
+
70
+ # if there is an exit_code then Cli will return it early, so we only
71
+ # return an exit_code if there's an error
72
+ exit_code = errors_were_handled ? 1 : nil
73
+
74
+ return results, exit_code
75
+ end
76
+
77
+ def run(args)
78
+ certnames = args['certnames']
79
+ config = args['config']
80
+
81
+ if config
82
+ errors = FileSystem.validate_file_paths(config)
83
+ return 1 if CliParsing.handle_errors(@logger, errors)
84
+ end
85
+
86
+ puppet = Config::Puppet.parse(config)
87
+ return 1 if CliParsing.handle_errors(@logger, puppet.errors)
88
+
89
+ passed = revoke_certs(certnames, puppet.settings)
90
+
91
+ return passed ? 0 : 1
92
+ end
93
+
94
+ def revoke_certs(certnames, settings)
95
+ ca = Puppetserver::Ca::CertificateAuthority.new(@logger, settings)
96
+ ca.revoke_certs(certnames)
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,126 @@
1
+ require 'puppetserver/ca/utils/cli_parsing'
2
+ require 'puppetserver/ca/utils/file_system'
3
+ require 'puppetserver/ca/config/puppet'
4
+ require 'puppetserver/ca/certificate_authority'
5
+
6
+ require 'optparse'
7
+ require 'openssl'
8
+ require 'net/https'
9
+
10
+ module Puppetserver
11
+ module Ca
12
+ module Action
13
+ class Sign
14
+
15
+ include Puppetserver::Ca::Utils
16
+
17
+ SUMMARY = 'Sign a given certificate'
18
+ BANNER = <<-BANNER
19
+ Usage:
20
+ puppetserver ca sign [--help]
21
+ puppetserver ca sign [--config] --certname CERTNAME[,CERTNAME]
22
+ puppetserver ca sign --all
23
+
24
+ Description:
25
+ Given a comma-separated list of valid certnames, instructs the CA to sign each cert.
26
+
27
+ Options:
28
+ BANNER
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('--all', 'Operate on all certnames') do |a|
43
+ parsed['all'] = true
44
+ end
45
+ end
46
+ end
47
+
48
+ def initialize(logger)
49
+ @logger = logger
50
+ end
51
+
52
+ def run(input)
53
+ config = input['config']
54
+
55
+ if config
56
+ errors = FileSystem.validate_file_paths(config)
57
+ return 1 if CliParsing.handle_errors(@logger, errors)
58
+ end
59
+
60
+ puppet = Config::Puppet.parse(config)
61
+ return 1 if CliParsing.handle_errors(@logger, puppet.errors)
62
+
63
+ ca = Puppetserver::Ca::CertificateAuthority.new(@logger, puppet.settings)
64
+
65
+ if input['all']
66
+ requested_certnames = get_all_pending_certs(ca)
67
+ if requested_certnames.nil?
68
+ return 1
69
+ end
70
+ else
71
+ requested_certnames = input['certname']
72
+ end
73
+
74
+ success = ca.sign_certs(requested_certnames)
75
+ return success ? 0 : 1
76
+ end
77
+
78
+ def get_all_pending_certs(ca)
79
+ if result = ca.get_certificate_statuses
80
+ select_pending_certs(result.body)
81
+ end
82
+ end
83
+
84
+ def select_pending_certs(get_result)
85
+ requested_certnames = JSON.parse(get_result).select{|e| e["state"] == "requested"}.map{|e| e["name"]}
86
+
87
+ if requested_certnames.empty?
88
+ @logger.err 'Error:'
89
+ @logger.err " No waiting certificate requests to sign"
90
+ return nil
91
+ end
92
+
93
+ return requested_certnames
94
+ end
95
+
96
+ def check_flag_usage(results)
97
+ if results['certname'] && results['all']
98
+ '--all and --certname cannot be used together'
99
+ elsif !results['certname'] && !results['all']
100
+ 'No arguments given'
101
+ elsif results['certname'] && results['certname'].include?('--all')
102
+ 'Cannot use --all with --certname. If you actually have a certificate request ' +
103
+ 'for a certifcate named --all, you need to use the HTTP API.'
104
+ end
105
+ end
106
+
107
+ def parse(args)
108
+ results = {}
109
+ parser = self.class.parser(results)
110
+
111
+ errors = CliParsing.parse_with_errors(parser, args)
112
+
113
+ if err = check_flag_usage(results)
114
+ errors << err
115
+ end
116
+
117
+ errors_were_handled = CliParsing.handle_errors(@logger, errors, parser.help)
118
+
119
+ exit_code = errors_were_handled ? 1 : nil
120
+
121
+ return results, exit_code
122
+ end
123
+ end
124
+ end
125
+ end
126
+ end