diff --git a/kong-1.4.0-0.rockspec b/kong-1.4.0-0.rockspec index f8c5b2fe85e6..da399527b963 100644 --- a/kong-1.4.0-0.rockspec +++ b/kong-1.4.0-0.rockspec @@ -45,6 +45,7 @@ dependencies = { "kong-proxy-cache-plugin ~> 1.2", "kong-plugin-request-transformer ~> 1.2", "kong-plugin-session ~> 2.1", + "kong-plugin-aws-lambda ~> 3.0", } build = { type = "builtin", @@ -344,10 +345,6 @@ build = { ["kong.plugins.bot-detection.schema"] = "kong/plugins/bot-detection/schema.lua", ["kong.plugins.bot-detection.rules"] = "kong/plugins/bot-detection/rules.lua", - ["kong.plugins.aws-lambda.handler"] = "kong/plugins/aws-lambda/handler.lua", - ["kong.plugins.aws-lambda.schema"] = "kong/plugins/aws-lambda/schema.lua", - ["kong.plugins.aws-lambda.v4"] = "kong/plugins/aws-lambda/v4.lua", - ["kong.plugins.request-termination.handler"] = "kong/plugins/request-termination/handler.lua", ["kong.plugins.request-termination.schema"] = "kong/plugins/request-termination/schema.lua", } diff --git a/kong/plugins/aws-lambda/handler.lua b/kong/plugins/aws-lambda/handler.lua deleted file mode 100644 index 79b0205f286d..000000000000 --- a/kong/plugins/aws-lambda/handler.lua +++ /dev/null @@ -1,248 +0,0 @@ --- Copyright (C) Kong Inc. -local aws_v4 = require "kong.plugins.aws-lambda.v4" -local http = require "resty.http" -local cjson = require "cjson.safe" -local meta = require "kong.meta" -local constants = require "kong.constants" - - -local VIA_HEADER = constants.HEADERS.VIA -local VIA_HEADER_VALUE = meta._NAME .. "/" .. meta._VERSION - - -local tostring = tostring -local tonumber = tonumber -local type = type -local fmt = string.format -local ngx_encode_base64 = ngx.encode_base64 - - -local raw_content_types = { - ["text/plain"] = true, - ["text/html"] = true, - ["application/xml"] = true, - ["text/xml"] = true, - ["application/soap+xml"] = true, -} - - -local AWS_PORT = 443 - - ---[[ - Response format should be - { - "statusCode": httpStatusCode, - "headers": { "headerName": "headerValue", ... }, - "body": "..." - } ---]] -local function validate_custom_response(response) - if type(response.statusCode) ~= "number" then - return nil, "statusCode must be a number" - end - - if response.headers ~= nil and type(response.headers) ~= "table" then - return nil, "headers must be a table" - end - - if response.body ~= nil and type(response.body) ~= "string" then - return nil, "body must be a string" - end - - return true -end - - -local function extract_proxy_response(content) - local serialized_content, err = cjson.decode(content) - if not serialized_content then - return nil, err - end - - local ok, err = validate_custom_response(serialized_content) - if not ok then - return nil, err - end - - local headers = serialized_content.headers or {} - local body = serialized_content.body or "" - headers["Content-Length"] = #body - - return { - status_code = tonumber(serialized_content.statusCode), - body = body, - headers = headers, - } -end - - -local AWSLambdaHandler = {} - - -function AWSLambdaHandler:access(conf) - local upstream_body = kong.table.new(0, 6) - local var = ngx.var - - if conf.forward_request_body or conf.forward_request_headers - or conf.forward_request_method or conf.forward_request_uri - then - -- new behavior to forward request method, body, uri and their args - if conf.forward_request_method then - upstream_body.request_method = kong.request.get_method() - end - - if conf.forward_request_headers then - upstream_body.request_headers = kong.request.get_headers() - end - - if conf.forward_request_uri then - local path = kong.request.get_path() - local query = kong.request.get_raw_query() - if query ~= "" then - upstream_body.request_uri = path .. "?" .. query - else - upstream_body.request_uri = path - end - upstream_body.request_uri_args = kong.request.get_query() - end - - if conf.forward_request_body then - local content_type = kong.request.get_header("content-type") - local body_raw = kong.request.get_raw_body() - local body_args, err = kong.request.get_body() - if err and err:match("content type") then - body_args = {} - if not raw_content_types[content_type] then - -- don't know what this body MIME type is, base64 it just in case - body_raw = ngx_encode_base64(body_raw) - upstream_body.request_body_base64 = true - end - end - - upstream_body.request_body = body_raw - upstream_body.request_body_args = body_args - end - - else - -- backwards compatible upstream body for configurations not specifying - -- `forward_request_*` values - local body_args = kong.request.get_body() - upstream_body = kong.table.merge(kong.request.get_query(), body_args) - end - - local upstream_body_json, err = cjson.encode(upstream_body) - if not upstream_body_json then - kong.log.err("could not JSON encode upstream body", - " to forward request values: ", err) - end - - local host = fmt("lambda.%s.amazonaws.com", conf.aws_region) - local path = fmt("/2015-03-31/functions/%s/invocations", - conf.function_name) - local port = conf.port or AWS_PORT - - local opts = { - region = conf.aws_region, - service = "lambda", - method = "POST", - headers = { - ["X-Amz-Target"] = "invoke", - ["X-Amz-Invocation-Type"] = conf.invocation_type, - ["X-Amz-Log-Type"] = conf.log_type, - ["Content-Type"] = "application/x-amz-json-1.1", - ["Content-Length"] = upstream_body_json and tostring(#upstream_body_json), - }, - body = upstream_body_json, - path = path, - host = host, - port = port, - access_key = conf.aws_key, - secret_key = conf.aws_secret, - query = conf.qualifier and "Qualifier=" .. conf.qualifier - } - - local request, err = aws_v4(opts) - if err then - kong.log.err(err) - return kong.response.exit(500, { message = "An unexpected error occurred" }) - end - - -- Trigger request - local client = http.new() - client:set_timeout(conf.timeout) - client:connect(host, port) - local ok, err = client:ssl_handshake() - if not ok then - kong.log.err(err) - return kong.response.exit(500, { message = "An unexpected error occurred" }) - end - - local res, err = client:request { - method = "POST", - path = request.url, - body = request.body, - headers = request.headers - } - if not res then - kong.log.err(err) - return kong.response.exit(500, { message = "An unexpected error occurred" }) - end - - local content = res:read_body() - local headers = res.headers - - if var.http2 then - headers["Connection"] = nil - headers["Keep-Alive"] = nil - headers["Proxy-Connection"] = nil - headers["Upgrade"] = nil - headers["Transfer-Encoding"] = nil - end - - local ok, err = client:set_keepalive(conf.keepalive) - if not ok then - kong.log.err(err) - return kong.response.exit(500, { message = "An unexpected error occurred" }) - end - - local status - - if conf.is_proxy_integration then - local proxy_response, err = extract_proxy_response(content) - if not proxy_response then - kong.log.err(err) - return kong.response.exit(502, { message = "Bad Gateway", - error = "could not JSON decode Lambda " .. - "function response: " .. err }) - end - - status = proxy_response.status_code - headers = kong.table.merge(headers, proxy_response.headers) - content = proxy_response.body - end - - if not status then - if conf.unhandled_status - and headers["X-Amz-Function-Error"] == "Unhandled" - then - status = conf.unhandled_status - - else - status = res.status - end - end - - if kong.configuration.enabled_headers[VIA_HEADER] then - headers[VIA_HEADER] = VIA_HEADER_VALUE - end - - return kong.response.exit(status, content, headers) -end - - -AWSLambdaHandler.PRIORITY = 750 -AWSLambdaHandler.VERSION = "2.0.0" - - -return AWSLambdaHandler diff --git a/kong/plugins/aws-lambda/schema.lua b/kong/plugins/aws-lambda/schema.lua deleted file mode 100644 index 5c186b728bc8..000000000000 --- a/kong/plugins/aws-lambda/schema.lua +++ /dev/null @@ -1,97 +0,0 @@ -local typedefs = require "kong.db.schema.typedefs" - -local REGIONS = { - "ap-northeast-1", "ap-northeast-2", - "ap-south-1", - "ap-southeast-1", "ap-southeast-2", - "ca-central-1", - "cn-north-1", - "cn-northwest-1", - "eu-central-1", - "eu-west-1", "eu-west-2", - "sa-east-1", - "us-east-1", "us-east-2", - "us-gov-west-1", - "us-west-1", "us-west-2", -} - - -return { - name = "aws-lambda", - fields = { - { run_on = typedefs.run_on_first }, - { protocols = typedefs.protocols_http }, - { config = { - type = "record", - fields = { - { timeout = { - type = "number", - required = true, - default = 60000 - } }, - { keepalive = { - type = "number", - required = true, - default = 60000 - } }, - { aws_key = { - type = "string", - required = true - } }, - { aws_secret = { - type = "string", - required = true - } }, - { aws_region = { - type = "string", - required = true, - one_of = REGIONS - } }, - { function_name = { - type = "string", - required = true - } }, - { qualifier = { - type = "string" - } }, - { invocation_type = { - type = "string", - required = true, - default = "RequestResponse", - one_of = { "RequestResponse", "Event", "DryRun" }, - } }, - { log_type = { - type = "string", - required = true, - default = "Tail", - one_of = { "Tail", "None" }, - } }, - { port = typedefs.port { default = 443 }, }, - { unhandled_status = { - type = "integer", - between = { 100, 999 }, - } }, - { forward_request_method = { - type = "boolean", - default = false - } }, - { forward_request_uri = { - type = "boolean", - default = false - } }, - { forward_request_headers = { - type = "boolean", - default = false - } }, - { forward_request_body = { - type = "boolean", - default = false - } }, - { is_proxy_integration = { - type = "boolean", - default = false - } }, - } - } }, - }, -} diff --git a/kong/plugins/aws-lambda/v4.lua b/kong/plugins/aws-lambda/v4.lua deleted file mode 100644 index d7c40de54600..000000000000 --- a/kong/plugins/aws-lambda/v4.lua +++ /dev/null @@ -1,227 +0,0 @@ --- Performs AWSv4 Signing --- http://docs.aws.amazon.com/general/latest/gr/sigv4_signing.html - -local resty_sha256 = require "resty.sha256" -local pl_string = require "pl.stringx" -local openssl_hmac = require "openssl.hmac" - -local ALGORITHM = "AWS4-HMAC-SHA256" - -local CHAR_TO_HEX = {}; -for i = 0, 255 do - local char = string.char(i) - local hex = string.format("%02x", i) - CHAR_TO_HEX[char] = hex -end - -local function hmac(secret, data) - return openssl_hmac.new(secret, "sha256"):final(data) -end - -local function hash(str) - local sha256 = resty_sha256:new() - sha256:update(str) - return sha256:final() -end - -local function hex_encode(str) -- From prosody's util.hex - return (str:gsub(".", CHAR_TO_HEX)) -end - -local function percent_encode(char) - return string.format("%%%02X", string.byte(char)) -end - -local function canonicalise_path(path) - local segments = {} - for segment in path:gmatch("/([^/]*)") do - if segment == "" or segment == "." then - segments = segments -- do nothing and avoid lint - elseif segment == " .. " then - -- intentionally discards components at top level - segments[#segments] = nil - else - segments[#segments+1] = ngx.unescape_uri(segment):gsub("[^%w%-%._~]", - percent_encode) - end - end - local len = #segments - if len == 0 then - return "/" - end - -- If there was a slash on the end, keep it there. - if path:sub(-1, -1) == "/" then - len = len + 1 - segments[len] = "" - end - segments[0] = "" - segments = table.concat(segments, "/", 0, len) - return segments -end - -local function canonicalise_query_string(query) - local q = {} - for key, val in query:gmatch("([^&=]+)=?([^&]*)") do - key = ngx.unescape_uri(key):gsub("[^%w%-%._~]", percent_encode) - val = ngx.unescape_uri(val):gsub("[^%w%-%._~]", percent_encode) - q[#q+1] = key .. "=" .. val - end - table.sort(q) - return table.concat(q, "&") -end - -local function derive_signing_key(kSecret, date, region, service) - local kDate = hmac("AWS4" .. kSecret, date) - local kRegion = hmac(kDate, region) - local kService = hmac(kRegion, service) - local kSigning = hmac(kService, "aws4_request") - return kSigning -end - -local function prepare_awsv4_request(tbl) - local domain = tbl.domain or "amazonaws.com" - local region = tbl.region - local service = tbl.service - local request_method = tbl.method - local canonicalURI = tbl.canonicalURI - local path = tbl.path - if path and not canonicalURI then - canonicalURI = canonicalise_path(path) - elseif canonicalURI == nil or canonicalURI == "" then - canonicalURI = "/" - end - local canonical_querystring = tbl.canonical_querystring - local query = tbl.query - if query and not canonical_querystring then - canonical_querystring = canonicalise_query_string(query) - end - local req_headers = tbl.headers or {} - local req_payload = tbl.body - local access_key = tbl.access_key - local signing_key = tbl.signing_key - local secret_key - if not signing_key then - secret_key = tbl.secret_key - if secret_key == nil then - return nil, "either 'signing_key' or 'secret_key' must be provided" - end - end - local tls = tbl.tls - if tls == nil then - tls = true - end - local port = tbl.port or (tls and 443 or 80) - local timestamp = tbl.timestamp or ngx.time() - local req_date = os.date("!%Y%m%dT%H%M%SZ", timestamp) - local date = os.date("!%Y%m%d", timestamp) - - local host = service .. "." .. region .. "." .. domain - local host_header do -- If the "standard" port is not in use, the port should be added to the Host header - local with_port - if tls then - with_port = port ~= 443 - else - with_port = port ~= 80 - end - if with_port then - host_header = string.format("%s:%d", host, port) - else - host_header = host - end - end - - local headers = { - ["X-Amz-Date"] = req_date; - Host = host_header; - } - local add_auth_header = true - for k, v in pairs(req_headers) do - k = k:gsub("%f[^%z-]%w", string.upper) -- convert to standard header title case - if k == "Authorization" then - add_auth_header = false - elseif v == false then -- don't allow a default value for this header - v = nil - end - headers[k] = v - end - - -- Task 1: Create a Canonical Request For Signature Version 4 - -- http://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html - local canonical_headers, signed_headers do - -- We structure this code in a way so that we only have to sort once. - canonical_headers, signed_headers = {}, {} - local i = 0 - for name, value in pairs(headers) do - if value then -- ignore headers with 'false', they are used to override defaults - i = i + 1 - local name_lower = name:lower() - signed_headers[i] = name_lower - if canonical_headers[name_lower] ~= nil then - return nil, "header collision" - end - canonical_headers[name_lower] = pl_string.strip(value) - end - end - table.sort(signed_headers) - for j=1, i do - local name = signed_headers[j] - local value = canonical_headers[name] - canonical_headers[j] = name .. ":" .. value .. "\n" - end - signed_headers = table.concat(signed_headers, ";", 1, i) - canonical_headers = table.concat(canonical_headers, nil, 1, i) - end - local canonical_request = - request_method .. '\n' .. - canonicalURI .. '\n' .. - (canonical_querystring or "") .. '\n' .. - canonical_headers .. '\n' .. - signed_headers .. '\n' .. - hex_encode(hash(req_payload or "")) - - local hashed_canonical_request = hex_encode(hash(canonical_request)) - -- Task 2: Create a String to Sign for Signature Version 4 - -- http://docs.aws.amazon.com/general/latest/gr/sigv4-create-string-to-sign.html - local credential_scope = date .. "/" .. region .. "/" .. service .. "/aws4_request" - local string_to_sign = - ALGORITHM .. '\n' .. - req_date .. '\n' .. - credential_scope .. '\n' .. - hashed_canonical_request - - -- Task 3: Calculate the AWS Signature Version 4 - -- http://docs.aws.amazon.com/general/latest/gr/sigv4-calculate-signature.html - if signing_key == nil then - signing_key = derive_signing_key(secret_key, date, region, service) - end - local signature = hex_encode(hmac(signing_key, string_to_sign)) - -- Task 4: Add the Signing Information to the Request - -- http://docs.aws.amazon.com/general/latest/gr/sigv4-add-signature-to-request.html - local authorization = ALGORITHM - .. " Credential=" .. access_key .. "/" .. credential_scope - .. ", SignedHeaders=" .. signed_headers - .. ", Signature=" .. signature - if add_auth_header then - headers.Authorization = authorization - end - - local target = path or canonicalURI - if query or canonical_querystring then - target = target .. "?" .. (query or canonical_querystring) - end - local scheme = tls and "https" or "http" - local url = scheme .. "://" .. host_header .. target - - return { - url = url, - host = host, - port = port, - tls = tls, - method = request_method, - target = target, - headers = headers, - body = req_payload, - } -end - -return prepare_awsv4_request diff --git a/spec/03-plugins/22-aws-lambda/01-access_spec.lua b/spec/03-plugins/22-aws-lambda/01-access_spec.lua deleted file mode 100644 index 597aa6a0db75..000000000000 --- a/spec/03-plugins/22-aws-lambda/01-access_spec.lua +++ /dev/null @@ -1,882 +0,0 @@ -local cjson = require "cjson" -local helpers = require "spec.helpers" -local meta = require "kong.meta" - - -local server_tokens = meta._SERVER_TOKENS - - -local fixtures = { - http_mock = { - lambda_plugin = [[ - - server { - server_name mock_aws_lambda; - listen 10001 ssl; - - ssl_certificate ${{SSL_CERT}}; - ssl_certificate_key ${{SSL_CERT_KEY}}; - ssl_protocols TLSv1.1 TLSv1.2 TLSv1.3; - - location ~ "/2015-03-31/functions/(?:[^/])*/invocations" { - content_by_lua_block { - local function x() - local function say(res, status) - ngx.header["x-amzn-RequestId"] = "foo" - - if string.match(ngx.var.uri, "functionWithUnhandledError") then - ngx.header["X-Amz-Function-Error"] = "Unhandled" - end - - ngx.status = status - - if string.match(ngx.var.uri, "functionWithBadJSON") then - local badRes = "{\"foo\":\"bar\"" - ngx.header["Content-Length"] = #badRes + 1 - ngx.say(badRes) - - elseif string.match(ngx.var.uri, "functionWithNoResponse") then - ngx.header["Content-Length"] = 0 - - elseif type(res) == 'string' then - ngx.header["Content-Length"] = #res + 1 - ngx.say(res) - - else - ngx.req.discard_body() - ngx.header['Content-Length'] = 0 - end - - ngx.exit(0) - end - - ngx.sleep(.2) -- mock some network latency - - local invocation_type = ngx.var.http_x_amz_invocation_type - if invocation_type == 'Event' then - say(nil, 202) - - elseif invocation_type == 'DryRun' then - say(nil, 204) - end - - local qargs = ngx.req.get_uri_args() - ngx.req.read_body() - local args = require("cjson").decode(ngx.req.get_body_data()) - - say(ngx.req.get_body_data(), 200) - end - local ok, err = pcall(x) - if not ok then - ngx.log(ngx.ERR, "Mock error: ", err) - end - } - } - } - - ]] - }, -} - - -for _, strategy in helpers.each_strategy() do - describe("Plugin: AWS Lambda (access) [#" .. strategy .. "]", function() - local proxy_client - local admin_client - - lazy_setup(function() - local bp = helpers.get_db_utils(strategy, { - "routes", - "services", - "plugins", - }) - - local route1 = bp.routes:insert { - hosts = { "lambda.com" }, - } - - local route2 = bp.routes:insert { - hosts = { "lambda2.com" }, - } - - local route3 = bp.routes:insert { - hosts = { "lambda3.com" }, - } - - local route4 = bp.routes:insert { - hosts = { "lambda4.com" }, - } - - local route5 = bp.routes:insert { - hosts = { "lambda5.com" }, - } - - local route6 = bp.routes:insert { - hosts = { "lambda6.com" }, - } - - local route7 = bp.routes:insert { - hosts = { "lambda7.com" }, - } - - local route8 = bp.routes:insert { - hosts = { "lambda8.com" }, - } - - local route9 = bp.routes:insert { - hosts = { "lambda9.com" }, - protocols = { "http", "https" }, - service = bp.services:insert({ - protocol = "http", - host = "httpbin.org", - port = 80, - }) - } - - local service10 = bp.services:insert({ - protocol = "http", - host = "httpbin.org", - port = 80, - }) - - local route10 = bp.routes:insert { - hosts = { "lambda10.com" }, - protocols = { "http", "https" }, - service = service10 - } - - local service11 = bp.services:insert({ - protocol = "http", - host = "httpbin.org", - port = 80, - }) - - local route11 = bp.routes:insert { - hosts = { "lambda11.com" }, - protocols = { "http", "https" }, - service = service11 - } - - local service12 = bp.services:insert({ - protocol = "http", - host = "httpbin.org", - port = 80, - }) - - local route12 = bp.routes:insert { - hosts = { "lambda12.com" }, - protocols = { "http", "https" }, - service = service12 - } - - local route13 = bp.routes:insert { - hosts = { "lambda13.com" }, - protocols = { "http", "https" }, - service = service12, - } - - local route14 = bp.routes:insert { - hosts = { "lambda14.com" }, - protocols = { "http", "https" }, - service = ngx.null, - } - - bp.plugins:insert { - name = "aws-lambda", - route = { id = route1.id }, - config = { - port = 10001, - aws_key = "mock-key", - aws_secret = "mock-secret", - aws_region = "us-east-1", - function_name = "kongLambdaTest", - }, - } - - bp.plugins:insert { - name = "aws-lambda", - route = { id = route2.id }, - config = { - port = 10001, - aws_key = "mock-key", - aws_secret = "mock-secret", - aws_region = "us-east-1", - function_name = "kongLambdaTest", - invocation_type = "Event", - }, - } - - bp.plugins:insert { - name = "aws-lambda", - route = { id = route3.id }, - config = { - port = 10001, - aws_key = "mock-key", - aws_secret = "mock-secret", - aws_region = "us-east-1", - function_name = "kongLambdaTest", - invocation_type = "DryRun", - }, - } - - bp.plugins:insert { - name = "aws-lambda", - route = { id = route4.id }, - config = { - port = 10001, - aws_key = "mock-key", - aws_secret = "mock-secret", - aws_region = "us-east-1", - function_name = "kongLambdaTest", - timeout = 100, - }, - } - - bp.plugins:insert { - name = "aws-lambda", - route = { id = route5.id }, - config = { - port = 10001, - aws_key = "mock-key", - aws_secret = "mock-secret", - aws_region = "us-east-1", - function_name = "functionWithUnhandledError", - }, - } - - bp.plugins:insert { - name = "aws-lambda", - route = { id = route6.id }, - config = { - port = 10001, - aws_key = "mock-key", - aws_secret = "mock-secret", - aws_region = "us-east-1", - function_name = "functionWithUnhandledError", - invocation_type = "Event", - }, - } - - bp.plugins:insert { - name = "aws-lambda", - route = { id = route7.id }, - config = { - port = 10001, - aws_key = "mock-key", - aws_secret = "mock-secret", - aws_region = "us-east-1", - function_name = "functionWithUnhandledError", - invocation_type = "DryRun", - }, - } - - bp.plugins:insert { - name = "aws-lambda", - route = { id = route8.id }, - config = { - port = 10001, - aws_key = "mock-key", - aws_secret = "mock-secret", - aws_region = "us-east-1", - function_name = "functionWithUnhandledError", - unhandled_status = 412, - }, - } - - bp.plugins:insert { - name = "aws-lambda", - route = { id = route9.id }, - config = { - port = 10001, - aws_key = "mock-key", - aws_secret = "mock-secret", - aws_region = "us-east-1", - function_name = "kongLambdaTest", - forward_request_method = true, - forward_request_uri = true, - forward_request_headers = true, - forward_request_body = true, - } - } - - bp.plugins:insert { - name = "aws-lambda", - route = { id = route10.id }, - config = { - port = 10001, - aws_key = "mock-key", - aws_secret = "mock-secret", - aws_region = "us-east-1", - function_name = "kongLambdaTest", - forward_request_method = true, - forward_request_uri = false, - forward_request_headers = true, - forward_request_body = true, - } - } - - bp.plugins:insert { - name = "aws-lambda", - route = { id = route11.id }, - config = { - port = 10001, - aws_key = "mock-key", - aws_secret = "mock-secret", - aws_region = "us-east-1", - function_name = "kongLambdaTest", - is_proxy_integration = true, - } - } - - bp.plugins:insert { - name = "aws-lambda", - route = { id = route12.id }, - config = { - port = 10001, - aws_key = "mock-key", - aws_secret = "mock-secret", - aws_region = "us-east-1", - function_name = "functionWithBadJSON", - is_proxy_integration = true, - } - } - - bp.plugins:insert { - name = "aws-lambda", - route = { id = route13.id }, - config = { - port = 10001, - aws_key = "mock-key", - aws_secret = "mock-secret", - aws_region = "us-east-1", - function_name = "functionWithNoResponse", - is_proxy_integration = true, - } - } - - bp.plugins:insert { - name = "aws-lambda", - route = { id = route14.id }, - config = { - port = 10001, - aws_key = "mock-key", - aws_secret = "mock-secret", - aws_region = "us-east-1", - function_name = "kongLambdaTest", - }, - } - - assert(helpers.start_kong({ - database = strategy, - nginx_conf = "spec/fixtures/custom_nginx.template", - }, nil, nil, fixtures)) - end) - - before_each(function() - proxy_client = helpers.proxy_client() - admin_client = helpers.admin_client() - end) - - after_each(function () - proxy_client:close() - admin_client:close() - end) - - lazy_teardown(function() - helpers.stop_kong() - end) - - it("invokes a Lambda function with GET", function() - local res = assert(proxy_client:send { - method = "GET", - path = "/get?key1=some_value1&key2=some_value2&key3=some_value3", - headers = { - ["Host"] = "lambda.com" - } - }) - assert.res_status(200, res) - local body = assert.response(res).has.jsonbody() - assert.is_string(res.headers["x-amzn-RequestId"]) - assert.equal("some_value1", body.key1) - assert.is_nil(res.headers["X-Amz-Function-Error"]) - end) - it("invokes a Lambda function with POST params", function() - local res = assert(proxy_client:send { - method = "POST", - path = "/post", - headers = { - ["Host"] = "lambda.com", - ["Content-Type"] = "application/x-www-form-urlencoded" - }, - body = { - key1 = "some_value_post1", - key2 = "some_value_post2", - key3 = "some_value_post3" - } - }) - assert.res_status(200, res) - local body = assert.response(res).has.jsonbody() - assert.is_string(res.headers["x-amzn-RequestId"]) - assert.equal("some_value_post1", body.key1) - end) - it("invokes a Lambda function with POST json body", function() - local res = assert(proxy_client:send { - method = "POST", - path = "/post", - headers = { - ["Host"] = "lambda.com", - ["Content-Type"] = "application/json" - }, - body = { - key1 = "some_value_json1", - key2 = "some_value_json2", - key3 = "some_value_json3" - } - }) - assert.res_status(200, res) - local body = assert.response(res).has.jsonbody() - assert.is_string(res.headers["x-amzn-RequestId"]) - assert.equal("some_value_json1", body.key1) - end) - it("passes empty json arrays unmodified", function() - local res = assert(proxy_client:send { - method = "POST", - path = "/post", - headers = { - ["Host"] = "lambda.com", - ["Content-Type"] = "application/json" - }, - body = '[{}, []]' - }) - assert.res_status(200, res) - assert.equal('[{},[]]', string.gsub(res:read_body(), "\n","")) - end) - it("invokes a Lambda function with POST and both querystring and body params", function() - local res = assert(proxy_client:send { - method = "POST", - path = "/post?key1=from_querystring", - headers = { - ["Host"] = "lambda.com", - ["Content-Type"] = "application/x-www-form-urlencoded" - }, - body = { - key2 = "some_value_post2", - key3 = "some_value_post3" - } - }) - assert.res_status(200, res) - local body = assert.response(res).has.jsonbody() - assert.is_string(res.headers["x-amzn-RequestId"]) - assert.equal("from_querystring", body.key1) - end) - it("invokes a Lambda function with POST and xml payload, custom header and query partameter", function() - local res = assert(proxy_client:send { - method = "POST", - path = "/post?key1=from_querystring", - headers = { - ["Host"] = "lambda9.com", - ["Content-Type"] = "application/xml", - ["custom-header"] = "someheader" - }, - body = "" - }) - assert.res_status(200, res) - local body = assert.response(res).has.jsonbody() - assert.is_string(res.headers["x-amzn-RequestId"]) - - -- request_method - assert.equal("POST", body.request_method) - - -- request_uri - assert.equal("/post?key1=from_querystring", body.request_uri) - assert.is_table(body.request_uri_args) - - -- request_headers - assert.equal("someheader", body.request_headers["custom-header"]) - assert.equal("lambda9.com", body.request_headers.host) - - -- request_body - assert.equal("", body.request_body) - assert.is_table(body.request_body_args) - end) - it("invokes a Lambda function with POST and json payload, custom header and query partameter", function() - local res = assert(proxy_client:send { - method = "POST", - path = "/post?key1=from_querystring", - headers = { - ["Host"] = "lambda10.com", - ["Content-Type"] = "application/json", - ["custom-header"] = "someheader" - }, - body = { key2 = "some_value" } - }) - assert.res_status(200, res) - local body = assert.response(res).has.jsonbody() - assert.is_string(res.headers["x-amzn-RequestId"]) - - -- request_method - assert.equal("POST", body.request_method) - - -- no request_uri - assert.is_nil(body.request_uri) - assert.is_nil(body.request_uri_args) - - -- request_headers - assert.equal("lambda10.com", body.request_headers.host) - assert.equal("someheader", body.request_headers["custom-header"]) - - -- request_body - assert.equal("some_value", body.request_body_args.key2) - assert.is_table(body.request_body_args) - end) - it("invokes a Lambda function with POST and txt payload, custom header and query partameter", function() - local res = assert(proxy_client:send { - method = "POST", - path = "/post?key1=from_querystring", - headers = { - ["Host"] = "lambda9.com", - ["Content-Type"] = "text/plain", - ["custom-header"] = "someheader" - }, - body = "some text" - }) - assert.res_status(200, res) - local body = assert.response(res).has.jsonbody() - assert.is_string(res.headers["x-amzn-RequestId"]) - - -- request_method - assert.equal("POST", body.request_method) - - -- request_uri - assert.equal("/post?key1=from_querystring", body.request_uri) - assert.is_table(body.request_uri_args) - - -- request_headers - assert.equal("someheader", body.request_headers["custom-header"]) - assert.equal("lambda9.com", body.request_headers.host) - - -- request_body - assert.equal("some text", body.request_body) - assert.is_nil(body.request_body_base64) - assert.is_table(body.request_body_args) - end) - it("invokes a Lambda function with POST and binary payload and custom header", function() - local res = assert(proxy_client:send { - method = "POST", - path = "/post?key1=from_querystring", - headers = { - ["Host"] = "lambda9.com", - ["Content-Type"] = "application/octet-stream", - ["custom-header"] = "someheader" - }, - body = "01234" - }) - assert.res_status(200, res) - local body = assert.response(res).has.jsonbody() - assert.is_string(res.headers["x-amzn-RequestId"]) - - -- request_method - assert.equal("POST", body.request_method) - - -- request_uri - assert.equal("/post?key1=from_querystring", body.request_uri) - assert.is_table(body.request_uri_args) - - -- request_headers - assert.equal("lambda9.com", body.request_headers.host) - assert.equal("someheader", body.request_headers["custom-header"]) - - -- request_body - assert.equal(ngx.encode_base64('01234'), body.request_body) - assert.is_true(body.request_body_base64) - assert.is_table(body.request_body_args) - end) - it("invokes a Lambda function with POST params and Event invocation_type", function() - local res = assert(proxy_client:send { - method = "POST", - path = "/post", - headers = { - ["Host"] = "lambda2.com", - ["Content-Type"] = "application/x-www-form-urlencoded" - }, - body = { - key1 = "some_value_post1", - key2 = "some_value_post2", - key3 = "some_value_post3" - } - }) - assert.res_status(202, res) - assert.is_string(res.headers["x-amzn-RequestId"]) - end) - it("invokes a Lambda function with POST params and DryRun invocation_type", function() - local res = assert(proxy_client:send { - method = "POST", - path = "/post", - headers = { - ["Host"] = "lambda3.com", - ["Content-Type"] = "application/x-www-form-urlencoded" - }, - body = { - key1 = "some_value_post1", - key2 = "some_value_post2", - key3 = "some_value_post3" - } - }) - assert.res_status(204, res) - assert.is_string(res.headers["x-amzn-RequestId"]) - end) - it("errors on connection timeout", function() - local res = assert(proxy_client:send { - method = "GET", - path = "/get?key1=some_value1&key2=some_value2&key3=some_value3", - headers = { - ["Host"] = "lambda4.com", - } - }) - assert.res_status(500, res) - end) - - it("invokes a Lambda function with an unhandled function error (and no unhandled_status set)", function() - local res = assert(proxy_client:send { - method = "GET", - path = "/get?key1=some_value1&key2=some_value2&key3=some_value3", - headers = { - ["Host"] = "lambda5.com" - } - }) - assert.res_status(200, res) - assert.equal("Unhandled", res.headers["X-Amz-Function-Error"]) - end) - it("invokes a Lambda function with an unhandled function error with Event invocation type", function() - local res = assert(proxy_client:send { - method = "GET", - path = "/get?key1=some_value1&key2=some_value2&key3=some_value3", - headers = { - ["Host"] = "lambda6.com" - } - }) - assert.res_status(202, res) - assert.equal("Unhandled", res.headers["X-Amz-Function-Error"]) - end) - it("invokes a Lambda function with an unhandled function error with DryRun invocation type", function() - local res = assert(proxy_client:send { - method = "GET", - path = "/get?key1=some_value1&key2=some_value2&key3=some_value3", - headers = { - ["Host"] = "lambda7.com" - } - }) - assert.res_status(204, res) - assert.equal("Unhandled", res.headers["X-Amz-Function-Error"]) - end) - it("invokes a Lambda function with an unhandled function error", function() - local res = assert(proxy_client:send { - method = "GET", - path = "/get?key1=some_value1&key2=some_value2&key3=some_value3", - headers = { - ["Host"] = "lambda8.com" - } - }) - assert.res_status(412, res) - assert.equal("Unhandled", res.headers["X-Amz-Function-Error"]) - end) - - it("returns server tokens with Via header", function() - local res = assert(proxy_client:send { - method = "GET", - path = "/get?key1=some_value1&key2=some_value2&key3=some_value3", - headers = { - ["Host"] = "lambda.com" - } - }) - - assert.equal(server_tokens, res.headers["Via"]) - end) - - it("returns Content-Length header", function() - local res = assert(proxy_client:send { - method = "GET", - path = "/get?key1=some_value1&key2=some_value2&key3=some_value3", - headers = { - ["Host"] = "lambda.com" - } - }) - - assert.equal(65, tonumber(res.headers["Content-Length"])) - end) - - - describe("config.is_proxy_integration = true", function() - it("sets proper status code on custom response from Lambda", function() - local res = assert(proxy_client:send { - method = "POST", - path = "/post", - headers = { - ["Host"] = "lambda11.com", - ["Content-Type"] = "application/json" - }, - body = { - statusCode = 201, - } - }) - local body = assert.res_status(201, res) - assert.equal(0, tonumber(res.headers["Content-Length"])) - assert.equal(nil, res.headers["X-Custom-Header"]) - assert.equal("", body) - end) - - it("sets proper status code/headers/body on custom response from Lambda", function() - -- the lambda function must return a string - -- for the custom response "body" property - local body = cjson.encode({ - key1 = "some_value_post1", - key2 = "some_value_post2", - key3 = "some_value_post3", - }) - - local res = assert(proxy_client:send { - method = "POST", - path = "/post", - headers = { - ["Host"] = "lambda11.com", - ["Content-Type"] = "application/json", - }, - body = { - statusCode = 201, - body = body, - headers = { - ["X-Custom-Header"] = "Hello world!" - } - } - }) - - local res_body = assert.res_status(201, res) - assert.equal(79, tonumber(res.headers["Content-Length"])) - assert.equal("Hello world!", res.headers["X-Custom-Header"]) - assert.equal(body, res_body) - end) - - it("override duplicated headers with value from the custom response from Lambda", function() - -- the default "x-amzn-RequestId" returned is "foo" - -- let's check it is overriden with a custom value - local headers = { - ["x-amzn-RequestId"] = "bar", - } - - local res = assert(proxy_client:send { - method = "POST", - path = "/post", - headers = { - ["Host"] = "lambda11.com", - ["Content-Type"] = "application/json", - }, - body = { - statusCode = 201, - headers = headers, - } - }) - - assert.res_status(201, res) - assert.equal("bar", res.headers["x-amzn-RequestId"]) - end) - - it("returns HTTP 502 when 'status' property of custom response is not a number", function() - local res = assert(proxy_client:send { - method = "POST", - path = "/post", - headers = { - ["Host"] = "lambda11.com", - ["Content-Type"] = "application/json", - }, - body = { - statusCode = "hello", - } - }) - - assert.res_status(502, res) - local b = assert.response(res).has.jsonbody() - assert.equal("Bad Gateway", b.message) - end) - - it("returns HTTP 502 when 'headers' property of custom response is not a table", function() - local res = assert(proxy_client:send { - method = "POST", - path = "/post", - headers = { - ["Host"] = "lambda11.com", - ["Content-Type"] = "application/json", - }, - body = { - headers = "hello", - } - }) - - assert.res_status(502, res) - local b = assert.response(res).has.jsonbody() - assert.equal("Bad Gateway", b.message) - end) - - it("returns HTTP 502 when 'body' property of custom response is not a string", function() - local res = assert(proxy_client:send { - method = "POST", - path = "/post", - headers = { - ["Host"] = "lambda11.com", - ["Content-Type"] = "application/json", - }, - body = { - statusCode = 201, - body = 1234, - } - }) - - assert.res_status(502, res) - local b = assert.response(res).has.jsonbody() - assert.equal("Bad Gateway", b.message) - end) - - it("returns HTTP 502 with when response from lambda is not valid JSON", function() - local res = assert(proxy_client:send { - method = "POST", - path = "/post", - headers = { - ["Host"] = "lambda12.com", - } - }) - - assert.res_status(502, res) - local b = assert.response(res).has.jsonbody() - assert.equal("Bad Gateway", b.message) - end) - - it("returns HTTP 502 on empty response from Lambda", function() - local res = assert(proxy_client:send { - method = "POST", - path = "/post", - headers = { - ["Host"] = "lambda13.com", - } - }) - - assert.res_status(502, res) - local b = assert.response(res).has.jsonbody() - assert.equal("Bad Gateway", b.message) - end) - - it("invokes a Lambda function with GET using serviceless route", function() - local res = assert(proxy_client:send { - method = "GET", - path = "/get?key1=some_value1&key2=some_value2&key3=some_value3", - headers = { - ["Host"] = "lambda14.com" - } - }) - assert.res_status(200, res) - local body = assert.response(res).has.jsonbody() - assert.is_string(res.headers["x-amzn-RequestId"]) - assert.equal("some_value1", body.key1) - assert.is_nil(res.headers["X-Amz-Function-Error"]) - end) - end) - end) -end diff --git a/spec/03-plugins/22-aws-lambda/02-schema_spec.lua b/spec/03-plugins/22-aws-lambda/02-schema_spec.lua deleted file mode 100644 index bbda7062ba34..000000000000 --- a/spec/03-plugins/22-aws-lambda/02-schema_spec.lua +++ /dev/null @@ -1,55 +0,0 @@ -local schema_def = require "kong.plugins.aws-lambda.schema" -local validate_plugin_config_schema = require("spec.helpers").validate_plugin_config_schema - - -local kong = { - table = require("kong.pdk.table").new() -} - - -local DEFAULTS = { - timeout = 60000, - keepalive = 60000, - aws_key = "my-key", - aws_secret = "my-secret", - aws_region = "us-east-1", - function_name = "my-function", - invocation_type = "RequestResponse", - log_type = "Tail", - port = 443, -} - - -local function v(config) - return validate_plugin_config_schema( - kong.table.merge(DEFAULTS, config), - schema_def - ) -end - - -describe("Plugin: AWS Lambda (schema)", function() - it("accepts nil Unhandled Response Status Code", function() - local ok, err = v({ unhandled_status = nil }) - assert.truthy(ok) - assert.is_nil(err) - end) - - it("accepts correct Unhandled Response Status Code", function() - local ok, err = v({ unhandled_status = 412 }) - assert.truthy(ok) - assert.is_nil(err) - end) - - it("errors with Unhandled Response Status Code less than 100", function() - local ok, err = v({ unhandled_status = 99 }) - assert.falsy(ok) - assert.equal("value should be between 100 and 999", err.config.unhandled_status) - end) - - it("errors with Unhandled Response Status Code greater than 999", function() - local ok, err = v({ unhandled_status = 1000 }) - assert.falsy(ok) - assert.equal("value should be between 100 and 999", err.config.unhandled_status) - end) -end)