openvoxserver-ca 3.0.0.pre.rc1

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 (54) hide show
  1. checksums.yaml +7 -0
  2. data/.github/dependabot.yml +17 -0
  3. data/.github/release.yml +41 -0
  4. data/.github/workflows/gem_release.yaml +106 -0
  5. data/.github/workflows/prepare_release.yml +28 -0
  6. data/.github/workflows/release.yml +28 -0
  7. data/.github/workflows/unit_tests.yaml +45 -0
  8. data/.gitignore +14 -0
  9. data/.rspec +2 -0
  10. data/.travis.yml +16 -0
  11. data/CHANGELOG.md +15 -0
  12. data/CODEOWNERS +4 -0
  13. data/CODE_OF_CONDUCT.md +74 -0
  14. data/CONTRIBUTING.md +15 -0
  15. data/Gemfile +20 -0
  16. data/LICENSE +202 -0
  17. data/README.md +118 -0
  18. data/Rakefile +30 -0
  19. data/bin/console +14 -0
  20. data/bin/setup +8 -0
  21. data/exe/puppetserver-ca +10 -0
  22. data/lib/puppetserver/ca/action/clean.rb +109 -0
  23. data/lib/puppetserver/ca/action/delete.rb +286 -0
  24. data/lib/puppetserver/ca/action/enable.rb +140 -0
  25. data/lib/puppetserver/ca/action/generate.rb +330 -0
  26. data/lib/puppetserver/ca/action/import.rb +196 -0
  27. data/lib/puppetserver/ca/action/list.rb +253 -0
  28. data/lib/puppetserver/ca/action/migrate.rb +97 -0
  29. data/lib/puppetserver/ca/action/prune.rb +289 -0
  30. data/lib/puppetserver/ca/action/revoke.rb +108 -0
  31. data/lib/puppetserver/ca/action/setup.rb +188 -0
  32. data/lib/puppetserver/ca/action/sign.rb +146 -0
  33. data/lib/puppetserver/ca/certificate_authority.rb +418 -0
  34. data/lib/puppetserver/ca/cli.rb +145 -0
  35. data/lib/puppetserver/ca/config/puppet.rb +309 -0
  36. data/lib/puppetserver/ca/config/puppetserver.rb +84 -0
  37. data/lib/puppetserver/ca/errors.rb +40 -0
  38. data/lib/puppetserver/ca/host.rb +176 -0
  39. data/lib/puppetserver/ca/local_certificate_authority.rb +304 -0
  40. data/lib/puppetserver/ca/logger.rb +49 -0
  41. data/lib/puppetserver/ca/stub.rb +17 -0
  42. data/lib/puppetserver/ca/utils/cli_parsing.rb +67 -0
  43. data/lib/puppetserver/ca/utils/config.rb +61 -0
  44. data/lib/puppetserver/ca/utils/file_system.rb +109 -0
  45. data/lib/puppetserver/ca/utils/http_client.rb +232 -0
  46. data/lib/puppetserver/ca/utils/inventory.rb +84 -0
  47. data/lib/puppetserver/ca/utils/signing_digest.rb +27 -0
  48. data/lib/puppetserver/ca/version.rb +5 -0
  49. data/lib/puppetserver/ca/x509_loader.rb +170 -0
  50. data/lib/puppetserver/ca.rb +7 -0
  51. data/openvoxserver-ca.gemspec +31 -0
  52. data/tasks/spec.rake +15 -0
  53. data/tasks/vox.rake +19 -0
  54. metadata +154 -0
@@ -0,0 +1,108 @@
1
+ require 'optparse'
2
+
3
+ require 'puppetserver/ca/certificate_authority'
4
+ require 'puppetserver/ca/config/puppet'
5
+ require 'puppetserver/ca/utils/cli_parsing'
6
+ require 'puppetserver/ca/utils/file_system'
7
+
8
+ module Puppetserver
9
+ module Ca
10
+ module Action
11
+ class Revoke
12
+
13
+ include Puppetserver::Ca::Utils
14
+
15
+ CERTNAME_BLOCKLIST = %w{--all --config}
16
+
17
+ SUMMARY = 'Revoke certificate(s)'
18
+ BANNER = <<-BANNER
19
+ Usage:
20
+ puppetserver ca revoke [--help]
21
+ puppetserver ca revoke [--config] --certname NAME[,NAME]
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 NAME[,NAME]', Array,
35
+ 'One or more comma separated certnames') do |certs|
36
+ parsed['certnames'] += certs
37
+ end
38
+ o.on('--config 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_BLOCKLIST.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 = Errors.handle_with_usage(@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 Errors.handle_with_usage(@logger, errors)
84
+ end
85
+
86
+ puppet = Config::Puppet.parse(config, @logger)
87
+ return 1 if Errors.handle_with_usage(@logger, puppet.errors)
88
+
89
+ result = revoke_certs(certnames, puppet.settings)
90
+
91
+ case result
92
+ when :success
93
+ return 0
94
+ when :invalid
95
+ return 24
96
+ when :not_found, :error
97
+ return 1
98
+ end
99
+ end
100
+
101
+ def revoke_certs(certnames, settings)
102
+ ca = Puppetserver::Ca::CertificateAuthority.new(@logger, settings)
103
+ ca.revoke_certs(certnames)
104
+ end
105
+ end
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,188 @@
1
+ require 'optparse'
2
+
3
+ require 'puppetserver/ca/config/puppet'
4
+ require 'puppetserver/ca/errors'
5
+ require 'puppetserver/ca/local_certificate_authority'
6
+ require 'puppetserver/ca/utils/config'
7
+ require 'puppetserver/ca/utils/cli_parsing'
8
+ require 'puppetserver/ca/utils/file_system'
9
+ require 'puppetserver/ca/utils/signing_digest'
10
+
11
+ module Puppetserver
12
+ module Ca
13
+ module Action
14
+ class Setup
15
+ include Puppetserver::Ca::Utils
16
+
17
+ SUMMARY = "Setup a self-signed CA chain for Puppet Server"
18
+ BANNER = <<-BANNER
19
+ Usage:
20
+ puppetserver ca setup [--help]
21
+ puppetserver ca setup [--config PATH] [--subject-alt-names NAME[,NAME]]
22
+ [--certname NAME] [--ca-name NAME] [--root-ca-name NAME]
23
+
24
+ Description:
25
+ Setup a root and intermediate signing CA for Puppet Server
26
+ and store generated CA keys, certs, crls, and associated
27
+ server related files on disk.
28
+
29
+ The `--subject-alt-names` flag can be used to add SANs to the
30
+ certificate generated for the Puppet server. Multiple names can be
31
+ listed as a comma separated string. These can be either DNS names or
32
+ IP addresses, differentiated by prefixes: `DNS:foo.bar.com,IP:123.456.789`.
33
+ Names with no prefix will be treated as DNS names.
34
+
35
+ Options:
36
+ BANNER
37
+
38
+ def initialize(logger)
39
+ @logger = logger
40
+ end
41
+
42
+ def run(input)
43
+ # Validate config_path provided
44
+ config_path = input['config']
45
+ if config_path
46
+ errors = FileSystem.validate_file_paths(config_path)
47
+ return 1 if Errors.handle_with_usage(@logger, errors)
48
+ end
49
+
50
+ # Load, resolve, and validate puppet config settings
51
+ settings_overrides = {}
52
+ settings_overrides[:certname] = input['certname'] unless input['certname'].empty?
53
+ settings_overrides[:ca_name] = input['ca-name'] unless input['ca-name'].empty?
54
+ settings_overrides[:root_ca_name] = input['root-ca-name'] unless input['root-ca-name'].empty?
55
+ # Since puppet expects the key to be called 'dns_alt_names', we need to use that here
56
+ # to ensure that the overriding works correctly.
57
+ settings_overrides[:dns_alt_names] = input['subject-alt-names'] unless input['subject-alt-names'].empty?
58
+
59
+ puppet = Config::Puppet.new(config_path)
60
+ puppet.load(cli_overrides: settings_overrides, logger: @logger)
61
+ return 1 if Errors.handle_with_usage(@logger, puppet.errors)
62
+
63
+ # Load most secure signing digest we can for cers/crl/csr signing.
64
+ signer = SigningDigest.new
65
+ return 1 if Errors.handle_with_usage(@logger, signer.errors)
66
+
67
+ # Generate root and intermediate ca and put all the certificates, crls,
68
+ # and keys where they should go.
69
+ errors = generate_pki(puppet.settings, signer.digest)
70
+ return 1 if Errors.handle_with_usage(@logger, errors)
71
+
72
+ @logger.inform "Generation succeeded. Find your files in #{puppet.settings[:cadir]}"
73
+ return 0
74
+ end
75
+
76
+ def generate_pki(settings, signing_digest)
77
+ ca = Puppetserver::Ca::LocalCertificateAuthority.new(signing_digest, settings)
78
+
79
+ root_key, root_cert, root_crl = ca.create_root_cert
80
+ ca.create_intermediate_cert(root_key, root_cert)
81
+ server_key, server_cert = ca.create_server_cert
82
+ return ca.errors if ca.errors.any?
83
+
84
+ FileSystem.ensure_dirs([settings[:ssldir],
85
+ settings[:cadir],
86
+ settings[:certdir],
87
+ settings[:privatekeydir],
88
+ settings[:publickeydir],
89
+ settings[:signeddir]])
90
+
91
+ public_files = [
92
+ [settings[:cacert], [ca.cert, root_cert]],
93
+ [settings[:cacrl], [ca.crl, root_crl]],
94
+ [settings[:cadir] + '/infra_crl.pem', [ca.crl, root_crl]],
95
+ [settings[:hostcert], server_cert],
96
+ [settings[:localcacert], [ca.cert, root_cert]],
97
+ [settings[:hostcrl], [ca.crl, root_crl]],
98
+ [settings[:hostpubkey], server_key.public_key],
99
+ [settings[:capub], ca.key.public_key],
100
+ [settings[:cert_inventory], ca.inventory_entry(server_cert)],
101
+ [settings[:cadir] + '/infra_inventory.txt', ''],
102
+ [settings[:cadir] + '/infra_serials', ''],
103
+ [settings[:serial], "002"],
104
+ [File.join(settings[:signeddir], "#{settings[:certname]}.pem"), server_cert],
105
+ ]
106
+
107
+ private_files = [
108
+ [settings[:hostprivkey], server_key],
109
+ [settings[:rootkey], root_key],
110
+ [settings[:cakey], ca.key],
111
+ ]
112
+
113
+ files_to_check = public_files + private_files
114
+ # We don't want to error if server's keys exist. Certain workflows
115
+ # allow the agent to have already be installed with keys and then
116
+ # upgraded to be a server. The host class will honor keys, if both
117
+ # public and private exist, and error if only one exists - as is
118
+ # previous behavior.
119
+ files_to_check = files_to_check.map(&:first) - [settings[:hostpubkey], settings[:hostprivkey]]
120
+ errors = FileSystem.check_for_existing_files(files_to_check)
121
+
122
+ if !errors.empty?
123
+ instructions = <<-ERR
124
+ If you would really like to replace your CA, please delete the existing files first.
125
+ Note that any certificates that were issued by this CA will become invalid if you
126
+ replace it!
127
+ ERR
128
+ errors << instructions
129
+ return errors
130
+ end
131
+
132
+ public_files.each do |location, content|
133
+ FileSystem.write_file(location, content, 0644)
134
+ end
135
+
136
+ private_files.each do |location, content|
137
+ FileSystem.write_file(location, content, 0640)
138
+ end
139
+
140
+ Puppetserver::Ca::Utils::Config.symlink_to_old_cadir(settings[:cadir], settings[:confdir])
141
+
142
+ return []
143
+ end
144
+
145
+ def parse(cli_args)
146
+ results = {}
147
+ parser = self.class.parser(results)
148
+ errors = CliParsing.parse_with_errors(parser, cli_args)
149
+ errors_were_handled = Errors.handle_with_usage(@logger, errors, parser.help)
150
+ exit_code = errors_were_handled ? 1 : nil
151
+ return results, exit_code
152
+ end
153
+
154
+ def self.parser(parsed = {})
155
+ parsed['subject-alt-names'] = ''
156
+ parsed['ca-name'] = ''
157
+ parsed['root-ca-name'] = ''
158
+ parsed['certname'] = ''
159
+ OptionParser.new do |opts|
160
+ opts.banner = BANNER
161
+ opts.on('--help', 'Display this command-specific help output') do |help|
162
+ parsed['help'] = true
163
+ end
164
+ opts.on('--config CONF', 'Path to puppet.conf') do |conf|
165
+ parsed['config'] = conf
166
+ end
167
+ opts.on('--subject-alt-names NAME[,NAME]',
168
+ 'Subject alternative names for the server cert') do |sans|
169
+ parsed['subject-alt-names'] = sans
170
+ end
171
+ opts.on('--ca-name NAME',
172
+ 'Common name to use for the CA signing cert') do |name|
173
+ parsed['ca-name'] = name
174
+ end
175
+ opts.on('--root-ca-name NAME',
176
+ 'Common name to use for the self-signed Root CA cert') do |name|
177
+ parsed['root-ca-name'] = name
178
+ end
179
+ opts.on('--certname NAME',
180
+ 'Common name to use for the server cert') do |name|
181
+ parsed['certname'] = name
182
+ end
183
+ end
184
+ end
185
+ end
186
+ end
187
+ end
188
+ end
@@ -0,0 +1,146 @@
1
+ require 'net/https'
2
+ require 'openssl'
3
+ require 'optparse'
4
+
5
+ require 'puppetserver/ca/certificate_authority'
6
+ require 'puppetserver/ca/config/puppet'
7
+ require 'puppetserver/ca/errors'
8
+ require 'puppetserver/ca/utils/cli_parsing'
9
+ require 'puppetserver/ca/utils/file_system'
10
+
11
+ module Puppetserver
12
+ module Ca
13
+ module Action
14
+ class Sign
15
+
16
+ include Puppetserver::Ca::Utils
17
+
18
+ SUMMARY = 'Sign certificate request(s)'
19
+ BANNER = <<-BANNER
20
+ Usage:
21
+ puppetserver ca sign [--help]
22
+ puppetserver ca sign [--config] --certname NAME[,NAME]
23
+ puppetserver ca sign --all
24
+
25
+ Description:
26
+ Given a comma-separated list of valid certnames, instructs the CA to sign
27
+ each cert.
28
+
29
+ Options:
30
+ BANNER
31
+
32
+ def self.parser(parsed = {})
33
+ OptionParser.new do |opts|
34
+ opts.banner = BANNER
35
+ opts.on('--ttl TTL', 'The time-to-live for each cert signed') do |ttl|
36
+ parsed['ttl'] = ttl
37
+ end
38
+ opts.on('--certname NAME[,NAME]', Array, 'the name(s) of the cert(s) to be signed') do |cert|
39
+ parsed['certname'] = cert
40
+ end
41
+ opts.on('--config CONF', 'Custom path to Puppet\'s config file') do |conf|
42
+ parsed['config'] = conf
43
+ end
44
+ opts.on('--help', 'Display this command-specific help output') do |help|
45
+ parsed['help'] = true
46
+ end
47
+ opts.on('--all', 'Operate on all certnames') do |a|
48
+ parsed['all'] = true
49
+ end
50
+ end
51
+ end
52
+
53
+ def initialize(logger)
54
+ @logger = logger
55
+ end
56
+
57
+ def run(input)
58
+ config = input['config']
59
+
60
+ if config
61
+ errors = FileSystem.validate_file_paths(config)
62
+ return 1 if Errors.handle_with_usage(@logger, errors)
63
+ end
64
+
65
+ puppet = Config::Puppet.parse(config, @logger)
66
+ return 1 if Errors.handle_with_usage(@logger, puppet.errors)
67
+
68
+ ca = Puppetserver::Ca::CertificateAuthority.new(@logger, puppet.settings)
69
+ bulk_sign = ca.server_has_bulk_signing_endpoints
70
+
71
+ # Bulk sign endpoints don't allow setting TTL, so
72
+ # use single signing endpoint if TTL is specified.
73
+ success = false
74
+ if input['ttl'] || !bulk_sign
75
+ if input['all']
76
+ requested_certnames = get_all_pending_certs(ca)
77
+ return 1 if requested_certnames.nil?
78
+ return 24 if requested_certnames.empty?
79
+ else
80
+ requested_certnames = input['certname']
81
+ end
82
+
83
+ success = ca.sign_certs(requested_certnames, input['ttl'])
84
+ return success ? 0 : 1
85
+ else
86
+ result = input['all'] ? ca.sign_all : ca.sign_bulk(input['certname'])
87
+ case result
88
+ when :success
89
+ return 0
90
+ when :no_requests
91
+ return 24
92
+ else
93
+ return 1
94
+ end
95
+ end
96
+ end
97
+
98
+ def get_all_pending_certs(ca)
99
+ if result = ca.get_certificate_statuses
100
+ select_pending_certs(result.body)
101
+ end
102
+ end
103
+
104
+ def select_pending_certs(get_result)
105
+ requested_certnames = JSON.parse(get_result).select{|e| e["state"] == "requested"}.map{|e| e["name"]}
106
+
107
+ if requested_certnames.empty?
108
+ @logger.err 'Error:'
109
+ @logger.err " No waiting certificate requests to sign"
110
+ return requested_certnames
111
+ end
112
+
113
+ return requested_certnames
114
+ end
115
+
116
+ def check_flag_usage(results)
117
+ if results['certname'] && results['all']
118
+ '--all and --certname cannot be used together'
119
+ elsif !results['certname'] && !results['all']
120
+ 'No arguments given'
121
+ elsif results['certname'] && results['certname'].include?('--all')
122
+ 'Cannot use --all with --certname. If you actually have a certificate request ' +
123
+ 'for a certifcate named --all, you need to use the HTTP API.'
124
+ end
125
+ end
126
+
127
+ def parse(args)
128
+ results = {}
129
+ parser = self.class.parser(results)
130
+
131
+ errors = CliParsing.parse_with_errors(parser, args)
132
+
133
+ if err = check_flag_usage(results)
134
+ errors << err
135
+ end
136
+
137
+ errors_were_handled = Errors.handle_with_usage(@logger, errors, parser.help)
138
+
139
+ exit_code = errors_were_handled ? 1 : nil
140
+
141
+ return results, exit_code
142
+ end
143
+ end
144
+ end
145
+ end
146
+ end