From 82c7a7e56b427cfa26aff518f24a8c23b8d2da65 Mon Sep 17 00:00:00 2001 From: Beth Skurrie Date: Mon, 28 Mar 2022 16:01:58 +1100 Subject: [PATCH] feat(webhook certificates): support setting webhook certificates using environment variables --- docs/configuration.yml | 19 +++++++++++---- .../config/runtime_configuration.rb | 23 ++++++++++++++++--- .../runtime_configuration_coercion_methods.rb | 11 +++++++++ .../runtime_configuration_logging_methods.rb | 9 ++++++-- ...time_configuration_logging_methods_spec.rb | 4 ++++ .../config/runtime_configuration_spec.rb | 12 ++++++++++ 6 files changed, 69 insertions(+), 9 deletions(-) diff --git a/docs/configuration.yml b/docs/configuration.yml index d70c8bffe..1b5c022a2 100644 --- a/docs/configuration.yml +++ b/docs/configuration.yml @@ -277,8 +277,6 @@ groups: When setting the path, the full path to the certificate file in PEM format must be specified. When using Docker, you must ensure the certificate file is [mounted into the container](https://docs.docker.com/storage/volumes/). - *NOTE: USING ENVIRONMENT VARIABLES TO SET THE WEBHOOK CERTIFICATES IS NOT SUPPORTED.* - YAML Example: ```yaml @@ -291,10 +289,23 @@ groups: jHT1Ty2CglM= -----END CERTIFICATE----- - description: "An example self signed certificate with a path" - path: /full/path/to/the/cert.pem + path: "/full/path/to/the/cert.pem" + + ``` + + Environment variable example: + ```shell + PACT_BROKER_WEBHOOK_CERTIFICATES__0__LABEL="An example self signed certificate with content" + PACT_BROKER_WEBHOOK_CERTIFICATES__0__CONTENT="-----BEGIN CERTIFICATE----- + MIIDZDCCAkygAwIBAgIBATANBgkqhkiG9w0BAQsFADBCMRMwEQYKCZImiZPyLGQB + + jHT1Ty2CglM= + -----END CERTIFICATE-----" + PACT_BROKER_WEBHOOK_CERTIFICATES__1__LABEL="An example self signed certificate with a path" + PACT_BROKER_WEBHOOK_CERTIFICATES__1__PATH="/full/path/to/the/cert.pem" ``` - supported_versions: From v2.90.0 + supported_versions: From v2.90.0 for YAML and 2.97.0 for environment variables. disable_ssl_verification: description: "If set to true, SSL verification will be disabled for the HTTP requests made by the webhooks" default_value: false diff --git a/lib/pact_broker/config/runtime_configuration.rb b/lib/pact_broker/config/runtime_configuration.rb index b88d255aa..99044f39d 100644 --- a/lib/pact_broker/config/runtime_configuration.rb +++ b/lib/pact_broker/config/runtime_configuration.rb @@ -52,9 +52,12 @@ class RuntimeConfiguration < Anyway::Config webhook_scheme_whitelist: ["https"], webhook_host_whitelist: [], disable_ssl_verification: false, - webhook_certificates: [], - user_agent: "Pact Broker v#{PactBroker::VERSION}", + user_agent: "Pact Broker v#{PactBroker::VERSION}" ) + # no default, if you set it to [] or nil, then anyway config blows up when it tries to merge in the + # numerically indexed hash from the environment variables. + attr_config :webhook_certificates + on_load :set_webhook_attribute_defaults # resource attributes attr_config( @@ -178,8 +181,14 @@ def features= features def webhook_certificates= webhook_certificates if webhook_certificates.is_a?(Array) super(webhook_certificates.collect(&:symbolize_keys)) + elsif webhook_certificates.is_a?(Hash) + if all_keys_are_number_strings?(webhook_certificates) + super(convert_hash_with_number_string_keys_to_array(webhook_certificates).collect(&:symbolize_keys)) + else + raise_validation_error("webhook_certificates must be an array, or a hash where each key is an integer in string format.") + end elsif !webhook_certificates.nil? - raise_validation_error("webhook_certificates must be an array") + raise_validation_error("webhook_certificates cannot be set using a #{webhook_certificates.class}") end end @@ -208,6 +217,14 @@ def validate_logging_attributes! def raise_validation_error(msg) raise PactBroker::ConfigurationError, msg end + + def set_webhook_attribute_defaults + # can't set a default on this, or anyway config blows up when trying to merge the + # hash from the env vars into an array/nil. + if webhook_certificates.nil? + self.webhook_certificates = [] + end + end end end end diff --git a/lib/pact_broker/config/runtime_configuration_coercion_methods.rb b/lib/pact_broker/config/runtime_configuration_coercion_methods.rb index 371f3abef..a249be6d1 100644 --- a/lib/pact_broker/config/runtime_configuration_coercion_methods.rb +++ b/lib/pact_broker/config/runtime_configuration_coercion_methods.rb @@ -4,6 +4,17 @@ module PactBroker module Config module RuntimeConfigurationCoercionMethods + + def all_keys_are_number_strings?(hash) + hash.keys.all? { | k | k.to_s.to_i.to_s == k } # is an integer as a string + end + + def convert_hash_with_number_string_keys_to_array(hash) + hash.keys.collect{ |k| [k, k.to_i]}.sort_by(&:last).collect(&:first).collect do | key | + hash[key] + end + end + def value_to_string_array value, property_name if value.is_a?(String) PactBroker::Config::SpaceDelimitedStringList.parse(value) diff --git a/lib/pact_broker/config/runtime_configuration_logging_methods.rb b/lib/pact_broker/config/runtime_configuration_logging_methods.rb index 54b786eae..321ebd396 100644 --- a/lib/pact_broker/config/runtime_configuration_logging_methods.rb +++ b/lib/pact_broker/config/runtime_configuration_logging_methods.rb @@ -24,12 +24,17 @@ def sensitive_value?(value) module InstanceMethods # base_url raises a not implemented error def log_configuration(logger) - to_source_trace.without("base_url").each_with_object({})do | (key, details), new_hash | - new_hash[key] = details.merge(value: self.send(key.to_sym)) + source_info = to_source_trace + (self.class.config_attributes - [:base_url]).collect(&:to_s).each_with_object({})do | (key, details), new_hash | + new_hash[key] = { + value: self.send(key.to_sym), + source: source_info.dig(key, :source) || {:type=>:defaults} + } end.sort_by { |key, _| key }.each { |key, value| log_config_inner(key, value, logger) } end def log_config_inner(key, value, logger) + # TODO fix the source display for webhook_certificates set by environment variables if !value.has_key? :value value.sort_by { |inner_key, _| inner_key }.each { |inner_key, inner_value| log_config_inner("#{key}.#{inner_key}", inner_value, logger) } elsif self.class.sensitive_value?(key) diff --git a/spec/lib/pact_broker/config/runtime_configuration_logging_methods_spec.rb b/spec/lib/pact_broker/config/runtime_configuration_logging_methods_spec.rb index 4ed82a129..54aafa360 100644 --- a/spec/lib/pact_broker/config/runtime_configuration_logging_methods_spec.rb +++ b/spec/lib/pact_broker/config/runtime_configuration_logging_methods_spec.rb @@ -18,6 +18,10 @@ module Config expect(subject).to include "database_url=protocol://username:*****@host/database" end + it "logs values that don't have an initial default, but get set afterward" do + expect(subject).to include "webhook_certificates=[] source={:type=>:defaults}" + end + context "with a database URL with no password" do let(:initial_values) { { database_password: "foo", database_url: "sqlite:///pact_broker.sqlite3" } } diff --git a/spec/lib/pact_broker/config/runtime_configuration_spec.rb b/spec/lib/pact_broker/config/runtime_configuration_spec.rb index aa920866d..0c9ca9304 100644 --- a/spec/lib/pact_broker/config/runtime_configuration_spec.rb +++ b/spec/lib/pact_broker/config/runtime_configuration_spec.rb @@ -66,6 +66,18 @@ module Config its(:base_urls) { is_expected.to eq %w[bar wiffle foo blah] } end + + describe "webhook_certificates" do + context "when setting using environment variables with indexes eg PACT_BROKER_WEBHOOK_CERTIFICATES__0__LABEL" do + subject do + runtime_configuration = RuntimeConfiguration.new + runtime_configuration.webhook_certificates = { "0" => { "description" => "cert1", "content" => "abc" }, "1" => { "description" => "cert1", "content" => "abc" } } + runtime_configuration + end + + its(:webhook_certificates) { is_expected.to eq [{ description: "cert1", content: "abc" }, { description: "cert1", content: "abc" }] } + end + end end end end