Skip to content

Commit

Permalink
feat: expose webhook whitelist configuration as environment variables
Browse files Browse the repository at this point in the history
  • Loading branch information
bethesque committed Jun 11, 2018
1 parent 3485446 commit 219cda4
Show file tree
Hide file tree
Showing 13 changed files with 252 additions and 9 deletions.
1 change: 1 addition & 0 deletions .gitignore
@@ -1,2 +1,3 @@
pact_broker/pact_broker.sqlite
pact_broker.sqlite
pact_broker/log
1 change: 1 addition & 0 deletions .rspec
@@ -0,0 +1 @@
--format documentation
2 changes: 2 additions & 0 deletions Gemfile
Expand Up @@ -2,3 +2,5 @@ source 'https://rubygems.org'

gem 'rake', '~> 12.0'
gem 'conventional-changelog', '~>1.3'
gem 'rspec', '~> 3.7'
gem 'rspec-its', '~> 1.2'
21 changes: 20 additions & 1 deletion Gemfile.lock
Expand Up @@ -2,14 +2,33 @@ GEM
remote: https://rubygems.org/
specs:
conventional-changelog (1.3.0)
diff-lcs (1.3)
rake (12.3.0)
rspec (3.7.0)
rspec-core (~> 3.7.0)
rspec-expectations (~> 3.7.0)
rspec-mocks (~> 3.7.0)
rspec-core (3.7.1)
rspec-support (~> 3.7.0)
rspec-expectations (3.7.0)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.7.0)
rspec-its (1.2.0)
rspec-core (>= 3.0.0)
rspec-expectations (>= 3.0.0)
rspec-mocks (3.7.0)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.7.0)
rspec-support (3.7.1)

PLATFORMS
ruby

DEPENDENCIES
conventional-changelog (~> 1.3)
rake (~> 12.0)
rspec (~> 3.7)
rspec-its (~> 1.2)

BUNDLED WITH
1.16.0
1.16.2
9 changes: 9 additions & 0 deletions README.md
Expand Up @@ -49,6 +49,15 @@ See the [Pact Broker configuration documentation][reverse-proxy].

Set the environment variable `PACT_BROKER_LOG_LEVEL` to one of `DEBUG`, `INFO`, `WARN`, `ERROR`, or `FATAL`.

## Webhook whitelists

* PACT_BROKER_WEBHOOK_HOST_WHITELIST - a space delimited list of hosts (eg. `github.com`), network ranges (eg. `10.2.3.41/24`, or regular expressions (eg. `/.*\\.foo\\.com$/`). Regular expressions should start and end with a `/` to differentiate them from Strings. Note that backslashes need to be escaped with a second backslash. Please read the [Webhook whitelists](https://github.com/pact-foundation/pact_broker/wiki/Configuration#webhook-whitelists) section of the Pact Broker configuration documentation to understand how the whitelist is used. Remember to use quotes around this value as it may have spaces in it.
* PACT_BROKER_WEBHOOK_SCHEME_WHITELIST - a space delimited list (eg. `http https`). Defaults to `https`.

## Other environment variables

* PACT_BROKER_BASE_EQUALITY_ONLY_ON_CONTENT_THAT_AFFECTS_VERIFICATION_RESULTS - `true` by default, may be set to `false`.

## General Pact Broker configuration and usage

Documentation for the Pact Broker application itself can be found in the Pact Broker [wiki][pact-broker-wiki].
Expand Down
6 changes: 6 additions & 0 deletions Rakefile
@@ -1,5 +1,11 @@
require 'conventional_changelog'
require 'rspec/core'
require 'rspec/core/rake_task'

task :generate_changelog do
ConventionalChangelog::Generator.new.generate! version: ENV.fetch('TAG')
end

RSpec::Core::RakeTask.new(:spec)

task :default => :spec
4 changes: 4 additions & 0 deletions container/etc/nginx/main.d/pactbroker-env.conf
Expand Up @@ -8,6 +8,10 @@ env PACT_BROKER_BASIC_AUTH_USERNAME;
env PACT_BROKER_BASIC_AUTH_PASSWORD;
env PACT_BROKER_PUBLIC_HEARTBEAT;
env PACT_BROKER_LOG_LEVEL;
env PACT_BROKER_WEBHOOK_HTTP_METHOD_WHITELIST;
env PACT_BROKER_WEBHOOK_SCHEME_WHITELIST;
env PACT_BROKER_WEBHOOK_HOST_WHITELIST;
env PACT_BROKER_BASE_EQUALITY_ONLY_ON_CONTENT_THAT_AFFECTS_VERIFICATION_RESULTS;
env http_proxy;
env https_proxy;
env no_proxy;
Expand Down
8 changes: 4 additions & 4 deletions pact_broker/Gemfile.lock
Expand Up @@ -64,7 +64,7 @@ GEM
rspec (>= 2.14)
term-ansicolor (~> 1.0)
thor
pact_broker (2.20.0)
pact_broker (2.22.0)
dry-types (~> 0.10.3)
dry-validation (~> 0.10.5)
haml (~> 5.0)
Expand Down Expand Up @@ -93,7 +93,7 @@ GEM
rake (>= 0.8.1)
pg (1.0.0)
rack (2.0.5)
rack-protection (2.0.2)
rack-protection (2.0.3)
rack
rake (12.3.1)
randexp (0.1.7)
Expand Down Expand Up @@ -122,10 +122,10 @@ GEM
rspec-support (3.7.1)
semver2 (3.4.2)
sequel (5.9.0)
sinatra (2.0.2)
sinatra (2.0.3)
mustermann (~> 1.0)
rack (~> 2.0)
rack-protection (= 2.0.2)
rack-protection (= 2.0.3)
tilt (~> 2.0)
sqlite3 (1.3.13)
sucker_punch (2.0.4)
Expand Down
12 changes: 12 additions & 0 deletions pact_broker/config.ru
Expand Up @@ -4,15 +4,27 @@ require_relative 'logger'
require_relative 'basic_auth'
require_relative 'database_connection'
require_relative 'passenger_config'
require_relative 'docker_configuration'

dc = PactBroker::DockerConfiguration.new(ENV, PactBroker::Configuration.default_configuration)
dc.pact_broker_environment_variables.each{ |key, value| $logger.info "#{key}=#{value}"}

app = PactBroker::App.new do | config |
config.logger = $logger
config.database_connection = create_database_connection(config.logger)
config.database_connection.timezone = :utc
config.webhook_host_whitelist = dc.webhook_host_whitelist
config.webhook_http_method_whitelist = dc.webhook_http_method_whitelist
config.webhook_scheme_whitelist = dc.webhook_scheme_whitelist
config.base_equality_only_on_content_that_affects_verification_results = dc.base_equality_only_on_content_that_affects_verification_results
end

PactBroker.configuration.load_from_database!

PactBroker::Configuration::SAVABLE_SETTING_NAMES.each do | setting |
$logger.info "PactBroker.configuration.#{setting}=#{PactBroker.configuration.send(setting).inspect}"
end

basic_auth_username = ENV.fetch('PACT_BROKER_BASIC_AUTH_USERNAME','')
basic_auth_password = ENV.fetch('PACT_BROKER_BASIC_AUTH_PASSWORD', '')
use_basic_auth = basic_auth_username != '' && basic_auth_password != ''
Expand Down
11 changes: 7 additions & 4 deletions pact_broker/database_connection.rb
Expand Up @@ -4,16 +4,17 @@
def create_database_connection(logger)
database_adapter = ENV.fetch('PACT_BROKER_DATABASE_ADAPTER','') != '' ? ENV['PACT_BROKER_DATABASE_ADAPTER'] : 'postgres'

credentials = {
config = {
adapter: database_adapter,
user: ENV['PACT_BROKER_DATABASE_USERNAME'],
password: ENV['PACT_BROKER_DATABASE_PASSWORD'],
host: ENV['PACT_BROKER_DATABASE_HOST'],
database: ENV['PACT_BROKER_DATABASE_NAME']
database: ENV['PACT_BROKER_DATABASE_NAME'],
encoding: 'utf8'
}

if ENV['PACT_BROKER_DATABASE_PORT'] =~ /^\d+$/
credentials[:port] = ENV['PACT_BROKER_DATABASE_PORT'].to_i
config[:port] = ENV['PACT_BROKER_DATABASE_PORT'].to_i
end

##
Expand All @@ -33,7 +34,9 @@ def create_database_connection(logger)
# -1 means that connections will be validated every time, which avoids errors
# when databases are restarted and connections are killed. This has a performance
# penalty, so consider increasing this timeout if building a frequently accessed service.
connection = Sequel.connect(credentials.merge(logger: DatabaseLogger.new(logger), encoding: 'utf8'))
logger.info "Connecting to database with config: #{config.merge(password: "*****")}"
config[:logger] = DatabaseLogger.new(logger)
connection = Sequel.connect(config)
connection.extension(:connection_validator)
connection.pool.connection_validation_timeout = -1
connection
Expand Down
86 changes: 86 additions & 0 deletions pact_broker/docker_configuration.rb
@@ -0,0 +1,86 @@
# @private - do not rely on these classes as a public interface

module PactBroker
class DockerConfiguration
def initialize env, default_configuration
@env = env
@default_configuration = default_configuration
end

def pact_broker_environment_variables
@env.each_with_object({}) do | (key, value), hash |
if key.start_with?("PACT_BROKER_")
hash[key] = key =~ /password/i ? "*****" : value
end
end
end

def webhook_host_whitelist
space_delimited_string_list_or_default(:webhook_host_whitelist)
end

def webhook_scheme_whitelist
space_delimited_string_list_or_default(:webhook_scheme_whitelist)
end

def webhook_http_method_whitelist
space_delimited_string_list_or_default(:webhook_http_method_whitelist)
end

def base_equality_only_on_content_that_affects_verification_results
if env_populated?(:base_equality_only_on_content_that_affects_verification_results)
env(:base_equality_only_on_content_that_affects_verification_results) == 'true'
else
default(:base_equality_only_on_content_that_affects_verification_results)
end
end

def env name
@env["PACT_BROKER_#{name.to_s.upcase}"]
end

def env_populated? name
(env(name) || "").size > 0
end

def default property_name
@default_configuration.send(property_name)
end

def space_delimited_string_list_or_default property_name
if env_populated?(property_name)
SpaceDelimitedStringList.parse(env(property_name))
else
default(property_name)
end
end

class SpaceDelimitedStringList < Array

def initialize list
super(list)
end

def self.parse(string)
array = (string || '').split(' ').collect do | word |
if word[0] == '/' and word[-1] == '/'
Regexp.new(word[1..-2])
else
word
end
end
SpaceDelimitedStringList.new(array)
end

def to_s
collect do | word |
if word.is_a?(Regexp)
"/#{word.source}/"
else
word
end
end.join(' ')
end
end
end
end
7 changes: 7 additions & 0 deletions script/test.sh
Expand Up @@ -60,6 +60,10 @@ fi
[ -z "${PSQL_CONT_NAME}" ] && PSQL_CONT_NAME="postgres"
[ -z "${PACT_BROKER_DATABASE_ADAPTER}" ] && PACT_BROKER_DATABASE_ADAPTER="postgres"
[ -z "${PACT_BROKER_PUBLIC_HEARTBEAT}" ] && PACT_BROKER_PUBLIC_HEARTBEAT="true"
[ -z "${PACT_BROKER_PUBLIC_HEARTBEAT}" ] && PACT_BROKER_PUBLIC_HEARTBEAT="true"
[ -z "${PACT_BROKER_WEBHOOK_HTTP_METHOD_WHITELIST}" ] && PACT_BROKER_WEBHOOK_HTTP_METHOD_WHITELIST="GET POST"
[ -z "${PACT_BROKER_WEBHOOK_SCHEME_WHITELIST}" ] && PACT_BROKER_WEBHOOK_SCHEME_WHITELIST="http https"
[ -z "${PACT_BROKER_WEBHOOK_HOST_WHITELIST}" ] && PACT_BROKER_WEBHOOK_HOST_WHITELIST="/.*\\.foo\\.com$/ bar.com 10.2.3.41/24"

echo "Will build the pact broker"
docker build -t=dius/pact_broker .
Expand Down Expand Up @@ -161,6 +165,9 @@ docker run --privileged --name=${PACT_CONT_NAME} -d -p ${PORT_BIND} \
-e PACT_BROKER_BASIC_AUTH_USERNAME=${PACT_BROKER_BASIC_AUTH_USERNAME} \
-e PACT_BROKER_BASIC_AUTH_PASSWORD=${PACT_BROKER_BASIC_AUTH_PASSWORD} \
-e PACT_BROKER_PUBLIC_HEARTBEAT=${PACT_BROKER_PUBLIC_HEARTBEAT} \
-e PACT_BROKER_WEBHOOK_HTTP_METHOD_WHITELIST="${PACT_BROKER_WEBHOOK_HTTP_METHOD_WHITELIST}" \
-e PACT_BROKER_WEBHOOK_SCHEME_WHITELIST="${PACT_BROKER_WEBHOOK_SCHEME_WHITELIST}" \
-e PACT_BROKER_WEBHOOK_HOST_WHITELIST="${PACT_BROKER_WEBHOOK_HOST_WHITELIST}" \
-e PACT_BROKER_LOG_LEVEL=INFO \
dius/pact_broker
sleep 1 && docker logs ${PACT_CONT_NAME}
Expand Down
93 changes: 93 additions & 0 deletions spec/docker_configuration_spec.rb
@@ -0,0 +1,93 @@
$: << "."
require "pact_broker/docker_configuration"
require 'rspec/its'

RSpec.describe PactBroker::DockerConfiguration do

subject { PactBroker::DockerConfiguration.new(env, default_configuration) }

let(:env) do
{
"PACT_BROKER_WEBHOOK_HOST_WHITELIST" => host_whitelist
}
end

let(:default_configuration) do
instance_double('default configuration',
webhook_host_whitelist: 'default'
)
end

describe "pact_broker_environment_variables" do
let(:env) do
{
"PACT_BROKER_FOO" => "foo",
"PACT_BROKER_PASSWORD" => "bar",
"SOMETHING" => "foo"
}
end

let(:expected_environment_variables) do
{
"PACT_BROKER_FOO" => "foo",
"PACT_BROKER_PASSWORD" => "*****"
}
end

its(:pact_broker_environment_variables) { is_expected.to eq expected_environment_variables }
end

describe "webhook_host_whitelist" do
context "when PACT_BROKER_WEBHOOK_HOST_WHITELIST is 'foo bar'" do
let(:host_whitelist) { "foo bar" }
its(:webhook_host_whitelist) { is_expected.to eq ["foo", "bar"] }
end

context "when PACT_BROKER_WEBHOOK_HOST_WHITELIST is ''" do
let(:host_whitelist) { "" }
its(:webhook_host_whitelist) { is_expected.to eq 'default' }
end
end
end

class PactBroker::DockerConfiguration
describe SpaceDelimitedStringList do
describe "parse" do
subject { SpaceDelimitedStringList.parse(input) }

context "when input is ''" do
let(:input) { "" }

it { is_expected.to eq [] }

its(:to_s) { is_expected.to eq input }
end

context "when input is 'foo bar'" do
let(:input) { "foo bar" }

it { is_expected.to eq ["foo", "bar"] }

it { is_expected.to be_a SpaceDelimitedStringList }

its(:to_s) { is_expected.to eq input }
end

context "when input is '/foo.*/'" do
let(:input) { "/foo.*/" }

it { is_expected.to eq [/foo.*/] }

its(:to_s) { is_expected.to eq input }
end

context "when input is '/foo\\.*/' (note double backslash)" do
let(:input) { "/foo\\.*/" }

it { is_expected.to eq [/foo\.*/] }

its(:to_s) { is_expected.to eq input }
end
end
end
end

0 comments on commit 219cda4

Please sign in to comment.