Skip to content

Commit

Permalink
Revert "Revert "feat(plugins): add standard-webhooks plugin (#12757)""
Browse files Browse the repository at this point in the history
This reverts commit 8ff1771.
  • Loading branch information
zekth authored Apr 29, 2024
1 parent 8ff1771 commit 2dd99e5
Show file tree
Hide file tree
Showing 10 changed files with 415 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 @@ -254,6 +254,10 @@ plugins/opentelemetry:
- changed-files:
- any-glob-to-any-file: kong/plugins/opentelemetry/**/*

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

schema-change-noteworthy:
- changed-files:
- any-glob-to-any-file: ['kong/db/schema/**/*.lua', 'kong/**/schema.lua', 'kong/plugins/**/daos.lua', 'plugins-ee/**/daos.lua', 'plugins-ee/**/schema.lua', 'kong/db/dao/*.lua', 'kong/enterprise_edition/redis/init.lua']
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: |
Add standard webhooks plugin
type: feature
scope: Plugin
4 changes: 4 additions & 0 deletions kong-3.7.0-0.rockspec
Original file line number Diff line number Diff line change
Expand Up @@ -608,6 +608,10 @@ build = {
["kong.plugins.ai-prompt-guard.handler"] = "kong/plugins/ai-prompt-guard/handler.lua",
["kong.plugins.ai-prompt-guard.schema"] = "kong/plugins/ai-prompt-guard/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",

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

local plugin_map = {}
Expand Down
12 changes: 12 additions & 0 deletions kong/plugins/standard-webhooks/handler.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
local plugin = require "kong.plugins.standard-webhooks.internal"

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

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

return StandardWebhooks
78 changes: 78 additions & 0 deletions kong/plugins/standard-webhooks/internal.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
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]
else
return input
end
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
return
end

kong.log.debug("signature not matched")
return kong.response.error(400)
end

return {
access = access,
sign = sign
}
38 changes: 38 additions & 0 deletions kong/plugins/standard-webhooks/schema.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
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/01-unit/12-plugins_order_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ describe("Plugins", function()
"ai-prompt-guard",
"ai-proxy",
"ai-response-transformer",
"standard-webhooks",
"aws-lambda",
"azure-functions",
"proxy-cache",
Expand Down
140 changes: 140 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,140 @@
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 2dd99e5

Please sign in to comment.