- Implements Apache APISIX packaging for Cloudron platform. - Includes Dockerfile, CloudronManifest.json, and start.sh. - Configured to use Cloudron's etcd addon. 🤖 Generated with Gemini CLI Co-Authored-By: Gemini <noreply@google.com>
188 lines
6.8 KiB
Lua
188 lines
6.8 KiB
Lua
--
|
|
-- Licensed to the Apache Software Foundation (ASF) under one or more
|
|
-- contributor license agreements. See the NOTICE file distributed with
|
|
-- this work for additional information regarding copyright ownership.
|
|
-- The ASF licenses this file to You under the Apache License, Version 2.0
|
|
-- (the "License"); you may not use this file except in compliance with
|
|
-- the License. You may obtain a copy of the License at
|
|
--
|
|
-- http://www.apache.org/licenses/LICENSE-2.0
|
|
--
|
|
-- Unless required by applicable law or agreed to in writing, software
|
|
-- distributed under the License is distributed on an "AS IS" BASIS,
|
|
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
-- See the License for the specific language governing permissions and
|
|
-- limitations under the License.
|
|
|
|
local ngx = ngx
|
|
local hmac = require("resty.hmac")
|
|
local hex_encode = require("resty.string").to_hex
|
|
local resty_sha256 = require("resty.sha256")
|
|
local str_strip = require("pl.stringx").strip
|
|
local norm_path = require("pl.path").normpath
|
|
local pairs = pairs
|
|
local tab_concat = table.concat
|
|
local tab_sort = table.sort
|
|
local os = os
|
|
|
|
|
|
local plugin_name = "aws-lambda"
|
|
local plugin_version = 0.1
|
|
local priority = -1899
|
|
|
|
local ALGO = "AWS4-HMAC-SHA256"
|
|
|
|
local function hmac256(key, msg)
|
|
return hmac:new(key, hmac.ALGOS.SHA256):final(msg)
|
|
end
|
|
|
|
local function sha256(msg)
|
|
local hash = resty_sha256:new()
|
|
hash:update(msg)
|
|
local digest = hash:final()
|
|
return hex_encode(digest)
|
|
end
|
|
|
|
local function get_signature_key(key, datestamp, region, service)
|
|
local kDate = hmac256("AWS4" .. key, datestamp)
|
|
local kRegion = hmac256(kDate, region)
|
|
local kService = hmac256(kRegion, service)
|
|
local kSigning = hmac256(kService, "aws4_request")
|
|
return kSigning
|
|
end
|
|
|
|
local aws_authz_schema = {
|
|
type = "object",
|
|
properties = {
|
|
-- API Key based authorization
|
|
apikey = {type = "string"},
|
|
-- IAM role based authorization, works via aws v4 request signing
|
|
-- more at https://docs.aws.amazon.com/general/latest/gr/sigv4_signing.html
|
|
iam = {
|
|
type = "object",
|
|
properties = {
|
|
accesskey = {
|
|
type = "string",
|
|
description = "access key id from from aws iam console"
|
|
},
|
|
secretkey = {
|
|
type = "string",
|
|
description = "secret access key from from aws iam console"
|
|
},
|
|
aws_region = {
|
|
type = "string",
|
|
default = "us-east-1",
|
|
description = "the aws region that is receiving the request"
|
|
},
|
|
service = {
|
|
type = "string",
|
|
default = "execute-api",
|
|
description = "the service that is receiving the request"
|
|
}
|
|
},
|
|
required = {"accesskey", "secretkey"}
|
|
}
|
|
}
|
|
}
|
|
|
|
local function request_processor(conf, ctx, params)
|
|
local headers = params.headers
|
|
-- set authorization headers if not already set by the client
|
|
-- we are following not to overwrite the authz keys
|
|
if not headers["x-api-key"] then
|
|
if conf.authorization and conf.authorization.apikey then
|
|
headers["x-api-key"] = conf.authorization.apikey
|
|
return
|
|
end
|
|
end
|
|
|
|
-- performing aws v4 request signing for IAM authorization
|
|
-- visit https://docs.aws.amazon.com/general/latest/gr/sigv4-signed-request-examples.html
|
|
-- to look at the pseudocode in python.
|
|
if headers["authorization"] or not conf.authorization or not conf.authorization.iam then
|
|
return
|
|
end
|
|
|
|
-- create a date for headers and the credential string
|
|
local t = ngx.time()
|
|
local amzdate = os.date("!%Y%m%dT%H%M%SZ", t)
|
|
local datestamp = os.date("!%Y%m%d", t) -- Date w/o time, used in credential scope
|
|
headers["X-Amz-Date"] = amzdate
|
|
|
|
-- computing canonical uri
|
|
local canonical_uri = norm_path(params.path)
|
|
if canonical_uri ~= "/" then
|
|
if canonical_uri:sub(-1, -1) == "/" then
|
|
canonical_uri = canonical_uri:sub(1, -2)
|
|
end
|
|
if canonical_uri:sub(1, 1) ~= "/" then
|
|
canonical_uri = "/" .. canonical_uri
|
|
end
|
|
end
|
|
|
|
-- computing canonical query string
|
|
local canonical_qs = {}
|
|
local canonical_qs_i = 0
|
|
for k, v in pairs(params.query) do
|
|
canonical_qs_i = canonical_qs_i + 1
|
|
canonical_qs[canonical_qs_i] = ngx.unescape_uri(k) .. "=" .. ngx.unescape_uri(v)
|
|
end
|
|
|
|
tab_sort(canonical_qs)
|
|
canonical_qs = tab_concat(canonical_qs, "&")
|
|
|
|
-- computing canonical and signed headers
|
|
|
|
local canonical_headers, signed_headers = {}, {}
|
|
local signed_headers_i = 0
|
|
for k, v in pairs(headers) do
|
|
k = k:lower()
|
|
if k ~= "connection" then
|
|
signed_headers_i = signed_headers_i + 1
|
|
signed_headers[signed_headers_i] = k
|
|
-- strip starting and trailing spaces including strip multiple spaces into single space
|
|
canonical_headers[k] = str_strip(v)
|
|
end
|
|
end
|
|
tab_sort(signed_headers)
|
|
|
|
for i = 1, #signed_headers do
|
|
local k = signed_headers[i]
|
|
canonical_headers[i] = k .. ":" .. canonical_headers[k] .. "\n"
|
|
end
|
|
canonical_headers = tab_concat(canonical_headers, nil, 1, #signed_headers)
|
|
signed_headers = tab_concat(signed_headers, ";")
|
|
|
|
-- combining elements to form the canonical request (step-1)
|
|
local canonical_request = params.method:upper() .. "\n"
|
|
.. canonical_uri .. "\n"
|
|
.. (canonical_qs or "") .. "\n"
|
|
.. canonical_headers .. "\n"
|
|
.. signed_headers .. "\n"
|
|
.. sha256(params.body or "")
|
|
|
|
-- creating the string to sign for aws signature v4 (step-2)
|
|
local iam = conf.authorization.iam
|
|
local credential_scope = datestamp .. "/" .. iam.aws_region .. "/"
|
|
.. iam.service .. "/aws4_request"
|
|
local string_to_sign = ALGO .. "\n"
|
|
.. amzdate .. "\n"
|
|
.. credential_scope .. "\n"
|
|
.. sha256(canonical_request)
|
|
|
|
-- calculate the signature (step-3)
|
|
local signature_key = get_signature_key(iam.secretkey, datestamp, iam.aws_region, iam.service)
|
|
local signature = hex_encode(hmac256(signature_key, string_to_sign))
|
|
|
|
-- add info to the headers (step-4)
|
|
headers["authorization"] = ALGO .. " Credential=" .. iam.accesskey
|
|
.. "/" .. credential_scope
|
|
.. ", SignedHeaders=" .. signed_headers
|
|
.. ", Signature=" .. signature
|
|
end
|
|
|
|
|
|
local serverless_obj = require("apisix.plugins.serverless.generic-upstream")
|
|
|
|
return serverless_obj(plugin_name, plugin_version, priority, request_processor, aws_authz_schema)
|