diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8407f6d..42f8db6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,12 +16,6 @@ jobs: ruby: ['2.7', '3.0', '3.1'] gemfile: - rails_7 - - rails_master - exclude: - - gemfile: rails_master - ruby: 2.7 - - gemfile: rails_master - ruby: 3.0 env: BUNDLE_GEMFILE: ${{ github.workspace }}/gemfiles/${{ matrix.gemfile }}.gemfile BUNDLE_PATH_RELATIVE_TO_CWD: true diff --git a/app/models/pager_tree/integrations/cloudflare/v3.rb b/app/models/pager_tree/integrations/cloudflare/v3.rb new file mode 100644 index 0000000..a08f5f3 --- /dev/null +++ b/app/models/pager_tree/integrations/cloudflare/v3.rb @@ -0,0 +1,75 @@ +module PagerTree::Integrations + class Cloudflare::V3 < Integration + OPTIONS = [ + {key: :webhook_secret, type: :string, default: nil} + ] + store_accessor :options, *OPTIONS.map { |x| x[:key] }.map(&:to_s), prefix: "option" + + after_initialize do + self.option_webhook_secret ||= nil + end + + def adapter_should_block_incoming?(request) + self.option_webhook_secret.present? && (request.headers["cf-webhook-auth"] != self.option_webhook_secret) + end + + def adapter_supports_incoming? + true + end + + def adapter_supports_outgoing? + false + end + + def adapter_incoming_can_defer? + true + end + + def adapter_thirdparty_id + @adapter_thirdparty_id ||= SecureRandom.uuid + end + + def adapter_action + :create + end + + def adapter_process_create + Alert.new( + title: _title, + description: _description, + thirdparty_id: adapter_thirdparty_id, + dedup_keys: [], + additional_data: _additional_datums + ) + end + + private + + def _title + adapter_incoming_request_params.dig("text")&.truncate(70) || "CF Event #{adapter_incoming_request_params.dig("text")&.titleize}" + end + + def _description + ActionController::Base.helpers.simple_format(adapter_incoming_request_params.dig("text")) + end + + def _additional_datums + timestamp = begin + Time.at(adapter_incoming_request_params.dig("ts")).utc.to_datetime + rescue + nil + end + + datums = [] + datums << AdditionalDatum.new(format: "text", label: "Account Name", value: adapter_incoming_request_params.dig("data", "account_name")) + datums << AdditionalDatum.new(format: "text", label: "Zone Name", value: adapter_incoming_request_params.dig("data", "zone_name")) + datums << AdditionalDatum.new(format: "link", label: "Dashboard Link", value: adapter_incoming_request_params.dig("data", "dashboard_link")) + datums << AdditionalDatum.new(format: "datetime", label: "Timestamp", value: timestamp) + datums << AdditionalDatum.new(format: "text", label: "Alert Type", value: adapter_incoming_request_params.dig("alert_type")) + datums << AdditionalDatum.new(format: "text", label: "Account ID", value: adapter_incoming_request_params.dig("account_id")) + datums << AdditionalDatum.new(format: "text", label: "Policy ID", value: adapter_incoming_request_params.dig("policy_id")) + + datums.filter { |datum| datum.value.present? } + end + end +end diff --git a/app/views/pager_tree/integrations/cloudflare/v3/_form_options.html.erb b/app/views/pager_tree/integrations/cloudflare/v3/_form_options.html.erb new file mode 100644 index 0000000..7439b3a --- /dev/null +++ b/app/views/pager_tree/integrations/cloudflare/v3/_form_options.html.erb @@ -0,0 +1,7 @@ +
+
+ <%= form.label :option_webhook_secret %> + <%= form.text_field :option_webhook_secret, class: "form-control" %> +

<%== t(".option_webhook_secret_hint_html") %>

+
+
\ No newline at end of file diff --git a/app/views/pager_tree/integrations/cloudflare/v3/_show_options.html.erb b/app/views/pager_tree/integrations/cloudflare/v3/_show_options.html.erb new file mode 100644 index 0000000..003399d --- /dev/null +++ b/app/views/pager_tree/integrations/cloudflare/v3/_show_options.html.erb @@ -0,0 +1,12 @@ +
+
+ <%= t("activerecord.attributes.pager_tree/integrations/cloudflare/v3.option_webhook_secret") %> +
+
+
+

+ <%= mask integration.option_webhook_secret %> +

+
+
+
diff --git a/config/locales/en.yml b/config/locales/en.yml index c9500b2..c3fcf23 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -18,6 +18,10 @@ en: option_alert_handoff_hint_html: "Send when the alert has been handed off" option_template_hint_html: "A handlebars template describing the body that should be posted. See docs for details." option_send_linked_hint_html: "Send linked data (source, source_log, user, team)" + cloudflare: + v3: + form_options: + option_webhook_secret_hint_html: "Cloudflare Webhook Secret" email: v3: form_options: @@ -133,6 +137,8 @@ en: option_outgoing_rules: "Outgoing Rules" "pager_tree/integrations/apex_ping/v3": option_api_key: "API Key" + "pager_tree/integrations/cloudflare/v3": + option_webhook_secret: "Webhook Secret" "pager_tree/integrations/email/v3": option_allow_spam: "Allow Spam" option_dedup_threads: "Dedup Threads" diff --git a/test/fixtures/pager_tree/integrations/integrations.yml b/test/fixtures/pager_tree/integrations/integrations.yml index 9258338..5fad334 100644 --- a/test/fixtures/pager_tree/integrations/integrations.yml +++ b/test/fixtures/pager_tree/integrations/integrations.yml @@ -20,6 +20,11 @@ azure_monitor_v3: type: "PagerTree::Integrations::AzureMonitor::V3" # options: no_options +cloudflare_v3: + type: "PagerTree::Integrations::Cloudflare::V3" + options: + webhook_secret: "abc123" + cronitor_v3: type: "PagerTree::Integrations::Cronitor::V3" # options: no_options diff --git a/test/models/pager_tree/integrations/cloudflare/v3_test.rb b/test/models/pager_tree/integrations/cloudflare/v3_test.rb new file mode 100644 index 0000000..259e77d --- /dev/null +++ b/test/models/pager_tree/integrations/cloudflare/v3_test.rb @@ -0,0 +1,74 @@ +require "test_helper" + +module PagerTree::Integrations + class Cloudflare::V3Test < ActiveSupport::TestCase + include Integrateable + + setup do + @integration = pager_tree_integrations_integrations(:cloudflare_v3) + + @create_request = { + name: "Testing Webhook", + text: "Requests to the following zone(s) have been failing for at least 5 minutes: zone-name", + data: { + unreachable_zones: [ + { + zone_name: "zone-name", + host: "" + } + ] + }, + ts: 1710782148, + account_id: "abc123", + policy_id: "def456", + alert_type: "real_origin_monitoring" + }.with_indifferent_access + end + + test "sanity" do + assert @integration.adapter_supports_incoming? + assert @integration.adapter_incoming_can_defer? + assert_not @integration.adapter_supports_outgoing? + assert @integration.adapter_show_alerts? + assert @integration.adapter_show_logs? + assert_not @integration.adapter_show_outgoing_webhook_delivery? + end + + test "adapter_actions" do + @integration.adapter_incoming_request_params = @create_request + assert_equal :create, @integration.adapter_action + end + + test "adapter_thirdparty_id" do + @integration.adapter_incoming_request_params = @create_request + assert @integration.adapter_thirdparty_id.present? + end + + test "adapter_process_create" do + @integration.adapter_incoming_request_params = @create_request + + true_alert = Alert.new( + title: "Requests to the following zone(s) have been failing for at least 5 ...", + description: ActionController::Base.helpers.simple_format(@create_request["text"]), + urgency: nil, + thirdparty_id: @integration.adapter_thirdparty_id, + dedup_keys: [], + additional_data: [ + AdditionalDatum.new(format: "datetime", label: "Timestamp", value: Time.at(1710782148).utc.to_datetime), + AdditionalDatum.new(format: "text", label: "Alert Type", value: "real_origin_monitoring"), + AdditionalDatum.new(format: "text", label: "Account ID", value: "abc123"), + AdditionalDatum.new(format: "text", label: "Policy ID", value: "def456") + ] + ) + + assert_equal true_alert.to_json, @integration.adapter_process_create.to_json + end + + test "blocking_incoming" do + @blocked_request = @create_request.deep_dup + @integration.option_webhook_secret = "abc123" + assert @integration.adapter_should_block_incoming?(OpenStruct.new({headers: {"cf-webhook-auth" => ""}})) + assert_not @integration.adapter_should_block_incoming?(OpenStruct.new({headers: {"cf-webhook-auth" => "abc123"}})) + end + end +end