diff --git a/db/migrations/20180108_create_certificates_table.rb b/db/migrations/20180108_create_certificates_table.rb new file mode 100644 index 000000000..5e50bde0f --- /dev/null +++ b/db/migrations/20180108_create_certificates_table.rb @@ -0,0 +1,14 @@ +require_relative 'migration_helper' + +Sequel.migration do + change do + create_table(:certificates, charset: 'utf8') do + primary_key :id + String :uuid, null: false, unique: true, unique_constraint_name: 'uq_certificate_uuid' + String :description, null: true + String :content, null: false, type: PactBroker::MigrationHelper.large_text_type + DateTime :created_at, null: false + DateTime :updated_at, null: false + end + end +end diff --git a/db/pact_broker_database.sqlite3 b/db/pact_broker_database.sqlite3 index 00d460c37..94e9da9fb 100644 Binary files a/db/pact_broker_database.sqlite3 and b/db/pact_broker_database.sqlite3 differ diff --git a/lib/pact_broker/certificates/certificate.rb b/lib/pact_broker/certificates/certificate.rb new file mode 100644 index 000000000..a24531e80 --- /dev/null +++ b/lib/pact_broker/certificates/certificate.rb @@ -0,0 +1,8 @@ +module PactBroker + module Certificates + class Certificate < Sequel::Model + end + + Certificate.plugin :timestamps, update_on_create: true + end +end diff --git a/lib/pact_broker/certificates/service.rb b/lib/pact_broker/certificates/service.rb new file mode 100644 index 000000000..8400c88df --- /dev/null +++ b/lib/pact_broker/certificates/service.rb @@ -0,0 +1,40 @@ +require 'pact_broker/certificates/certificate' +require 'pact_broker/logging' +require 'openssl' + +module PactBroker + module Certificates + module Service + + extend self + extend PactBroker::Logging + + def cert_store + cert_store = OpenSSL::X509::Store.new + cert_store.set_default_paths + find_all_certificates.each do | certificate | + begin + cert_store.add_cert(certificate) + rescue StandardError => e + log_error e, "Error adding certificate object #{certificate.to_s} to store" + end + end + cert_store + end + + def find_all_certificates + Certificate.collect do | certificate | + cert_arr = certificate.content.split(/(-----END [^\-]+-----)/).each_slice(2).map(&:join) + cert_arr.collect do |c| + begin + OpenSSL::X509::Certificate.new(c) + rescue StandardError => e + log_error e, "Error creating certificate object from certificate #{certificate.uuid} '#{certificate.description}'" + nil + end + end + end.flatten.compact + end + end + end +end diff --git a/lib/pact_broker/domain/webhook_request.rb b/lib/pact_broker/domain/webhook_request.rb index f683b3669..a1f088e06 100644 --- a/lib/pact_broker/domain/webhook_request.rb +++ b/lib/pact_broker/domain/webhook_request.rb @@ -5,6 +5,7 @@ require 'net/http' require 'pact_broker/webhooks/redact_logs' require 'pact_broker/api/pact_broker_urls' +require 'pact_broker/services' module PactBroker @@ -23,6 +24,7 @@ class WebhookRequest include PactBroker::Logging include PactBroker::Messages + include PactBroker::Services attr_accessor :method, :url, :headers, :body, :username, :password, :uuid @@ -103,7 +105,7 @@ def build_request uri, pact, execution_logger def do_request uri, req logger.info "Making webhook #{uuid} request #{to_s}" Net::HTTP.start(uri.hostname, uri.port, - :use_ssl => uri.scheme == 'https') do |http| + :use_ssl => uri.scheme == 'https', cert_store: cert_store) do |http| http.request req end end @@ -153,6 +155,10 @@ def gsub_url pact, url escaped_pact_url = CGI::escape(pact_url) url.gsub('${pactbroker.pactUrl}', escaped_pact_url) end + + def cert_store + certificate_service.cert_store + end end end end diff --git a/lib/pact_broker/services.rb b/lib/pact_broker/services.rb index 3a079d7bd..3731fae35 100644 --- a/lib/pact_broker/services.rb +++ b/lib/pact_broker/services.rb @@ -56,5 +56,10 @@ def matrix_service require 'pact_broker/matrix/service' Matrix::Service end + + def certificate_service + require 'pact_broker/certificates/service' + Certificates::Service + end end end diff --git a/spec/fixtures/certificate-invalid.pem b/spec/fixtures/certificate-invalid.pem new file mode 100644 index 000000000..cf6384809 --- /dev/null +++ b/spec/fixtures/certificate-invalid.pem @@ -0,0 +1,29 @@ +-----BEGIN CERTIFICATE----- +foo +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEXDCCA0SgAwIBAgIEOGO5ZjANBgkqhkiG9w0BAQUFADCBtDEUMBIGA1UEChML +RW50cnVzdC5uZXQxQDA+BgNVBAsUN3d3dy5lbnRydXN0Lm5ldC9DUFNfMjA0OCBp +bmNvcnAuIGJ5IHJlZi4gKGxpbWl0cyBsaWFiLikxJTAjBgNVBAsTHChjKSAxOTk5 +IEVudHJ1c3QubmV0IExpbWl0ZWQxMzAxBgNVBAMTKkVudHJ1c3QubmV0IENlcnRp +ZmljYXRpb24gQXV0aG9yaXR5ICgyMDQ4KTAeFw05OTEyMjQxNzUwNTFaFw0xOTEy +MjQxODIwNTFaMIG0MRQwEgYDVQQKEwtFbnRydXN0Lm5ldDFAMD4GA1UECxQ3d3d3 +LmVudHJ1c3QubmV0L0NQU18yMDQ4IGluY29ycC4gYnkgcmVmLiAobGltaXRzIGxp +YWIuKTElMCMGA1UECxMcKGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDEzMDEG +A1UEAxMqRW50cnVzdC5uZXQgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgKDIwNDgp +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArU1LqRKGsuqjIAcVFmQq +K0vRvwtKTY7tgHalZ7d4QMBzQshowNtTK91euHaYNZOLGp18EzoOH1u3Hs/lJBQe +sYGpjX24zGtLA/ECDNyrpUAkAH90lKGdCCmziAv1h3edVc3kw37XamSrhRSGlVuX +MlBvPci6Zgzj/L24ScF2iUkZ/cCovYmjZy/Gn7xxGWC4LeksyZB2ZnuU4q941mVT +XTzWnLLPKQP5L6RQstRIzgUyVYr9smRMDuSYB3Xbf9+5CFVghTAp+XtIpGmG4zU/ +HoZdenoVve8AjhUiVBcAkCaTvA5JaJG/+EfTnZVCwQ5N328mz8MYIWJmQ3DW1cAH +4QIDAQABo3QwcjARBglghkgBhvhCAQEEBAMCAAcwHwYDVR0jBBgwFoAUVeSB0RGA +vtiJuQijMfmhJAkWuXAwHQYDVR0OBBYEFFXkgdERgL7YibkIozH5oSQJFrlwMB0G +CSqGSIb2fQdBAAQQMA4bCFY1LjA6NC4wAwIEkDANBgkqhkiG9w0BAQUFAAOCAQEA +WUesIYSKF8mciVMeuoCFGsY8Tj6xnLZ8xpJdGGQC49MGCBFhfGPjK50xA3B20qMo +oPS7mmNz7W3lKtvtFKkrxjYR0CvrB4ul2p5cGZ1WEvVUKcgF7bISKo30Axv/55IQ +h7A6tcOdBTcSo8f0FbnVpDkWm1M6I5HxqIKiaohowXkCIryqptau37AUX7iH0N18 +f3v/rxzP5tsHrV7bhZ3QKw0z2wTR5klAEyt2+z7pnIkPFc4YsIV4IU9rTw76NmfN +B/L/CNDi3tm/Kq+4h4YhPATKt5Rof8886ZjXOP/swNlQ8C5LWK5Gb9Auw2DaclVy +vUxFnmG6v4SBkgPR0ml8xQ== +-----END CERTIFICATE----- \ No newline at end of file diff --git a/spec/fixtures/certificate.pem b/spec/fixtures/certificate.pem new file mode 100644 index 000000000..1ff74cced --- /dev/null +++ b/spec/fixtures/certificate.pem @@ -0,0 +1,53 @@ +-----BEGIN CERTIFICATE----- +MIIEhjCCA26gAwIBAgIJAOoR4cViLrYBMA0GCSqGSIb3DQEBBQUAMIGIMQswCQYD +VQQGEwJBVTERMA8GA1UECBMIVmljdG9yaWExEjAQBgNVBAcTCU1lbGJvdXJuZTEY +MBYGA1UEChMPUGFjdCBGb3VuZGF0aW9uMRUwEwYDVQQDEwxCZXRoIFNrdXJyaWUx +ITAfBgkqhkiG9w0BCQEWEmJldGhAYmV0aGVzcXVlLmNvbTAeFw0xNzEyMDcwMDI1 +MjZaFw00NTA0MjQwMDI1MjZaMIGIMQswCQYDVQQGEwJBVTERMA8GA1UECBMIVmlj +dG9yaWExEjAQBgNVBAcTCU1lbGJvdXJuZTEYMBYGA1UEChMPUGFjdCBGb3VuZGF0 +aW9uMRUwEwYDVQQDEwxCZXRoIFNrdXJyaWUxITAfBgkqhkiG9w0BCQEWEmJldGhA +YmV0aGVzcXVlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKli +EwzIbbZdoDbuJPkMsdGihSYK6KQrLCsvFmNLEgDBKds387E5mQoOjDvlDHLkY+uq +Lm10bI2MSnQAj9B+jqf48FNaoHq2A30iohmK2hPKsrIMi3eaWrXYm9+ZxnNUO/j6 +/WKCsX/InUsZDd0J6F6HO9RZ+/AmfBC6fTuxKFgGENwsZSPIpi2JwBeEV+YCthPf +yOBzgNyljd+BqvZCJK3+r074131TC4AEzWZsV33ipOTpcY3y8XAOa79npWUfnTOX +E3NW1Jqrd9Ozky9+HldGGr0VhRkAUneyhhT0HZsSoYcI6QRaYbA1+GkHNIWtPhhk +coSjhKBXJ2RduJi7eisCAwEAAaOB8DCB7TAdBgNVHQ4EFgQUb7yD7klC/K/sJCoK +SJjhWRvygsYwgb0GA1UdIwSBtTCBsoAUb7yD7klC/K/sJCoKSJjhWRvygsahgY6k +gYswgYgxCzAJBgNVBAYTAkFVMREwDwYDVQQIEwhWaWN0b3JpYTESMBAGA1UEBxMJ +TWVsYm91cm5lMRgwFgYDVQQKEw9QYWN0IEZvdW5kYXRpb24xFTATBgNVBAMTDEJl +dGggU2t1cnJpZTEhMB8GCSqGSIb3DQEJARYSYmV0aEBiZXRoZXNxdWUuY29tggkA +6hHhxWIutgEwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAAOCAQEARThJZnP0 +21PjLxb0/RZ/W/Rl7qqp5jh6sxPTfdztDpfJHcQztmmXQhZaKmcPtqSp7zFKZKIC +D5b0n1lSKqf1bwxwcGQfBu1EAZMYBSNNRJsdMqqnaj76RFysOye4KXXMrCNJ0Md6 +MEObX87XBWiKmy5ZbtfoqiWcHrsw9Bnl+9ZPwIaxndtlaaJbnAyKTP28Z8rltiva +kUxcmh37DpAjEBPMWgiD+pfVWhz54rNN7IqndIJhAE8Zphvq2RguMo6CgMbmwlcv +gf9w/hcz5FES3GA01bDmp9CVdGxWN+njcstuOOrVuFgdFR8z3WokPI4YsACXxIZc +5R2NaXTfO9Mw2w== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEXDCCA0SgAwIBAgIEOGO5ZjANBgkqhkiG9w0BAQUFADCBtDEUMBIGA1UEChML +RW50cnVzdC5uZXQxQDA+BgNVBAsUN3d3dy5lbnRydXN0Lm5ldC9DUFNfMjA0OCBp +bmNvcnAuIGJ5IHJlZi4gKGxpbWl0cyBsaWFiLikxJTAjBgNVBAsTHChjKSAxOTk5 +IEVudHJ1c3QubmV0IExpbWl0ZWQxMzAxBgNVBAMTKkVudHJ1c3QubmV0IENlcnRp +ZmljYXRpb24gQXV0aG9yaXR5ICgyMDQ4KTAeFw05OTEyMjQxNzUwNTFaFw0xOTEy +MjQxODIwNTFaMIG0MRQwEgYDVQQKEwtFbnRydXN0Lm5ldDFAMD4GA1UECxQ3d3d3 +LmVudHJ1c3QubmV0L0NQU18yMDQ4IGluY29ycC4gYnkgcmVmLiAobGltaXRzIGxp +YWIuKTElMCMGA1UECxMcKGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDEzMDEG +A1UEAxMqRW50cnVzdC5uZXQgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgKDIwNDgp +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArU1LqRKGsuqjIAcVFmQq +K0vRvwtKTY7tgHalZ7d4QMBzQshowNtTK91euHaYNZOLGp18EzoOH1u3Hs/lJBQe +sYGpjX24zGtLA/ECDNyrpUAkAH90lKGdCCmziAv1h3edVc3kw37XamSrhRSGlVuX +MlBvPci6Zgzj/L24ScF2iUkZ/cCovYmjZy/Gn7xxGWC4LeksyZB2ZnuU4q941mVT +XTzWnLLPKQP5L6RQstRIzgUyVYr9smRMDuSYB3Xbf9+5CFVghTAp+XtIpGmG4zU/ +HoZdenoVve8AjhUiVBcAkCaTvA5JaJG/+EfTnZVCwQ5N328mz8MYIWJmQ3DW1cAH +4QIDAQABo3QwcjARBglghkgBhvhCAQEEBAMCAAcwHwYDVR0jBBgwFoAUVeSB0RGA +vtiJuQijMfmhJAkWuXAwHQYDVR0OBBYEFFXkgdERgL7YibkIozH5oSQJFrlwMB0G +CSqGSIb2fQdBAAQQMA4bCFY1LjA6NC4wAwIEkDANBgkqhkiG9w0BAQUFAAOCAQEA +WUesIYSKF8mciVMeuoCFGsY8Tj6xnLZ8xpJdGGQC49MGCBFhfGPjK50xA3B20qMo +oPS7mmNz7W3lKtvtFKkrxjYR0CvrB4ul2p5cGZ1WEvVUKcgF7bISKo30Axv/55IQ +h7A6tcOdBTcSo8f0FbnVpDkWm1M6I5HxqIKiaohowXkCIryqptau37AUX7iH0N18 +f3v/rxzP5tsHrV7bhZ3QKw0z2wTR5klAEyt2+z7pnIkPFc4YsIV4IU9rTw76NmfN +B/L/CNDi3tm/Kq+4h4YhPATKt5Rof8886ZjXOP/swNlQ8C5LWK5Gb9Auw2DaclVy +vUxFnmG6v4SBkgPR0ml8xQ== +-----END CERTIFICATE----- \ No newline at end of file diff --git a/spec/lib/pact_broker/certificates/service_spec.rb b/spec/lib/pact_broker/certificates/service_spec.rb new file mode 100644 index 000000000..542224038 --- /dev/null +++ b/spec/lib/pact_broker/certificates/service_spec.rb @@ -0,0 +1,60 @@ +require 'pact_broker/certificates/service' + +module PactBroker + module Certificates + describe Service do + let(:certificate_content) { File.read('spec/fixtures/certificate.pem') } + + describe "#cert_store" do + subject { Service.cert_store } + + it "returns an OpenSSL::X509::Store" do + expect(subject).to be_instance_of(OpenSSL::X509::Store) + end + + context "when there is a duplicate certificate" do + before do + Certificate.create(uuid: '1234', content: certificate_content) + Certificate.create(uuid: '5678', content: certificate_content) + end + + it "logs the error" do + expect(PactBroker.logger).to receive(:error).with(/Error adding certificate/).at_least(1).times + subject + end + + it "returns an OpenSSL::X509::Store" do + expect(subject).to be_instance_of(OpenSSL::X509::Store) + end + end + end + + describe "#find_all_certificates" do + let!(:certificate) do + Certificate.create(uuid: '1234', content: certificate_content) + end + + subject { Service.find_all_certificates } + + context "with a valid certificate file" do + it "returns all the X509 Certificate objects" do + expect(subject.size).to eq 2 + end + end + + context "with an invalid certificate file" do + let(:certificate_content) { File.read('spec/fixtures/certificate-invalid.pem') } + + it "logs an error" do + expect(PactBroker.logger).to receive(:error).with(/Error.*1234/) + subject + end + + it "returns all the valid X509 Certificate objects" do + expect(subject.size).to eq 1 + end + end + end + end + end +end diff --git a/spec/lib/pact_broker/domain/webhook_request_spec.rb b/spec/lib/pact_broker/domain/webhook_request_spec.rb index 7b1bb227a..ed3a4613a 100644 --- a/spec/lib/pact_broker/domain/webhook_request_spec.rb +++ b/spec/lib/pact_broker/domain/webhook_request_spec.rb @@ -3,9 +3,7 @@ require 'webmock/rspec' module PactBroker - module Domain - describe WebhookRequest do before do allow(PactBroker::Api::PactBrokerUrls).to receive(:pact_url).and_return('http://example.org/pact-url') @@ -33,7 +31,6 @@ module Domain let(:logs) { subject.execute(pact, options).logs } - describe "description" do it "returns a brief description of the HTTP request" do expect(subject.description).to eq 'POST example.org' @@ -55,7 +52,6 @@ module Domain end describe "execute" do - let!(:http_request) do stub_request(:post, "http://example.org/hook"). with(:headers => {'Content-Type'=>'text/plain'}, :body => 'body').