tzssangglass commented on a change in pull request #5594:
URL: https://github.com/apache/apisix/pull/5594#discussion_r758881212



##########
File path: apisix/plugins/aws-lambda.lua
##########
@@ -0,0 +1,183 @@
+--
+-- 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 = {}
+    for k, v in pairs(params.query) do
+        canonical_qs[#canonical_qs+1] = 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 = {}, {}
+    for k, v in pairs(headers) do
+        k = k:lower()
+        if k ~= "connection" then
+            signed_headers[#signed_headers+1] = k

Review comment:
       I don't understand here, do we just need to skip `connection`?

##########
File path: docs/en/latest/plugins/aws-lambda.md
##########
@@ -0,0 +1,156 @@
+---
+title: aws-lambda
+---
+
+<!--
+#
+# 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.
+#
+-->
+
+## Summary
+
+- [Summary](#summary)
+- [Name](#name)
+- [Attributes](#attributes)
+  - [IAM Authorization Schema](#iam-authorization-schema)
+- [How To Enable](#how-to-enable)
+- [Disable Plugin](#disable-plugin)
+
+## Name
+
+`aws-lambda` is a serverless plugin built into Apache APISIX for seamless 
integration with [AWS Lambda](https://aws.amazon.com/lambda/), a widely used 
serverless solution, as a dynamic upstream to proxy all requests for a 
particular URI to the AWS cloud - one of the highly used public cloud platforms 
for production environment. If enabled, this plugin terminates the ongoing 
request to that particular URI and initiates a new request to the aws lambda 
gateway uri (the new upstream) on behalf of the client with the suitable 
authorization details set by the users, request headers, request body, params ( 
all these three components are passed from the original request ) and returns 
the response body, status code and the headers back to the original client that 
has invoked the request to the APISIX agent.
+At present, the plugin supports authorization via aws api key and aws IAM 
Secrets.
+
+## Attributes
+
+| Name             | Type   | Requirement  | Default      | Valid       | 
Description                                                                     
           |
+| -----------      | ------ | -----------  | -------      | -----       | 
------------------------------------------------------------                    
           |
+| function_uri      | string | required    |          |   | The aws api 
gateway endpoint which triggers the lambda serverless function code.   |
+| authorization   | object | optional    |         |     |  Authorization 
credentials to access the cloud function.                                       
                      |
+| authorization.apikey | string | optional    |             |     | Field 
inside _authorization_. The generate API Key to authorize requests to that 
endpoint of the aws gateway. |                         |
+| authorization.iam | object | optional    |             |     | Field inside 
_authorization_. AWS IAM role based authorization, performed via aws v4 request 
signing. See schema details below ([here](#iam-authorization-schema)). |        
                 |
+| timeout  | integer | optional    | 3000           | [100,...]     | Proxy 
request timeout in milliseconds.   |
+| ssl_verify  | boolean | optional    | true           | true/false     | If 
enabled performs SSL verification of the server.                     |
+| keepalive  | boolean | optional    | true           | true/false     | To 
reuse the same proxy connection in near future. Set to false to disable 
keepalives and immediately close the connection.                    |
+| keepalive_pool  | integer | optional    | 5          | [1,...]     | The 
maximum number of connections in the pool.              |
+| keepalive_timeout  | integer | optional    | 60000           | [1000,...]    
 |  The maximal idle timeout (ms).                     |
+
+### IAM Authorization Schema
+
+| Name             | Type   | Requirement  | Default        | Valid       | 
Description                                                                     
           |
+| -----------      | ------ | -----------  | -------        | -----       | 
------------------------------------------------------------                    
           |
+| accesskey        | string | required     |                |             | 
Genereated  access key ID from aws IAM console.                                 
           |

Review comment:
       ```suggestion
   | accesskey        | string | required     |                |             | 
Genereated access key ID from aws IAM console.                                  
          |
   ```

##########
File path: t/plugin/aws-lambda.t
##########
@@ -0,0 +1,299 @@
+#
+# 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.
+#
+use t::APISIX 'no_plan';
+
+repeat_each(1);
+no_long_string();
+no_root_location();
+no_shuffle();
+
+add_block_preprocessor(sub {
+    my ($block) = @_;
+
+    my $inside_lua_block = $block->inside_lua_block // "";
+    chomp($inside_lua_block);
+    my $http_config = $block->http_config // <<_EOC_;
+
+    server {
+        listen 8765;
+
+        location /httptrigger {
+            content_by_lua_block {
+                ngx.req.read_body()
+                local msg = "aws lambda invoked"
+                ngx.header['Content-Length'] = #msg + 1
+                ngx.header['Connection'] = "Keep-Alive"
+                ngx.say(msg)
+            }
+        }
+
+        location /generic {
+            content_by_lua_block {
+                $inside_lua_block
+            }
+        }
+    }
+_EOC_
+
+    $block->set_value("http_config", $http_config);
+
+    if (!$block->request) {
+        $block->set_value("request", "GET /t");
+    }
+    if (!$block->no_error_log && !$block->error_log) {
+        $block->set_value("no_error_log", "[error]\n[alert]");
+    }
+});
+
+run_tests;
+
+__DATA__
+
+=== TEST 1: checking iam schema
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.aws-lambda")
+            local ok, err = plugin.check_schema({
+                function_uri = "https://api.amazonaws.com";,
+                authorization = {
+                    iam = {
+                        accesskey = "key1",
+                        secretkey = "key2"
+                    }
+                }
+            })
+            if not ok then
+                ngx.say(err)
+            else
+                ngx.say("done")
+            end
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 2: missing fields in iam schema
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.aws-lambda")
+            local ok, err = plugin.check_schema({
+                function_uri = "https://api.amazonaws.com";,
+                authorization = {
+                    iam = {
+                        secretkey = "key2"
+                    }
+                }
+            })
+            if not ok then
+                ngx.say(err)
+            else
+                ngx.say("done")
+            end
+        }
+    }
+--- response_body
+property "authorization" validation failed: property "iam" validation failed: 
property "accesskey" is required
+
+
+
+=== TEST 3: create route with aws plugin enabled
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+
+            local code, body = t('/apisix/admin/routes/1',
+                 ngx.HTTP_PUT,
+                 [[{
+                        "plugins": {
+                            "aws-lambda": {
+                                "function_uri": 
"http://localhost:8765/httptrigger";,
+                                "authorization": {
+                                    "apikey" : "testkey"
+                                }
+                            }
+                        },
+                        "uri": "/aws"
+                }]],
+                [[{
+                    "node": {
+                        "value": {
+                            "plugins": {
+                                "aws-lambda": {
+                                    "keepalive": true,
+                                    "timeout": 3000,
+                                    "ssl_verify": true,
+                                    "keepalive_timeout": 60000,
+                                    "keepalive_pool": 5,
+                                    "function_uri": 
"http://localhost:8765/httptrigger";,
+                                    "authorization": {
+                                        "apikey": "testkey"
+                                    }
+                                }
+                            },
+                            "uri": "/aws"
+                        },
+                        "key": "/apisix/routes/1"
+                    },
+                    "action": "set"
+                }]]
+                )
+
+            if code >= 300 then
+                ngx.status = code
+                ngx.say("fail")
+                return
+            end
+
+            ngx.say(body)
+        }
+    }
+--- response_body
+passed
+
+
+
+=== TEST 4: Test plugin endpoint
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local core = require("apisix.core")
+
+            local code, _, body, headers = t("/aws", "GET")
+             if code >= 300 then
+                ngx.status = code
+                ngx.say(body)
+                return
+            end
+
+            -- headers proxied 2 times -- one by plugin, another by this test 
case
+            core.response.set_header(headers)
+            ngx.print(body)
+        }
+    }
+--- response_body
+aws lambda invoked
+--- response_headers
+Content-Length: 19

Review comment:
       why check the `Content-Length` 

##########
File path: t/plugin/aws-lambda.t
##########
@@ -0,0 +1,299 @@
+#
+# 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.
+#
+use t::APISIX 'no_plan';
+
+repeat_each(1);
+no_long_string();
+no_root_location();
+no_shuffle();
+
+add_block_preprocessor(sub {
+    my ($block) = @_;
+
+    my $inside_lua_block = $block->inside_lua_block // "";
+    chomp($inside_lua_block);
+    my $http_config = $block->http_config // <<_EOC_;
+
+    server {
+        listen 8765;
+
+        location /httptrigger {
+            content_by_lua_block {
+                ngx.req.read_body()
+                local msg = "aws lambda invoked"
+                ngx.header['Content-Length'] = #msg + 1
+                ngx.header['Connection'] = "Keep-Alive"
+                ngx.say(msg)
+            }
+        }
+
+        location /generic {
+            content_by_lua_block {
+                $inside_lua_block
+            }
+        }
+    }
+_EOC_
+
+    $block->set_value("http_config", $http_config);
+
+    if (!$block->request) {
+        $block->set_value("request", "GET /t");
+    }
+    if (!$block->no_error_log && !$block->error_log) {
+        $block->set_value("no_error_log", "[error]\n[alert]");
+    }
+});
+
+run_tests;
+
+__DATA__
+
+=== TEST 1: checking iam schema
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.aws-lambda")
+            local ok, err = plugin.check_schema({
+                function_uri = "https://api.amazonaws.com";,
+                authorization = {
+                    iam = {
+                        accesskey = "key1",
+                        secretkey = "key2"
+                    }
+                }
+            })
+            if not ok then
+                ngx.say(err)
+            else
+                ngx.say("done")
+            end
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 2: missing fields in iam schema
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.aws-lambda")
+            local ok, err = plugin.check_schema({
+                function_uri = "https://api.amazonaws.com";,
+                authorization = {
+                    iam = {
+                        secretkey = "key2"
+                    }
+                }
+            })
+            if not ok then
+                ngx.say(err)
+            else
+                ngx.say("done")
+            end
+        }
+    }
+--- response_body
+property "authorization" validation failed: property "iam" validation failed: 
property "accesskey" is required
+
+
+
+=== TEST 3: create route with aws plugin enabled
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+
+            local code, body = t('/apisix/admin/routes/1',
+                 ngx.HTTP_PUT,
+                 [[{
+                        "plugins": {
+                            "aws-lambda": {
+                                "function_uri": 
"http://localhost:8765/httptrigger";,
+                                "authorization": {
+                                    "apikey" : "testkey"
+                                }
+                            }
+                        },
+                        "uri": "/aws"
+                }]],
+                [[{
+                    "node": {
+                        "value": {
+                            "plugins": {
+                                "aws-lambda": {
+                                    "keepalive": true,
+                                    "timeout": 3000,
+                                    "ssl_verify": true,
+                                    "keepalive_timeout": 60000,
+                                    "keepalive_pool": 5,
+                                    "function_uri": 
"http://localhost:8765/httptrigger";,
+                                    "authorization": {
+                                        "apikey": "testkey"
+                                    }
+                                }
+                            },
+                            "uri": "/aws"
+                        },
+                        "key": "/apisix/routes/1"
+                    },
+                    "action": "set"
+                }]]
+                )
+
+            if code >= 300 then
+                ngx.status = code
+                ngx.say("fail")
+                return
+            end
+
+            ngx.say(body)
+        }
+    }
+--- response_body
+passed
+
+
+
+=== TEST 4: Test plugin endpoint

Review comment:
       ```suggestion
   === TEST 4: test plugin endpoint
   ```




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscr...@apisix.apache.org

For queries about this service, please contact Infrastructure at:
us...@infra.apache.org


Reply via email to