Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(plugins): aws lambda serverless #5594

Merged
merged 22 commits into from
Dec 1, 2021

Conversation

bisakhmondal
Copy link
Member

@bisakhmondal bisakhmondal commented Nov 23, 2021

What this PR does / why we need it:

create a route

With API key-based authorization

{
    "plugins": {
        "aws-lambda": {
            "function_uri": "https://x9w6z07gb9.execute-api.us-east-1.amazonaws.com/default/test-apisix",
            "authorization": {
                "apikey": "<apikey>"
            },
            "ssl_verify":false
        }
    },
    "uri": "/aws"
}

With IAM v4 request signing

{
    "plugins": {
        "aws-lambda": {
            "function_uri": "https://x9w6z07gb9.execute-api.us-east-1.amazonaws.com/default/test-apisix",
            "authorization": {
                "iam": {
                     "accesskey": <accesskey>,
                     "secretkey": <secretkey>,
                     "aws_region": "us-east-1",
                     "service": "execute-api"
                 }
            },
            "ssl_verify":false
        }
    },
    "uri": "/aws"
}
curl -i -XGET localhost:9080/aws\?name=bisakh
HTTP/1.1 200 OK
Content-Type: application/json
Connection: keep-alive
X-Amzn-Trace-Id: Root=1-619d3cef-5a2070dc05d8cf4f37eb9877;Sampled=0
x-amzn-RequestId: d74198ab-1cfd-4eb3-b4e5-a81095ddb6ba
Date: Tue, 23 Nov 2021 19:11:43 GMT
Content-Length: 16
x-amz-apigw-id: JRZ1aHaHoAMFZmw=
Server: APISIX/2.10.2

"Hello, bisakh!"

Pre-submission checklist:

  • Did you explain what problem does this PR solve? Or what new features have been added?
  • Have you added corresponding test cases?
  • Have you modified the corresponding document?
  • Is this PR backward compatible? If it is not backward compatible, please discuss on the mailing list first

@bisakhmondal bisakhmondal changed the title feat(plugins): aws lambda serverless with generic upstream refactoring feat(plugins): aws lambda serverless Nov 25, 2021
@bisakhmondal bisakhmondal marked this pull request as ready for review November 25, 2021 22:23
@bisakhmondal
Copy link
Member Author

depends on: #5616
Need to be rebased once #5616 merges into master
cc @spacewander for review


local schema = {
local azure_authz_schema = {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we are developing a new feature "aws lambda serverless", so we should not update azure-functions.lua, it is a different thing.

one for one thing.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree. This plugin depends on the refactoring proposed in #5616. For sake of fast development, I developed the feature on top of the branch (ref). As the changes in 5616 has been merged into master, I have rebased it. Thanks : )

local resty_sha256 = require("resty.sha256")


local plugin_name, plugin_version, priority = "aws-lambda", 0.1, -1899
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bad style

good style:

local plugin_name = "aws-lambda"
local plugin_version = 0.1
local priority = -1899

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, updated

return kSigning
end

local aws_authz_schema = {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we use schema is fine, the file name is aws-lamda

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not the complete schema, rather it's the part of the schema (handling the plugin specific authorization details).

see:

authorization = authz_schema,

service = {
type = "string",
default = "execute-api",
description = "The service that is receiving the request"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The service or the service?

we should use the same style with others.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done


local ngx = ngx
local pairs = pairs
local concat = table.concat
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bad name, I think we should add a prefix tab_

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ack

local ngx = ngx
local pairs = pairs
local concat = table.concat
local sort = table.sort
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

end


return require("apisix.plugins.serverless.generic-upstream")(plugin_name,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it is not easy to read.

another style:

local serverless_obj = require("apisix.plugins.serverless.generic-upstream")
local aws_obj = serverless_obj.new({
    name = plugin_name,
    version = plugin_version,
    priority = priority,
    ... ...
})

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice. Updated accordingly

@@ -40,7 +40,7 @@ __DATA__
--- request
GET /apisix/admin/plugins/list
--- response_body_like eval
qr/\["real-ip","client-control","ext-plugin-pre-req","zipkin","request-id","fault-injection","serverless-pre-function","batch-requests","cors","ip-restriction","ua-restriction","referer-restriction","uri-blocker","request-validation","openid-connect","authz-casbin","wolf-rbac","ldap-auth","hmac-auth","basic-auth","jwt-auth","key-auth","consumer-restriction","authz-keycloak","proxy-mirror","proxy-cache","proxy-rewrite","api-breaker","limit-conn","limit-count","limit-req","gzip","server-info","traffic-split","redirect","response-rewrite","grpc-transcode","prometheus","datadog","echo","http-logger","skywalking-logger","google-cloud-logging","sls-logger","tcp-logger","kafka-logger","syslog","udp-logger","example-plugin","azure-functions","openwhisk","serverless-post-function","ext-plugin-post-req"\]/
qr/\["real-ip","client-control","ext-plugin-pre-req","zipkin","request-id","fault-injection","serverless-pre-function","batch-requests","cors","ip-restriction","ua-restriction","referer-restriction","uri-blocker","request-validation","openid-connect","authz-casbin","wolf-rbac","ldap-auth","hmac-auth","basic-auth","jwt-auth","key-auth","consumer-restriction","authz-keycloak","proxy-mirror","proxy-cache","proxy-rewrite","api-breaker","limit-conn","limit-count","limit-req","gzip","server-info","traffic-split","redirect","response-rewrite","grpc-transcode","prometheus","datadog","echo","http-logger","skywalking-logger","google-cloud-logging","sls-logger","tcp-logger","kafka-logger","syslog","udp-logger","example-plugin","aws-lambda","azure-functions","openwhisk","serverless-post-function","ext-plugin-post-req"\]/
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we split this into multiple lines? We can do it in the next PR.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, It's time to modify this to another easy-to-read format.

Copy link
Member Author

@bisakhmondal bisakhmondal Nov 29, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Guys, here it is: #5642

if k ~= "connection" then
signed_headers[#signed_headers+1] = k
-- strip starting and trailing spaces including strip multiple spaces into single space
canonical_headers[k] = v:gsub("^%s+", ""):gsub("%s+$", "")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

headers["X-Amz-Date"] = amzdate

-- computing canonical uri
local canonical_uri = params.path:gsub("//", "/")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

local serverless_obj = require("apisix.plugins.serverless.generic-upstream")

return serverless_obj(plugin_name,
plugin_version,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bad indent

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated

local tab_sort = table.sort
local os = os
local hmac = require("resty.hmac")
local hexencode = require("resty.string").to_hex
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we use underscore style for function name?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ack

return hexencode(digest)
end

local function getSignatureKey(key, datestamp, region, service)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto

"Hello, apisix!"
```

For requests where the mode of communication between the client and the Apache APISIX gateway is HTTP/2, the example looks like ( make sure you are running APISIX agent with `enable_http2: true` for a port in conf.yaml or uncomment port 9081 of `node_listen` field inside [config-default.yaml](../../../../conf/config-default.yaml) ) :
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Better to use config-default.yaml directly, since people may read the doc on the doc's website. Please fix the azure-functions' doc too.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated


## How To Enable

The following is an example of how to enable the aws-lambda faas plugin for a specific route URI. Calling the apisix route uri will make an invocation to the lambda function uri (the new upstream). We are assuming your cloud function is already up and running.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use APISIX route instead of apisix route. Need to fix other similar places.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should use APISIX always

Copy link
Member Author

@bisakhmondal bisakhmondal Nov 29, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay. Got it. Thanks for letting me know : )

@membphis
Copy link
Member

@shuaijinchao @tzssangglass pls review when you have time


## How To Enable

The following is an example of how to enable the aws-lambda faas plugin for a specific route URI. Calling the apisix route uri will make an invocation to the lambda function uri (the new upstream). We are assuming your cloud function is already up and running.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should use APISIX always

@@ -40,7 +40,7 @@ __DATA__
--- request
GET /apisix/admin/plugins/list
--- response_body_like eval
qr/\["real-ip","client-control","ext-plugin-pre-req","zipkin","request-id","fault-injection","serverless-pre-function","batch-requests","cors","ip-restriction","ua-restriction","referer-restriction","uri-blocker","request-validation","openid-connect","authz-casbin","wolf-rbac","ldap-auth","hmac-auth","basic-auth","jwt-auth","key-auth","consumer-restriction","authz-keycloak","proxy-mirror","proxy-cache","proxy-rewrite","api-breaker","limit-conn","limit-count","limit-req","gzip","server-info","traffic-split","redirect","response-rewrite","grpc-transcode","prometheus","datadog","echo","http-logger","skywalking-logger","google-cloud-logging","sls-logger","tcp-logger","kafka-logger","syslog","udp-logger","example-plugin","azure-functions","openwhisk","serverless-post-function","ext-plugin-post-req"\]/
qr/\["real-ip","client-control","ext-plugin-pre-req","zipkin","request-id","fault-injection","serverless-pre-function","batch-requests","cors","ip-restriction","ua-restriction","referer-restriction","uri-blocker","request-validation","openid-connect","authz-casbin","wolf-rbac","ldap-auth","hmac-auth","basic-auth","jwt-auth","key-auth","consumer-restriction","authz-keycloak","proxy-mirror","proxy-cache","proxy-rewrite","api-breaker","limit-conn","limit-count","limit-req","gzip","server-info","traffic-split","redirect","response-rewrite","grpc-transcode","prometheus","datadog","echo","http-logger","skywalking-logger","google-cloud-logging","sls-logger","tcp-logger","kafka-logger","syslog","udp-logger","example-plugin","aws-lambda","azure-functions","openwhisk","serverless-post-function","ext-plugin-post-req"\]/
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, It's time to modify this to another easy-to-read format.

| 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. |
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if the ssl_verify is true, do we need to specify a certificate?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it is generated from any trusted public CA, we don't have to specify the certificate of the CA as along the hierarchy chain eventually it will point to the root CA. But that's not the case with self signed certs. In that case, simply ssl_verify:false will do or there is an option to specify the CA certificate file. see ref below

The optional ssl_verify argument takes a Lua boolean value to control whether to perform SSL verification. When set to true, the server certificate will be verified according to the CA certificates specified by the lua_ssl_trusted_certificate directive. You may also need to adjust the lua_ssl_verify_depth directive to control how deep we should follow along the certificate chain. Also, when the ssl_verify argument is true and the server_name argument is also specified, the latter will be used to validate the server name in the server certificate.

ref2: https://github.com/openresty/lua-nginx-module#lua_ssl_trusted_certificate

docs/en/latest/plugins/aws-lambda.md Outdated Show resolved Hide resolved
docs/en/latest/plugins/aws-lambda.md Outdated Show resolved Hide resolved
docs/en/latest/plugins/aws-lambda.md Outdated Show resolved Hide resolved
docs/en/latest/plugins/aws-lambda.md Outdated Show resolved Hide resolved
t/plugin/aws-lambda.t Outdated Show resolved Hide resolved
Comment on lines +195 to +196
--- response_headers
Content-Length: 19
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why check the Content-Length

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi, thanks for the review.
It just checks if the headers that are being received from upstream lambda are being forwarded by the plugin. Not just Content-Length specifically but all headers from the mock HTTP endpoint

docs/en/latest/plugins/aws-lambda.md Outdated Show resolved Hide resolved
Comment on lines +137 to +138
if k ~= "connection" then
signed_headers[#signed_headers+1] = k
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand here, do we just need to skip connection?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

First of all, I would like to specify, we are not tampering connection header for the request that is being sent to the lambda.
This section performs aws v4 request signing with aws iam secret key. As per the aws docs, to have the authenticity of the request (and to validate that the request has not been modified by any third party over the wire) it suggests generating hash digest from as many headers as we can (however host and x-amz-date is mandatory). In real-world testing what i have found that aws gateway doesn't include the connection header value (keep-alive, close) for hash calculation. That's why I simply omitted it as it never was any hard requirement.
2021-11-30_09-27

@spacewander spacewander merged commit e90e3b7 into apache:master Dec 1, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants