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.
- checksums.yaml +7 -0
- data/.github/dependabot.yml +17 -0
- data/.github/release.yml +41 -0
- data/.github/workflows/gem_release.yaml +106 -0
- data/.github/workflows/prepare_release.yml +28 -0
- data/.github/workflows/release.yml +28 -0
- data/.github/workflows/unit_tests.yaml +45 -0
- data/.gitignore +14 -0
- data/.rspec +2 -0
- data/.travis.yml +16 -0
- data/CHANGELOG.md +15 -0
- data/CODEOWNERS +4 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/CONTRIBUTING.md +15 -0
- data/Gemfile +20 -0
- data/LICENSE +202 -0
- data/README.md +118 -0
- data/Rakefile +30 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/exe/puppetserver-ca +10 -0
- data/lib/puppetserver/ca/action/clean.rb +109 -0
- data/lib/puppetserver/ca/action/delete.rb +286 -0
- data/lib/puppetserver/ca/action/enable.rb +140 -0
- data/lib/puppetserver/ca/action/generate.rb +330 -0
- data/lib/puppetserver/ca/action/import.rb +196 -0
- data/lib/puppetserver/ca/action/list.rb +253 -0
- data/lib/puppetserver/ca/action/migrate.rb +97 -0
- data/lib/puppetserver/ca/action/prune.rb +289 -0
- data/lib/puppetserver/ca/action/revoke.rb +108 -0
- data/lib/puppetserver/ca/action/setup.rb +188 -0
- data/lib/puppetserver/ca/action/sign.rb +146 -0
- data/lib/puppetserver/ca/certificate_authority.rb +418 -0
- data/lib/puppetserver/ca/cli.rb +145 -0
- data/lib/puppetserver/ca/config/puppet.rb +309 -0
- data/lib/puppetserver/ca/config/puppetserver.rb +84 -0
- data/lib/puppetserver/ca/errors.rb +40 -0
- data/lib/puppetserver/ca/host.rb +176 -0
- data/lib/puppetserver/ca/local_certificate_authority.rb +304 -0
- data/lib/puppetserver/ca/logger.rb +49 -0
- data/lib/puppetserver/ca/stub.rb +17 -0
- data/lib/puppetserver/ca/utils/cli_parsing.rb +67 -0
- data/lib/puppetserver/ca/utils/config.rb +61 -0
- data/lib/puppetserver/ca/utils/file_system.rb +109 -0
- data/lib/puppetserver/ca/utils/http_client.rb +232 -0
- data/lib/puppetserver/ca/utils/inventory.rb +84 -0
- data/lib/puppetserver/ca/utils/signing_digest.rb +27 -0
- data/lib/puppetserver/ca/version.rb +5 -0
- data/lib/puppetserver/ca/x509_loader.rb +170 -0
- data/lib/puppetserver/ca.rb +7 -0
- data/openvoxserver-ca.gemspec +31 -0
- data/tasks/spec.rake +15 -0
- data/tasks/vox.rake +19 -0
- metadata +154 -0
@@ -0,0 +1,304 @@
|
|
1
|
+
require 'openssl'
|
2
|
+
|
3
|
+
require 'puppetserver/ca/host'
|
4
|
+
require 'puppetserver/ca/utils/file_system'
|
5
|
+
require 'puppetserver/ca/x509_loader'
|
6
|
+
|
7
|
+
module Puppetserver
|
8
|
+
module Ca
|
9
|
+
class LocalCertificateAuthority
|
10
|
+
|
11
|
+
# Make the certificate valid as of yesterday, because so many people's
|
12
|
+
# clocks are out of sync. This gives one more day of validity than people
|
13
|
+
# might expect, but is better than making every person who has a messed up
|
14
|
+
# clock fail, and better than having every cert we generate expire a day
|
15
|
+
# before the user expected it to when they asked for "one year".
|
16
|
+
CERT_VALID_FROM = (Time.now - (60*60*24)).freeze
|
17
|
+
|
18
|
+
SSL_SERVER_CERT = "serverAuth"
|
19
|
+
SSL_CLIENT_CERT = "clientAuth"
|
20
|
+
|
21
|
+
CLI_AUTH_EXT_OID = "1.3.6.1.4.1.34380.1.3.39"
|
22
|
+
|
23
|
+
SERVER_EXTENSIONS = [
|
24
|
+
["basicConstraints", "CA:FALSE", true],
|
25
|
+
["nsComment", "Puppet Server Internal Certificate", false],
|
26
|
+
["authorityKeyIdentifier", "keyid:always", false],
|
27
|
+
["extendedKeyUsage", "#{SSL_SERVER_CERT}, #{SSL_CLIENT_CERT}", true],
|
28
|
+
["keyUsage", "keyEncipherment, digitalSignature", true],
|
29
|
+
["subjectKeyIdentifier", "hash", false]
|
30
|
+
].freeze
|
31
|
+
|
32
|
+
CA_EXTENSIONS = [
|
33
|
+
["basicConstraints", "CA:TRUE", true],
|
34
|
+
["keyUsage", "keyCertSign, cRLSign", true],
|
35
|
+
["subjectKeyIdentifier", "hash", false],
|
36
|
+
["nsComment", "Puppet Server Internal Certificate", false],
|
37
|
+
["authorityKeyIdentifier", "keyid:always", false]
|
38
|
+
].freeze
|
39
|
+
|
40
|
+
attr_reader :cert, :cert_bundle, :key, :crl, :crl_chain
|
41
|
+
|
42
|
+
def initialize(digest, settings)
|
43
|
+
@digest = digest
|
44
|
+
@host = Host.new(digest)
|
45
|
+
@settings = settings
|
46
|
+
@errors = []
|
47
|
+
|
48
|
+
if ssl_assets_exist?
|
49
|
+
loader = Puppetserver::Ca::X509Loader.new(@settings[:cacert], @settings[:cakey], @settings[:cacrl])
|
50
|
+
if loader.errors.empty?
|
51
|
+
load_ssl_components(loader)
|
52
|
+
else
|
53
|
+
@errors += loader.errors
|
54
|
+
@errors << "CA not initialized. Please set up your CA before attempting to generate certs offline."
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def ssl_assets_exist?
|
60
|
+
File.exist?(@settings[:cacert]) &&
|
61
|
+
File.exist?(@settings[:cakey]) &&
|
62
|
+
File.exist?(@settings[:cacrl])
|
63
|
+
end
|
64
|
+
|
65
|
+
def load_ssl_components(loader)
|
66
|
+
@cert_bundle = loader.certs
|
67
|
+
@key = loader.key
|
68
|
+
@cert = loader.cert
|
69
|
+
@crl_chain = loader.crls
|
70
|
+
@crl = loader.crl
|
71
|
+
end
|
72
|
+
|
73
|
+
# Initialize SSL state
|
74
|
+
#
|
75
|
+
# This method is similar to {#load_ssl_components}, but has extra
|
76
|
+
# logic for initializing components that may not be present when
|
77
|
+
# the CA is set up for the first time. For example, SSL components
|
78
|
+
# provided by an external CA will often not include a pre-generated
|
79
|
+
# leaf CRL.
|
80
|
+
#
|
81
|
+
# @note Check {#errors} after calling this method for issues that
|
82
|
+
# may have occurred during initialization.
|
83
|
+
#
|
84
|
+
# @param loader [Puppetserver::Ca::X509Loader]
|
85
|
+
# @return [void]
|
86
|
+
def initialize_ssl_components(loader)
|
87
|
+
@cert_bundle = loader.certs
|
88
|
+
@key = loader.key
|
89
|
+
@cert = loader.cert
|
90
|
+
|
91
|
+
if loader.crl.nil?
|
92
|
+
loader.crl = create_crl_for(@cert, @key)
|
93
|
+
|
94
|
+
loader.validate_full_chain(@cert_bundle, loader.crls)
|
95
|
+
@errors += loader.errors
|
96
|
+
end
|
97
|
+
|
98
|
+
@crl_chain = loader.crls
|
99
|
+
@crl = loader.crl
|
100
|
+
end
|
101
|
+
|
102
|
+
def errors
|
103
|
+
@errors += @host.errors
|
104
|
+
end
|
105
|
+
|
106
|
+
def valid_until
|
107
|
+
Time.now + @settings[:ca_ttl]
|
108
|
+
end
|
109
|
+
|
110
|
+
def extension_factory_for(ca, cert = nil)
|
111
|
+
ef = OpenSSL::X509::ExtensionFactory.new
|
112
|
+
ef.issuer_certificate = ca
|
113
|
+
ef.subject_certificate = cert if cert
|
114
|
+
|
115
|
+
ef
|
116
|
+
end
|
117
|
+
|
118
|
+
def inventory_entry(cert)
|
119
|
+
"0x%04x %s %s %s" % [cert.serial, format_time(cert.not_before),
|
120
|
+
format_time(cert.not_after), cert.subject]
|
121
|
+
end
|
122
|
+
|
123
|
+
def next_serial(serial_file)
|
124
|
+
if File.exist?(serial_file)
|
125
|
+
File.read(serial_file).to_i(16)
|
126
|
+
else
|
127
|
+
1
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def format_time(time)
|
132
|
+
time.strftime('%Y-%m-%dT%H:%M:%S%Z')
|
133
|
+
end
|
134
|
+
|
135
|
+
def create_server_cert
|
136
|
+
server_cert = nil
|
137
|
+
server_key = @host.create_private_key(@settings[:keylength],
|
138
|
+
@settings[:hostprivkey],
|
139
|
+
@settings[:hostpubkey])
|
140
|
+
if server_key
|
141
|
+
server_csr = @host.create_csr(name: @settings[:certname], key: server_key)
|
142
|
+
if @settings[:subject_alt_names].empty?
|
143
|
+
alt_names = "DNS:puppet, DNS:#{@settings[:certname]}"
|
144
|
+
else
|
145
|
+
alt_names = @settings[:subject_alt_names]
|
146
|
+
end
|
147
|
+
|
148
|
+
server_cert = sign_authorized_cert(server_csr, alt_names)
|
149
|
+
end
|
150
|
+
|
151
|
+
return server_key, server_cert
|
152
|
+
end
|
153
|
+
|
154
|
+
def sign_authorized_cert(csr, alt_names = '')
|
155
|
+
cert = OpenSSL::X509::Certificate.new
|
156
|
+
cert.public_key = csr.public_key
|
157
|
+
cert.subject = csr.subject
|
158
|
+
cert.issuer = @cert.subject
|
159
|
+
cert.version = 2
|
160
|
+
cert.serial = next_serial(@settings[:serial])
|
161
|
+
cert.not_before = CERT_VALID_FROM
|
162
|
+
cert.not_after = valid_until
|
163
|
+
|
164
|
+
return unless add_custom_extensions(cert)
|
165
|
+
|
166
|
+
ef = extension_factory_for(@cert, cert)
|
167
|
+
add_authorized_extensions(cert, ef)
|
168
|
+
|
169
|
+
if !alt_names.empty?
|
170
|
+
add_subject_alt_names_extension(alt_names, cert, ef)
|
171
|
+
end
|
172
|
+
|
173
|
+
cert.sign(@key, @digest)
|
174
|
+
|
175
|
+
cert
|
176
|
+
end
|
177
|
+
|
178
|
+
def add_authorized_extensions(cert, ef)
|
179
|
+
SERVER_EXTENSIONS.each do |ext|
|
180
|
+
extension = ef.create_extension(*ext)
|
181
|
+
cert.add_extension(extension)
|
182
|
+
end
|
183
|
+
|
184
|
+
# Status API access for the CA CLI
|
185
|
+
cli_auth_ext = OpenSSL::X509::Extension.new(CLI_AUTH_EXT_OID, OpenSSL::ASN1::UTF8String.new("true").to_der, false)
|
186
|
+
cert.add_extension(cli_auth_ext)
|
187
|
+
end
|
188
|
+
|
189
|
+
def add_subject_alt_names_extension(alt_names, cert, ef)
|
190
|
+
alt_names_ext = ef.create_extension("subjectAltName", alt_names, false)
|
191
|
+
cert.add_extension(alt_names_ext)
|
192
|
+
end
|
193
|
+
|
194
|
+
# This takes all the extension requests from csr_attributes.yaml and
|
195
|
+
# adds those to the cert
|
196
|
+
def add_custom_extensions(cert)
|
197
|
+
extension_requests = @host.get_extension_requests(@settings[:csr_attributes])
|
198
|
+
|
199
|
+
if extension_requests
|
200
|
+
extensions = @host.validated_extensions(extension_requests)
|
201
|
+
extensions.each do |ext|
|
202
|
+
cert.add_extension(ext)
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
@host.errors.empty?
|
207
|
+
end
|
208
|
+
|
209
|
+
def create_root_cert
|
210
|
+
root_key = @host.create_private_key(@settings[:keylength])
|
211
|
+
root_cert = self_signed_ca(root_key)
|
212
|
+
root_crl = create_crl_for(root_cert, root_key)
|
213
|
+
|
214
|
+
return root_key, root_cert, root_crl
|
215
|
+
end
|
216
|
+
|
217
|
+
def self_signed_ca(key)
|
218
|
+
cert = OpenSSL::X509::Certificate.new
|
219
|
+
|
220
|
+
cert.public_key = key.public_key
|
221
|
+
cert.subject = OpenSSL::X509::Name.new([["CN", @settings[:root_ca_name]]])
|
222
|
+
cert.issuer = cert.subject
|
223
|
+
cert.version = 2
|
224
|
+
cert.serial = 1
|
225
|
+
|
226
|
+
cert.not_before = CERT_VALID_FROM
|
227
|
+
cert.not_after = valid_until
|
228
|
+
|
229
|
+
ef = extension_factory_for(cert, cert)
|
230
|
+
CA_EXTENSIONS.each do |ext|
|
231
|
+
extension = ef.create_extension(*ext)
|
232
|
+
cert.add_extension(extension)
|
233
|
+
end
|
234
|
+
|
235
|
+
cert.sign(key, @digest)
|
236
|
+
|
237
|
+
cert
|
238
|
+
end
|
239
|
+
|
240
|
+
def create_crl_for(cert, key)
|
241
|
+
crl = OpenSSL::X509::CRL.new
|
242
|
+
crl.version = 1
|
243
|
+
crl.issuer = cert.subject
|
244
|
+
|
245
|
+
ef = extension_factory_for(cert)
|
246
|
+
crl.add_extension(
|
247
|
+
ef.create_extension(["authorityKeyIdentifier", "keyid:always", false]))
|
248
|
+
crl.add_extension(
|
249
|
+
OpenSSL::X509::Extension.new("crlNumber", OpenSSL::ASN1::Integer(0)))
|
250
|
+
|
251
|
+
crl.last_update = CERT_VALID_FROM
|
252
|
+
crl.next_update = valid_until
|
253
|
+
crl.sign(key, @digest)
|
254
|
+
|
255
|
+
# FIXME: Workaround a bug in jruby-openssl. Without this, #to_pem return an invalid CRL:
|
256
|
+
# ----BEGIN X509 CRL-----
|
257
|
+
# MAA=
|
258
|
+
# -----END X509 CRL-----
|
259
|
+
# See:
|
260
|
+
# https://github.com/jruby/jruby-openssl/issues/163
|
261
|
+
# https://github.com/jruby/jruby-openssl/pull/333
|
262
|
+
crl = OpenSSL::X509::CRL.new(crl.to_der)
|
263
|
+
|
264
|
+
crl
|
265
|
+
end
|
266
|
+
|
267
|
+
def create_intermediate_cert(root_key, root_cert)
|
268
|
+
@key = @host.create_private_key(@settings[:keylength])
|
269
|
+
int_csr = @host.create_csr(name: @settings[:ca_name], key: @key)
|
270
|
+
@cert = sign_intermediate(root_key, root_cert, int_csr)
|
271
|
+
@crl = create_crl_for(@cert, @key)
|
272
|
+
|
273
|
+
return nil
|
274
|
+
end
|
275
|
+
|
276
|
+
def sign_intermediate(ca_key, ca_cert, csr)
|
277
|
+
cert = OpenSSL::X509::Certificate.new
|
278
|
+
|
279
|
+
cert.public_key = csr.public_key
|
280
|
+
cert.subject = csr.subject
|
281
|
+
cert.issuer = ca_cert.subject
|
282
|
+
cert.version = 2
|
283
|
+
cert.serial = 2
|
284
|
+
|
285
|
+
cert.not_before = CERT_VALID_FROM
|
286
|
+
cert.not_after = valid_until
|
287
|
+
|
288
|
+
ef = extension_factory_for(ca_cert, cert)
|
289
|
+
CA_EXTENSIONS.each do |ext|
|
290
|
+
extension = ef.create_extension(*ext)
|
291
|
+
cert.add_extension(extension)
|
292
|
+
end
|
293
|
+
|
294
|
+
cert.sign(ca_key, @digest)
|
295
|
+
|
296
|
+
cert
|
297
|
+
end
|
298
|
+
|
299
|
+
def update_serial_file(serial)
|
300
|
+
Puppetserver::Ca::Utils::FileSystem.write_file(@settings[:serial], serial.to_s(16), 0644)
|
301
|
+
end
|
302
|
+
end
|
303
|
+
end
|
304
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Puppetserver
|
2
|
+
module Ca
|
3
|
+
class Logger
|
4
|
+
LEVELS = {error: 1, warning: 2, info: 3, debug: 4}
|
5
|
+
|
6
|
+
def initialize(level = :info, out = STDOUT, err = STDERR)
|
7
|
+
@level = LEVELS[level]
|
8
|
+
if @level.nil?
|
9
|
+
raise ArgumentError, "Unknown log level #{level}"
|
10
|
+
end
|
11
|
+
|
12
|
+
@out = out
|
13
|
+
@err = err
|
14
|
+
end
|
15
|
+
|
16
|
+
def level
|
17
|
+
@level
|
18
|
+
end
|
19
|
+
|
20
|
+
def debug?
|
21
|
+
return @level >= LEVELS[:debug]
|
22
|
+
end
|
23
|
+
|
24
|
+
def debug(text)
|
25
|
+
if debug?
|
26
|
+
@out.puts(text)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def inform(text)
|
31
|
+
if @level >= LEVELS[:info]
|
32
|
+
@out.puts(text)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def warn(text)
|
37
|
+
if @level >= LEVELS[:warning]
|
38
|
+
@err.puts(text)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def err(text)
|
43
|
+
if @level >= LEVELS[:error]
|
44
|
+
@err.puts(text)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
|
3
|
+
module Puppetserver
|
4
|
+
module Ca
|
5
|
+
module Stub
|
6
|
+
KEY_PATH = '/etc/puppetlabs/puppet/ssl/ca/ca_key.pem'
|
7
|
+
BUNDLE_PATH = '/etc/puppetlabs/puppet/ssl/ca/ca_crt.pem'
|
8
|
+
CRL_PATH = '/etc/puppetlabs/puppet/ssl/ca/ca_crl.pem'
|
9
|
+
|
10
|
+
def self.import(key, bundle, crl)
|
11
|
+
FileUtils.copy(File.absolute_path(key), KEY_PATH)
|
12
|
+
FileUtils.copy(File.absolute_path(bundle), BUNDLE_PATH)
|
13
|
+
FileUtils.copy(File.absolute_path(crl), CRL_PATH)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
module Puppetserver
|
2
|
+
module Ca
|
3
|
+
module Utils
|
4
|
+
module CliParsing
|
5
|
+
def self.parse_without_raising(parser, args)
|
6
|
+
all, not_flags, malformed_flags, unknown_flags = [], [], [], []
|
7
|
+
|
8
|
+
begin
|
9
|
+
# OptionParser calls this block when it finds a value that doesn't
|
10
|
+
# start with one or two dashes and doesn't follow a flag that
|
11
|
+
# consumes a value.
|
12
|
+
parser.order!(args) do |not_flag|
|
13
|
+
not_flags << not_flag
|
14
|
+
all << not_flag
|
15
|
+
end
|
16
|
+
rescue OptionParser::MissingArgument => e
|
17
|
+
malformed_flags += e.args
|
18
|
+
all += e.args
|
19
|
+
|
20
|
+
retry
|
21
|
+
rescue OptionParser::ParseError => e
|
22
|
+
flag = e.args.first
|
23
|
+
unknown_flags << flag
|
24
|
+
all << flag
|
25
|
+
|
26
|
+
if does_not_contain_argument(flag) &&
|
27
|
+
args.first &&
|
28
|
+
next_arg_is_not_another_flag(args.first)
|
29
|
+
|
30
|
+
value = args.shift
|
31
|
+
unknown_flags << value
|
32
|
+
all << value
|
33
|
+
end
|
34
|
+
|
35
|
+
retry
|
36
|
+
end
|
37
|
+
|
38
|
+
return all, not_flags, malformed_flags, unknown_flags
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.parse_with_errors(parser, args)
|
42
|
+
errors = []
|
43
|
+
|
44
|
+
_, non_flags, malformed_flags, unknown_flags = parse_without_raising(parser, args)
|
45
|
+
|
46
|
+
malformed_flags.each {|f| errors << " Missing argument to flag `#{f}`" }
|
47
|
+
unknown_flags.each {|f| errors << " Unknown flag or argument `#{f}`" }
|
48
|
+
non_flags.each {|f| errors << " Unknown input `#{f}`" }
|
49
|
+
|
50
|
+
errors
|
51
|
+
end
|
52
|
+
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
# eg. --flag=argument-to-flag
|
57
|
+
def self.does_not_contain_argument(flag)
|
58
|
+
!flag.include?('=')
|
59
|
+
end
|
60
|
+
|
61
|
+
def self.next_arg_is_not_another_flag(maybe_an_arg)
|
62
|
+
!maybe_an_arg.start_with?('-')
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'puppetserver/ca/utils/file_system'
|
2
|
+
|
3
|
+
module Puppetserver
|
4
|
+
module Ca
|
5
|
+
module Utils
|
6
|
+
module Config
|
7
|
+
|
8
|
+
def self.running_as_root?
|
9
|
+
!Gem.win_platform? && Process::UID.eid == 0
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.munge_alt_names(names)
|
13
|
+
raw_names = names.split(/\s*,\s*/).map(&:strip)
|
14
|
+
munged_names = raw_names.map do |name|
|
15
|
+
# Prepend the DNS tag if no tag was specified
|
16
|
+
if !name.start_with?("IP:") && !name.start_with?("DNS:")
|
17
|
+
"DNS:#{name}"
|
18
|
+
else
|
19
|
+
name
|
20
|
+
end
|
21
|
+
end.sort.uniq.join(", ")
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.puppet_confdir
|
25
|
+
if running_as_root?
|
26
|
+
'/etc/puppetlabs/puppet'
|
27
|
+
else
|
28
|
+
"#{ENV['HOME']}/.puppetlabs/etc/puppet"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.puppetserver_confdir(puppet_confdir)
|
33
|
+
File.join(File.dirname(puppet_confdir), 'puppetserver')
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.default_ssldir(confdir = puppet_confdir)
|
37
|
+
File.join(confdir, 'ssl')
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.old_default_cadir(confdir = puppet_confdir)
|
41
|
+
File.join(confdir, 'ssl', 'ca')
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.new_default_cadir(confdir = puppet_confdir)
|
45
|
+
File.join(puppetserver_confdir(confdir), 'ca')
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.symlink_to_old_cadir(current_cadir, puppet_confdir)
|
49
|
+
old_cadir = old_default_cadir(puppet_confdir)
|
50
|
+
new_cadir = new_default_cadir(puppet_confdir)
|
51
|
+
return if current_cadir != new_cadir
|
52
|
+
# This is only run on setup/import, so there should be no files in the
|
53
|
+
# old cadir, so it should be safe to forcibly remove it (which we need
|
54
|
+
# to do in order to create a symlink).
|
55
|
+
Puppetserver::Ca::Utils::FileSystem.forcibly_symlink(new_cadir, old_cadir)
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
require 'etc'
|
2
|
+
require 'fileutils'
|
3
|
+
|
4
|
+
module Puppetserver
|
5
|
+
module Ca
|
6
|
+
module Utils
|
7
|
+
class FileSystem
|
8
|
+
|
9
|
+
DIR_MODES = {
|
10
|
+
:ssldir => 0771,
|
11
|
+
:cadir => 0755,
|
12
|
+
:certdir => 0755,
|
13
|
+
:privatekeydir => 0750,
|
14
|
+
:publickeydir => 0755,
|
15
|
+
:signeddir => 0755
|
16
|
+
}
|
17
|
+
|
18
|
+
def self.instance
|
19
|
+
@instance ||= new
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.write_file(*args)
|
23
|
+
instance.write_file(*args)
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.ensure_dirs(one_or_more_dirs)
|
27
|
+
Array(one_or_more_dirs).each do |directory|
|
28
|
+
instance.ensure_dir(directory)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.validate_file_paths(one_or_more_paths)
|
33
|
+
errors = []
|
34
|
+
Array(one_or_more_paths).each do |path|
|
35
|
+
if !File.exist?(path) || !File.readable?(path)
|
36
|
+
errors << "Could not read file '#{path}'"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
errors
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.check_for_existing_files(one_or_more_paths)
|
44
|
+
errors = []
|
45
|
+
Array(one_or_more_paths).each do |path|
|
46
|
+
if File.exist?(path)
|
47
|
+
errors << "Existing file at '#{path}'"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
errors
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.forcibly_symlink(source, link_target)
|
54
|
+
FileUtils.remove_dir(link_target, true)
|
55
|
+
FileUtils.symlink(source, link_target)
|
56
|
+
# Ensure the symlink has the same ownership as the source.
|
57
|
+
# This requires using `FileUtils.chown` rather than `File.chown`, as
|
58
|
+
# the latter will update the ownership of the source rather than the
|
59
|
+
# link itself.
|
60
|
+
# Symlink permissions are ignored in favor of the source's permissions,
|
61
|
+
# so we don't have to change those.
|
62
|
+
source_info = File.stat(source)
|
63
|
+
FileUtils.chown(source_info.uid, source_info.gid, link_target)
|
64
|
+
end
|
65
|
+
|
66
|
+
def initialize
|
67
|
+
@user, @group = find_user_and_group
|
68
|
+
end
|
69
|
+
|
70
|
+
def find_user_and_group
|
71
|
+
if !running_as_root?
|
72
|
+
return Process.euid, Process.egid
|
73
|
+
else
|
74
|
+
if pe_puppet_exists?
|
75
|
+
return 'pe-puppet', 'pe-puppet'
|
76
|
+
else
|
77
|
+
return 'puppet', 'puppet'
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def running_as_root?
|
83
|
+
!Gem.win_platform? && Process.euid == 0
|
84
|
+
end
|
85
|
+
|
86
|
+
def pe_puppet_exists?
|
87
|
+
!!(Etc.getpwnam('pe-puppet') rescue nil)
|
88
|
+
end
|
89
|
+
|
90
|
+
def write_file(path, one_or_more_objects, mode)
|
91
|
+
File.open(path, 'w', mode) do |f|
|
92
|
+
Array(one_or_more_objects).each do |object|
|
93
|
+
f.puts object.to_s
|
94
|
+
end
|
95
|
+
end
|
96
|
+
FileUtils.chown(@user, @group, path)
|
97
|
+
end
|
98
|
+
|
99
|
+
# Warning: directory mode should be specified in DIR_MODES above
|
100
|
+
def ensure_dir(directory)
|
101
|
+
if !File.exist?(directory)
|
102
|
+
FileUtils.mkdir_p(directory, mode: DIR_MODES[directory])
|
103
|
+
FileUtils.chown(@user, @group, directory)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|