Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 59 additions & 9 deletions acceptance/suites/tests/authorization/default_rules.rb
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,22 @@ def curl_unauthenticated(path)
on(master, curl)
end

# Issue a request with a certificate other than the primary's own. The
# primary's certificate carries the pp_cli_auth extension so that its CLI
# tooling can reach administrative endpoints, which makes it unsuitable for
# testing rules that gate on that extension. An ordinary agent certificate
# does not carry it.
def curl_with_cert(certname, path, &block)
curl = 'curl '
curl += "--cert $(puppet config print ssldir)/certs/#{certname}.pem "
curl += "--key $(puppet config print ssldir)/private_keys/#{certname}.pem "
curl += '--cacert $(puppet config print localcacert) '
curl += "--write-out '\\nSTATUSCODE=%{http_code}\\n' "
curl += "https://#{master}:8140#{path}"
result = on(master, curl)
block.call(result.stdout)
end

def assert_allowed(stdout, expected_statuscode = 200)
refute_match(/Forbidden request/, stdout)
assert_match(/STATUSCODE=#{expected_statuscode}/, stdout)
Expand All @@ -52,6 +68,27 @@ def report_query(node)
curl += '\",\"metrics\":{},\"logs\":[],\"resource_statuses\":{}}"'
end

# An ordinary agent certificate, used to test rules that are gated on the
# pp_cli_auth extension. Generated once the server is up, and cleaned up in
# the teardown below.
agent_certname = 'filebucket-read-test-agent'

# 'puppetserver ca generate' refuses to run while any key file for the certname
# is still on disk, so clear them before generating as well as afterwards. That
# keeps the test repeatable if an earlier run was interrupted before teardown.
def revoke_and_remove_cert(certname)
on(master, "puppetserver ca clean --certname #{certname}",
:accept_all_exit_codes => true)
on(master, "rm -f $(puppet config print ssldir)/certs/#{certname}.pem " \
"$(puppet config print ssldir)/private_keys/#{certname}.pem " \
"$(puppet config print ssldir)/public_keys/#{certname}.pem",
:accept_all_exit_codes => true)
end

teardown do
revoke_and_remove_cert(agent_certname)
end

with_puppet_running_on(master, {}) do
masterfqdn = on(master, '/opt/puppetlabs/bin/facter fqdn').stdout.chomp

Expand Down Expand Up @@ -98,15 +135,14 @@ def report_query(node)
assert_allowed(stdout)
end

# In PE, the master (specifically the orchestrator)
# is allowed to make report submissions on behalf of
# other nodes
# Nodes may submit only their own reports (allow: "$1"); submitting on
# behalf of other nodes was a PE orchestrator capability that OpenVox
# does not have. Note: this must not be guarded on master.is_pe? --
# this suite's beaker options do not set a host type, and beaker's
# default type is 'pe', which made is_pe? return true on FOSS hosts
# and select the wrong assertion.
curl_authenticated(report_query('notme')) do |stdout|
if master.is_pe?
assert_allowed(stdout)
else
assert_denied(stdout, /\/puppet\/v3\/report\/notme \(method :put\)/)
end
assert_denied(stdout, /\/puppet\/v3\/report\/notme \(method :put\)/)
end

curl_unauthenticated(report_query(masterfqdn)) do |stdout|
Expand Down Expand Up @@ -143,6 +179,9 @@ def report_query(node)
sum = 'a' * 64
bucket_path = "/puppet/v3/file_bucket_file/sha256/#{sum}?environment=production"

revoke_and_remove_cert(agent_certname)
on(master, "puppetserver ca generate --certname=#{agent_certname}")

# Agents are allowed 'head' so they can test whether content is already
# stored before uploading it. We'd actually need to store a file in the
# filebucket in order to get back a 200, but we know that a 404 means we got
Expand All @@ -151,9 +190,20 @@ def report_query(node)
assert_allowed(stdout, 404)
end

curl_with_cert(agent_certname, "#{bucket_path} --head") do |stdout|
assert_allowed(stdout, 404)
end

# Reading content back out is restricted to certificates carrying the
# pp_cli_auth extension, which an ordinary agent certificate does not have.
# pp_cli_auth extension. The primary's own certificate carries it, so the
# request reaches the endpoint and is answered with a 404.
curl_authenticated(bucket_path) do |stdout|
assert_allowed(stdout, 404)
end

# An ordinary agent certificate does not carry pp_cli_auth, so reads are
# refused even though the same certificate may store content.
curl_with_cert(agent_certname, bucket_path) do |stdout|
assert_denied(stdout, /\/puppet\/v3\/file_bucket_file\/sha256\/#{sum} \(method :get\)/)
end

Expand Down
14 changes: 13 additions & 1 deletion spec/puppet-server-lib/puppet/jvm/config_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,23 @@
end
end

it "falls back to the vendored PEM CA bundle when the keystore is absent" do
stub_const('Puppet::Server::Config::PUPPET_KEYSTORE_LOCATION',
'spec/fixtures/does-not-exist')
stub_const('Puppet::Server::Config::PUPPET_CA_BUNDLE_LOCATION',
'spec/fixtures/ca-cert.pem')

expect(Puppet).not_to receive(:warning)

ssl_context = Puppet::Server::Config.puppet_and_system_ssl_context
expect(ssl_context).to be_a_kind_of(Java::JavaxNetSsl::SSLContext)
end

it "warns if :ssl_trust_store is set but not readable" do
Puppet[:ssl_trust_store] = "spec/fixtures/foo.pem"
allow(File).to receive(:exist?).and_return(false)

expect(Puppet).to receive(:warning).with(/Could not find OpenVox-vendored keystore/)
expect(Puppet).to receive(:warning).with(/Could not find an OpenVox-vendored trust store/)
expect(Puppet).to receive(:warning).with(/The 'ssl_trust_store' setting does not refer to a file/)

Puppet::Server::Config.puppet_and_system_ssl_context
Expand Down
11 changes: 10 additions & 1 deletion src/ruby/puppetserver-lib/puppet/server/config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

class Puppet::Server::Config
PUPPET_KEYSTORE_LOCATION = '/opt/puppetlabs/puppet/ssl/puppet-cacerts'
PUPPET_CA_BUNDLE_LOCATION = '/opt/puppetlabs/puppet/ssl/cert.pem'
CERT_REGEX = /.*-----BEGIN CERTIFICATE-----.*/

def self.initialize_puppet_server(puppet_server_config)
Expand Down Expand Up @@ -82,8 +83,16 @@ def self.load_puppet_and_system_ssl_context
truststore = stores['truststore']
if File.exist?(PUPPET_KEYSTORE_LOCATION)
associate_entries(truststore, PUPPET_KEYSTORE_LOCATION)
elsif File.exist?(PUPPET_CA_BUNDLE_LOCATION)
# puppet-runtime stopped shipping the Java keystore form of the
# vendored CA bundle (puppet-runtime@066fd48); load the PEM bundle
# it still ships instead.
SSLUtils.associateCertsFromReader(
truststore,
'openvox_vendored_ca_bundle',
FileReader.new(PUPPET_CA_BUNDLE_LOCATION))
else
Puppet.warning("Could not find OpenVox-vendored keystore at '#{PUPPET_KEYSTORE_LOCATION}'")
Puppet.warning("Could not find an OpenVox-vendored trust store at '#{PUPPET_KEYSTORE_LOCATION}' or '#{PUPPET_CA_BUNDLE_LOCATION}'")
end

if additional_store_location = Puppet[:ssl_trust_store]
Expand Down