Skip to content

Commit

Permalink
feat(plugin): standard-webhooks
Browse files Browse the repository at this point in the history
* feat(plugins): standard-webhooks (#12962)

* Revert "Revert "feat(plugins): add standard-webhooks plugin (#12757)""

This reverts commit 8ff1771.

* Update kong/plugins/standard-webhooks/internal.lua

Co-authored-by: Samuele <samuele@konghq.com>

---------

Co-authored-by: Samuele <samuele@konghq.com>
(cherry picked from commit 03f056c)

* fix: cherry pick

* Update plugins-add-standard-webhooks.yml

* Update plugins-add-standard-webhooks.yml

* Update kong/plugins/standard-webhooks/handler.lua

Co-authored-by: Samuele <samuele@konghq.com>

* fix order

---------

Co-authored-by: Samuele <samuele@konghq.com>
  • Loading branch information
zekth and samugi authored Jul 8, 2024
1 parent 41abf29 commit 71dcff4
Show file tree
Hide file tree
Showing 11 changed files with 449 additions and 0 deletions.
4 changes: 4 additions & 0 deletions .github/labeler.yml
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,10 @@ copyright:
- changed-files:
- any-glob-to-any-file: COPYRIGHT

plugins/standard-webhooks:
- changed-files:
- any-glob-to-any-file: kong/plugins/standard-webhooks/**/*

schema-change-noteworthy:
- changed-files:
- any-glob-to-any-file: [
Expand Down
4 changes: 4 additions & 0 deletions changelog/unreleased/kong/plugins-add-standard-webhooks.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
message: |
**standard-webhooks**: Added standard webhooks plugin.
type: feature
scope: Plugin
4 changes: 4 additions & 0 deletions kong-3.8.0-0.rockspec
Original file line number Diff line number Diff line change
Expand Up @@ -1037,6 +1037,10 @@ build = {
["kong.plugins.xml-threat-protection.handler"] = "kong/plugins/xml-threat-protection/handler.lua",
["kong.plugins.xml-threat-protection.schema"] = "kong/plugins/xml-threat-protection/schema.lua",

["kong.plugins.standard-webhooks.handler"] = "kong/plugins/standard-webhooks/handler.lua",
["kong.plugins.standard-webhooks.internal"] = "kong/plugins/standard-webhooks/internal.lua",
["kong.plugins.standard-webhooks.schema"] = "kong/plugins/standard-webhooks/schema.lua",

["kong.vaults.env"] = "kong/vaults/env/init.lua",
["kong.vaults.env.schema"] = "kong/vaults/env/schema.lua",
["kong.vaults.aws"] = "kong/vaults/aws/init.lua",
Expand Down
1 change: 1 addition & 0 deletions kong/constants.lua
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ local plugins = {
"ai-prompt-guard",
"ai-request-transformer",
"ai-response-transformer",
"standard-webhooks",
}

local ce_plugin_map = {}
Expand Down
19 changes: 19 additions & 0 deletions kong/plugins/standard-webhooks/handler.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
-- This software is copyright Kong Inc. and its licensors.
-- Use of the software is subject to the agreement between your organization
-- and Kong Inc. If there is no such agreement, use is governed by and
-- subject to the terms of the Kong Master Software License Agreement found
-- at https://konghq.com/enterprisesoftwarelicense/.
-- [ END OF LICENSE 0867164ffc95e54f04670b5169c09574bdbd9bba ]

local plugin = require "kong.plugins.standard-webhooks.internal"

local StandardWebhooks = {
VERSION = require("kong.meta").core_version,
PRIORITY = 760
}

function StandardWebhooks:access(conf)
plugin.access(conf)
end

return StandardWebhooks
83 changes: 83 additions & 0 deletions kong/plugins/standard-webhooks/internal.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
-- This software is copyright Kong Inc. and its licensors.
-- Use of the software is subject to the agreement between your organization
-- and Kong Inc. If there is no such agreement, use is governed by and
-- subject to the terms of the Kong Master Software License Agreement found
-- at https://konghq.com/enterprisesoftwarelicense/.
-- [ END OF LICENSE 0867164ffc95e54f04670b5169c09574bdbd9bba ]

local kong = kong
local mac = require "resty.openssl.mac"
local tonumber = tonumber
local ngx = ngx
local type = type

local HEADER_WEBHOOK_ID = "webhook-id"
local HEADER_WEBHOOK_SIGN = "webhook-signature"
local HEADER_WEBHOOK_TS = "webhook-timestamp"

local function getHeader(input)
if type(input) == "table" then
return input[1]
end

return input
end

local function sign(secret, id, ts, payload)
local d, err = mac.new(secret, "HMAC", nil, "sha256")
if err then
kong.log.error(err)
return kong.response.error(500)
end
local r, err = d:final(id .. "." .. ts .. "." .. payload)
if err then
kong.log.error(err)
return kong.response.error(500)
end
return "v1," .. ngx.encode_base64(r)
end

local function extract_webhook()
local headers = kong.request.get_headers()

local id = getHeader(headers[HEADER_WEBHOOK_ID])
local signature = getHeader(headers[HEADER_WEBHOOK_SIGN])
local ts = getHeader(headers[HEADER_WEBHOOK_TS])
if not id or not signature or not ts then
kong.log.debug("missing required headers")
return kong.response.error(400)
end

ts = tonumber(ts) or 0 -- if parse fails we inject 0, which will fail on clock-skew check

return id, signature, ts
end


local function access(config)
local id, signature, ts = extract_webhook()

if ngx.now() - ts > config.tolerance_second then
kong.log.debug("timestamp tolerance exceeded")
return kong.response.error(400)
end

local body = kong.request.get_raw_body()

if not body or body == "" then
kong.log.debug("missing required body")
return kong.response.error(400)
end

local expected_signature = sign(config.secret_v1, id, ts, body)

if signature ~= expected_signature then
kong.log.debug("signature not matched")
return kong.response.error(400)
end
end

return {
access = access,
sign = sign
}
45 changes: 45 additions & 0 deletions kong/plugins/standard-webhooks/schema.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
-- This software is copyright Kong Inc. and its licensors.
-- Use of the software is subject to the agreement between your organization
-- and Kong Inc. If there is no such agreement, use is governed by and
-- subject to the terms of the Kong Master Software License Agreement found
-- at https://konghq.com/enterprisesoftwarelicense/.
-- [ END OF LICENSE 0867164ffc95e54f04670b5169c09574bdbd9bba ]

local typedefs = require "kong.db.schema.typedefs"

local PLUGIN_NAME = "standard-webhooks"

local schema = {
name = PLUGIN_NAME,
fields = {
{ consumer = typedefs.no_consumer },
{ protocols = typedefs.protocols_http },
{
config = {
type = "record",
fields = {
{
secret_v1 = {
type = "string",
required = true,
description = "Webhook secret",
referenceable = true,
encrypted = true,
},
},
{
tolerance_second = {
description = "Tolerance of the webhook timestamp in seconds. If the webhook timestamp is older than this number of seconds, it will be rejected with a '400' response.",
type = "integer",
required = true,
gt = -1,
default = 5 * 60
}
}
}
}
}
}
}

return schema
1 change: 1 addition & 0 deletions spec-ee/03-plugins/01-plugins_order_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@ describe("Plugins", function()
'ai-prompt-guard',
'ai-proxy',
'ai-response-transformer',
'standard-webhooks',
'kafka-upstream',
'aws-lambda',
'azure-functions',
Expand Down
1 change: 1 addition & 0 deletions spec/01-unit/12-plugins_order_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ describe("Plugins", function()
"ai-prompt-guard",
"ai-proxy",
"ai-response-transformer",
"standard-webhooks",
"aws-lambda",
"azure-functions",
"upstream-timeout",
Expand Down
147 changes: 147 additions & 0 deletions spec/03-plugins/44-standard-webhooks/01-unit_spec.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
-- This software is copyright Kong Inc. and its licensors.
-- Use of the software is subject to the agreement between your organization
-- and Kong Inc. If there is no such agreement, use is governed by and
-- subject to the terms of the Kong Master Software License Agreement found
-- at https://konghq.com/enterprisesoftwarelicense/.
-- [ END OF LICENSE 0867164ffc95e54f04670b5169c09574bdbd9bba ]

local PLUGIN_NAME = "standard-webhooks"


-- helper function to validate data against a schema
local validate do
local validate_entity = require("spec.helpers").validate_plugin_config_schema
local plugin_schema = require("kong.plugins."..PLUGIN_NAME..".schema")

function validate(data)
return validate_entity(data, plugin_schema)
end
end


describe(PLUGIN_NAME .. ": (schema)", function()


it("accepts a valid config", function()
local ok, err = validate({
secret_v1 = "abc123",
tolerance_second = 5*60,
})
assert.is_nil(err)
assert.is_truthy(ok)
end)


describe("secret", function()

it("must be set", function()
local ok, err = validate({
secret_v1 = nil,
tolerance_second = 5*60,
})

assert.is_same({
["config"] = {
["secret_v1"] = 'required field missing',
}
}, err)
assert.is_falsy(ok)
end)


it("is not nullable", function()
local ok, err = validate({
secret_v1 = assert(ngx.null),
tolerance_second = 5*60,
})

assert.is_same({
["config"] = {
["secret_v1"] = 'required field missing',
}
}, err)
assert.is_falsy(ok)
end)


it("must be a string", function()
local ok, err = validate({
secret_v1 = 123,
tolerance_second = 5*60,
})

assert.is_same({
["config"] = {
["secret_v1"] = 'expected a string',
}
}, err)
assert.is_falsy(ok)
end)

end)



describe("tolerance_second", function()

it("gets a default", function()
local ok, err = validate({
secret_v1 = "abc123",
tolerance_second = nil,
})

assert.is_nil(err)
assert.are.same(ok.config, {
secret_v1 = "abc123",
tolerance_second = 5*60,
})
end)


it("is not nullable", function()
local ok, err = validate({
secret_v1 = "abc123",
tolerance_second = assert(ngx.null),
})

assert.is_same({
["config"] = {
["tolerance_second"] = 'required field missing',
}
}, err)
assert.is_falsy(ok)
end)


it("must be an integer", function()
local ok, err = validate({
secret_v1 = "abc123",
tolerance_second = 5.67,
})

assert.is_same({
["config"] = {
["tolerance_second"] = 'expected an integer',
}
}, err)
assert.is_falsy(ok)
end)


it("must be >= 0", function()
local ok, err = validate({
secret_v1 = "abc123",
tolerance_second = -1,
})

assert.is_same({
["config"] = {
["tolerance_second"] = 'value must be greater than -1',
}
}, err)
assert.is_falsy(ok)
end)

end)

end)
Loading

0 comments on commit 71dcff4

Please sign in to comment.