Setup SSL certificates for the PostgreSQL test server

This allows better testing of encryption or openssl related things.

The tcp_gate_scheduler blocks while sending lots of data to the server per SSL.
For now don't use SSL for the scheduler tests therefore.
This commit is contained in:
Lars Kanis 2022-03-09 12:21:40 +01:00
parent 031bf25794
commit f270b714c6
3 changed files with 147 additions and 2 deletions

View File

@ -4,6 +4,7 @@ require 'pathname'
require 'rspec'
require 'shellwords'
require 'pg'
require 'openssl'
require_relative 'helpers/scheduler.rb'
require_relative 'helpers/tcp_gate_scheduler.rb'
@ -236,6 +237,22 @@ module PG::TestingHelpers
log_and_run @logfile, 'initdb', '-E', 'UTF8', '--no-locale', '-D', @test_pgdata.to_s
end
unless (@test_pgdata+"ruby-pg-server-cert").exist?
trace "Enable SSL"
# Enable SSL in server config
File.open(@test_pgdata+"postgresql.conf", "a+") do |fd|
fd.puts <<-EOT
ssl = on
ssl_ca_file = 'ruby-pg-ca-cert'
ssl_cert_file = 'ruby-pg-server-cert'
ssl_key_file = 'ruby-pg-server-key'
EOT
end
trace "Generate certificates"
generate_ssl_certs(@test_pgdata.to_s)
end
trace "Starting postgres"
unix_socket = ['-o', "-k #{TEST_DIRECTORY.to_s.dump}"] unless RUBY_PLATFORM=~/mingw|mswin/i
log_and_run @logfile, 'pg_ctl', '-w', *unix_socket,
@ -270,6 +287,132 @@ module PG::TestingHelpers
log_and_run @logfile, 'pg_ctl', '-D', @test_pgdata.to_s, 'stop'
end
class CertGenerator
attr_reader :output_dir
def initialize(output_dir='.')
@output_dir = output_dir
@serial = Time.now.to_i
end
def next_serial
@serial += 1
end
def create_ca_cert(name, ca_key, x509_name, valid_years: 10)
ca_key = OpenSSL::PKey::RSA.new File.read "#{ca_key}" unless ca_key.kind_of?(OpenSSL::PKey::RSA)
ca_name = OpenSSL::X509::Name.parse x509_name
ca_cert = OpenSSL::X509::Certificate.new
ca_cert.serial = next_serial
ca_cert.version = 2
ca_cert.not_before = Time.now
ca_cert.not_after = Time.now + valid_years*365*24*60*60
ca_cert.public_key = ca_key.public_key
ca_cert.subject = ca_name
ca_cert.issuer = ca_name
extension_factory = OpenSSL::X509::ExtensionFactory.new
extension_factory.subject_certificate = ca_cert
extension_factory.issuer_certificate = ca_cert
ca_cert.add_extension extension_factory.create_extension('subjectKeyIdentifier', 'hash')
ca_cert.add_extension extension_factory.create_extension('basicConstraints', 'CA:TRUE', true)
ca_cert.add_extension extension_factory.create_extension('keyUsage', 'cRLSign,keyCertSign', true)
ca_cert.sign ca_key, OpenSSL::Digest::SHA256.new
File.open "#{output_dir}/#{name}", 'w' do |io|
io.puts ca_cert.to_text
io.write ca_cert.to_pem
end
ca_cert
end
def create_key(name, rsa_size: 2048)
ca_key = OpenSSL::PKey::RSA.new rsa_size
#cipher = OpenSSL::Cipher.new 'AES-128-CBC'
File.open "#{output_dir}/#{name}", 'w', 0600 do |io|
io.puts ca_key.to_text
io.write ca_key.export # (cipher)
end
ca_key
end
def create_signing_request(name, x509_name, key)
key = OpenSSL::PKey::RSA.new File.read "#{key}" unless key.kind_of?(OpenSSL::PKey::RSA)
csr = OpenSSL::X509::Request.new
csr.version = 0
csr.subject = OpenSSL::X509::Name.parse x509_name
csr.public_key = key.public_key
csr.sign key, OpenSSL::Digest::SHA256.new
File.open "#{output_dir}/#{name}", 'w' do |io|
io.puts csr.to_text
io.write csr.to_pem
end
csr
end
def create_cert_from_csr(name, csr, ca_cert, ca_key, valid_years: 10, dns_names: nil)
ca_key = OpenSSL::PKey::RSA.new File.read "#{ca_key}" unless ca_key.kind_of?(OpenSSL::PKey::RSA)
ca_cert = OpenSSL::X509::Certificate.new File.read "#{ca_cert}" unless ca_cert.kind_of?(OpenSSL::X509::Certificate)
csr = OpenSSL::X509::Request.new File.read "#{csr}" unless csr.kind_of?(OpenSSL::X509::Request)
raise 'CSR can not be verified' unless csr.verify csr.public_key
csr_cert = OpenSSL::X509::Certificate.new
csr_cert.serial = next_serial
csr_cert.version = 2
csr_cert.not_before = Time.now
csr_cert.not_after = Time.now + valid_years*365*24*60*60
csr_cert.subject = csr.subject
csr_cert.public_key = csr.public_key
csr_cert.issuer = ca_cert.subject
extension_factory = OpenSSL::X509::ExtensionFactory.new
extension_factory.subject_certificate = csr_cert
extension_factory.issuer_certificate = ca_cert
csr_cert.add_extension extension_factory.create_extension('basicConstraints', 'CA:FALSE')
csr_cert.add_extension extension_factory.create_extension('keyUsage', 'keyEncipherment,dataEncipherment,digitalSignature')
csr_cert.add_extension extension_factory.create_extension('subjectKeyIdentifier', 'hash')
if dns_names
san = dns_names.map{|n| "DNS:#{n}" }.join(",")
csr_cert.add_extension extension_factory.create_extension('subjectAltName', san)
end
csr_cert.sign ca_key, OpenSSL::Digest::SHA256.new
open "#{output_dir}/#{name}", 'w' do |io|
io.puts csr_cert.to_text
io.write csr_cert.to_pem
end
csr_cert
end
end
def generate_ssl_certs(output_dir)
gen = CertGenerator.new(output_dir)
trace "create ca-key"
ca_key = gen.create_key('ruby-pg-ca-key')
ca_cert = gen.create_ca_cert('ruby-pg-ca-cert', ca_key, '/CN=ruby-pg root key')
trace "create server cert"
key = gen.create_key('ruby-pg-server-key')
csr = gen.create_signing_request('ruby-pg-server-csr', '/CN=localhost', key)
gen.create_cert_from_csr('ruby-pg-server-cert', csr, ca_cert, ca_key, dns_names: %w[localhost] )
trace "create client cert"
key = gen.create_key('ruby-pg-client-key')
csr = gen.create_signing_request('ruby-pg-client-csr', '/CN=ruby-pg client', key)
gen.create_cert_from_csr('ruby-pg-client-cert', csr, ca_cert, ca_key)
end
def check_for_lingering_connections( conn )
conn.exec( "SELECT * FROM pg_stat_activity" ) do |res|

View File

@ -1229,7 +1229,7 @@ EOT
describe "connection information related to SSL" do
it "can retrieve connection's ssl state", :postgresql_95 do
expect( @conn.ssl_in_use? ).to be false
expect( @conn.ssl_in_use? ).to be true
end
it "can retrieve connection's ssl attribute_names", :postgresql_95 do
@ -1238,6 +1238,8 @@ EOT
it "can retrieve a single ssl connection attribute", :postgresql_95 do
expect( @conn.ssl_attribute('dbname') ).to eq( nil )
expect( @conn.ssl_attribute('protocol') ).to match( /^TLSv/ )
expect( @conn.ssl_attribute('key_bits') ).to match( /^\d+$/ )
end
it "can retrieve all connection's ssl attributes", :postgresql_95 do

View File

@ -11,7 +11,7 @@ context "with a Fiber scheduler", :scheduler do
# Run examples with gated scheduler
sched = Helpers::TcpGateScheduler.new(external_host: 'localhost', external_port: ENV['PGPORT'].to_i, debug: ENV['PG_DEBUG']=='1')
Fiber.set_scheduler(sched)
@conninfo_gate = @conninfo.gsub(/(^| )port=\d+/, " port=#{sched.internal_port}")
@conninfo_gate = @conninfo.gsub(/(^| )port=\d+/, " port=#{sched.internal_port} sslmode=disable")
# Run examples with default scheduler
#Fiber.set_scheduler(Helpers::Scheduler.new)