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