This is a proof-of-concept for validation Webhooks from https://getport.io. This is the source for a GCP Cloud Function for Ruby 3.2.
Created
January 9, 2024 23:50
-
-
Save tcaddy/d1dd1b9270bcd694dae2de3014a79d30 to your computer and use it in GitHub Desktop.
getport.io Webhook validation in Ruby
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require 'functions_framework' | |
require 'base64' | |
require 'openssl' | |
FunctionsFramework.http "entrypoint" do |request| | |
# The request parameter is a Rack::Request object. | |
# See https://www.rubydoc.info/gems/rack/Rack/Request | |
WebhookProcessor.new(request: request).call | |
end | |
class WebhookProcessor | |
# See: https://docs.getport.io/create-self-service-experiences/security/ | |
GET_PORT_IP_ADDRESSES = [ | |
'44.221.30.248', '44.193.148.179', '34.197.132.205', '3.251.12.205', | |
].freeze | |
HEADERS = { | |
get_port: { | |
signature: 'X_PORT_SIGNATURE', | |
timestamp: 'X_PORT_TIMESTAMP' | |
} | |
}.freeze | |
def call | |
return four_zero_four unless valid_signature? | |
process | |
response | |
end | |
private | |
def initialize(request:) | |
@request = request | |
@response = {} | |
end | |
def response | |
# response can be: | |
# * a string | |
# * a Ruby Hash (which will be converted to a JSON-encoded string) | |
# * an instance of `Rack::Response` | |
# * A Rack response array | |
@response | |
end | |
def four_zero_four | |
Rack::Response.new('not authorized', 401) | |
end | |
def process | |
puts "TODO: do stuff here to handle webhook" | |
@response[:msg] = "OK" | |
end | |
def request_originated_from_get_port? | |
(GET_PORT_IP_ADDRESSES & @request.forwarded_for).any? | |
end | |
def expected_signature | |
case | |
when request_originated_from_get_port? then | |
@request.get_header("HTTP_#{HEADERS[:get_port][:signature]}").split(',')[1] | |
else nil | |
end | |
end | |
def computed_signature | |
case | |
when request_originated_from_get_port? then | |
Base64.strict_encode64( | |
OpenSSL::HMAC.digest( | |
'sha256', | |
ENV['GET_PORT_CLIENT_SECRET'], | |
[ | |
@request.get_header("HTTP_#{HEADERS[:get_port][:timestamp]}"), | |
@request.body.string | |
].join('.') | |
) | |
) | |
else nil | |
end | |
end | |
def valid_signature? | |
case | |
when request_originated_from_get_port? then | |
Rack::Utils.secure_compare(computed_signature, expected_signature) | |
else false | |
end | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
source "https://rubygems.org" | |
gem "functions_framework", "~> 1.4" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
GEM | |
remote: https://rubygems.org/ | |
specs: | |
cloud_events (0.7.1) | |
functions_framework (1.4.1) | |
cloud_events (>= 0.7.0, < 2.a) | |
puma (>= 4.3.0, < 7.a) | |
rack (>= 2.1, < 4.a) | |
nio4r (2.5.9) | |
puma (6.4.0) | |
nio4r (~> 2.0) | |
rack (3.0.8) | |
PLATFORMS | |
ruby | |
DEPENDENCIES | |
functions_framework (~> 1.4) | |
BUNDLED WITH | |
2.4.10 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment