利用Kong的AWS Lambda插件实现无服务器安全的5个步骤

425 阅读10分钟

照片:Kvistholt PhotographyonUnsplash

使用Kong的AWS Lambda插件实现无服务器安全的5个步骤

如何使用Kong的AWS Lambda插件进行lambda调用

[

Alvin Lee

](medium.com/@alvinslee?…)

阿尔文-李

关注

1月24日

- 12分钟阅读

对于不喜欢DevOps的开发者来说,lambdas是天堂。他们可以专注于编写独立的、模块化的代码片断,部署这些功能以按需执行,而不必关心资源管理或基础设施。然而,Lambda的执行可能很棘手。

使用AWS API网关的无服务器安全可能会让人感到畏惧,特别是当你想做的只是调用一个简单的函数作为API端点时。对于这一点,有Kong Gateway的便利性。

这篇文章和视频将介绍如何用AWS Lambda插件设置Kong Gateway。我们的小项目将经历以下步骤。

  1. 在Node.js中建立一个简单的无服务器函数,处理一个请求的头、查询参数和正文,然后用JSON进行响应。
  2. 将我们的无服务器函数部署到AWS Lambda。
  3. 获得调用Lambda所需的凭证。
  4. 配置Kong Gateway以监听特定的本地路径上的请求。
  5. 将AWS Lambda插件添加到Kong中,以便对该路径的请求能够调用我们的Lambda并返回调用结果。

要在旁边走,你需要以下条件。

  1. 一个AWS账户(我们的资源使用可能是免费级别的)。
  2. 对JavaScript和Node.js的基本熟悉程度。
  3. 在你的本地机器上安装AWS CLI。
  4. 在你的本地机器上安装Kong Gateway。按照Kong文档中的入门说明进行操作。

核心概念

AWS Lambda

几年前,AWS Lambda服务是无服务器一词的同义词。它代表了功能即服务(FaaS)的首批主流产品之一。有了AWS Lambda,开发者只需要上传函数的代码。

每当你需要执行该功能时,AWS Lambda就会提供执行该功能所需的资源,然后在无服务器功能执行完成后,再取消提供这些资源。开发人员不再需要关心资源管理问题,因为AWS Lambda在幕后处理了这个问题。AWS Lambda目前支持Node.js、Ruby、Python和其他几种语言。

无服务器安全

在部署无服务器代码时,安全是一个重要的问题,AWS提供了一些安全的最佳实践,请大家牢记。虽然不再有需要保持安全的服务器,但关键的问题是适当地保护无服务器功能代码的调用和更新。

只有那些具有适当授权的人,无论是服务还是人类用户,都应该被允许调用或更新无服务器代码。在AWS领域,这意味着建立适当的IAM策略,并将其附加到与服务相关的角色或个人用户上。对于AWS Lambda来说,需要安全锁定的相关操作包括InvokeFunctionUpdateFunctionCode

Kong Gateway

Kong Gateway是一个轻量级的开源API网关,位于你的上游服务前面。Kong Gateway易于配置和快速部署,它作为一个中央看门人,将所有传入的请求路由到其预定的目的地,无论是上游API服务器、第三方服务、数据库,甚至是部署在AWS Lambda上的功能。

作为你的用户客户端和你的上游服务之间的一个薄层,API网关被无数的Kong插件进一步扩展,以处理认证、授权、流量控制、安全等等。

第一步:编写我们的函数

在我们的小项目中,我们将使用一个为Node.js编写的基本Lambda函数。根据AWS文档中关于示例函数代码的提示,我们编写以下代码。

exports.handler = function(event, context, callback) {    var res ={        "statusCode": 200,        "headers": {            "Content-Type": "*/*"        }    };    var resultBody = {        message: "Lambda invoked successfully."    };    res.body = resultBody;    callback(null, res);};

简而言之,我们的函数以一个状态代码、一个简单的头和一个带有消息的主体来响应。我们将在后面的步骤中扩展这个函数,利用传入请求的头信息、查询字符串参数和正文。不过现在,这已经足够让我们开始行动了。

第2步:将函数部署到AWS Lambda上

接下来,我们把我们的函数代码部署到AWS Lambda上。我们在AWS控制台中导航到Lambda服务,确保我们已经选择了一个区域进行部署。在这个例子的其余部分,我们将使用us-west-2 region 。 点击创建函数。

我们将从头开始编写我们的函数,为我们的函数选择一个名字(my-lambda),并选择一个Node.js运行时间(Node.js 14.x )。

在我们的Lambda创建后,我们导航到代码源,双击index.js 。然后,我们把上面的函数代码粘贴进去。

单击 "部署"。

为了确保我们的函数正常工作,我们可以在控制台中直接测试一个调用。点击Test,然后点击Configure Test Event。使用默认的 "hello world "模板。命名你的测试事件,保留提供的默认主体。点击 "创建"。

现在,选择你的testevent ,点击测试。执行结果显示了我们的函数调用的结果一状态代码、头文件和带消息的主体。

现在我们部署了我们的简单Lambda,我们想远程调用我们的函数。让我们在本地机器上使用命令行。

第3步:获取调用Lambda的凭证

当然,不是任何人都可以调用你的Lambda。我们谈到了无服务器安全的需要,以确保我们已经适当锁定了调用我们的函数和更新函数代码的能力。对于我们现在的例子,我们将把关注点放在函数调用上。

我们需要为有权限调用该Lambda的AWS IAM用户提供凭据。当我们使用AWS CLI尝试在没有任何凭证的情况下调用我们的Lambda函数时,这一点就很清楚了。

$ aws lambda invoke \--region=us-west-2 \--invocation-type=RequestResponse \--function-name=my-lambda \response.json

我们的第一步是创建一个IAM用户。在AWS控制台,导航到IAM服务,然后导航到用户。用你选择的名字创建一个新的IAM用户,并给予该用户程序性访问权限。我们将我们的用户称为kong-lambda-invoker

逐步完成权限、标签和审查,使用默认值。我们将创建没有权限的用户来开始,然后从那里开始迭代。点击 "创建用户"。现在,你有一个 "访问密钥ID "和一个 "秘密访问密钥 "给你的新用户。

回到命令行,让我们来设置这些凭证。我们把IAM用户创建结果中的值粘贴进去,我们还指定了我们的地区(us-west-2 )和我们的默认输出格式(json )。

$ aws configureAWS Access Key ID [None]: AKIA5***************AWS Secret Access Key [None]: hg8o***********************************Default region name [None]: us-west-2Default output format [json]: json

现在我们已经配置了我们的AWS凭证,我们再一次尝试从命令行调用我们的函数。

$ aws lambda invoke \--region=us-west-2 \--invocation-type=RequestResponse \--function-name=my-lambda \response.json

有所进展我们仍然无法调用我们的函数,但这次的原因是不同的。我们的用户,kong-lambda-invoker ,没有调用我们函数的权限。让我们来提供这些权限。回到AWS IAM,我们选择我们的用户并点击添加权限。

接下来,直接点击附加现有策略,然后点击创建策略。在策略编辑器中,点击JSON并粘贴以下代码。确保用你的lambda部署的区域替换REGION (us-west-2)。另外,用你的AWS账户ID替换AWS-ID

{    "Version": "2012-10-17",    "Statement": [        {            "Sid": "VisualEditor0",            "Effect": "Allow",            "Action": "lambda:InvokeFunction",            "Resource": "arn:aws:lambda:REGION:AWS-ID:function:my-lambda"        }    ]}

点击其余的策略创建部分,接受所有的默认值。给策略取一个名字(例如,Invoke-my-lambda )。现在我们创建了lambda调用策略,我们将回到我们的IAM用户,为kong-lambda-invoker 用户添加权限。

我们直接附加现有策略,然后找到我们刚刚创建的策略。选定策略后,我们点击剩下的步骤,为我们的用户附加权限。

有了一个用户和一个策略,我们就可以回到我们的命令行,再次调用这个功能。AWS可能需要一两分钟的时间来对你的用户进行权限修改。然而,不久之后,这是我们的结果。

$ aws lambda invoke \--region=us-west-2 \--invocation-type=RequestResponse \--function-name=my-lambda \response.json{    "StatusCode": 200,    "ExecutedVersion": "$LATEST"}

它成功了!我们看一下输出文件,response.json ,看到我们的200状态和初始主体,并有一条信息。随着我们的Lambda函数的部署和我们的凭证的获得,现在是时候配置Kong了。

第4步:配置开源Kong网关

根据你的无服务器环境,将Kong安装到本地机器的步骤会有所不同。一旦安装完成,我们有几个配置步骤需要遵循。

设置声明式配置

为了使用AWS Lambda插件,我们可以在无数据库模式下运行Kong,也就是说,我们只需要写一个声明性的配置文件,在启动Kong时系统就会读取。首先,我们将创建一个项目文件夹并生成模板配置文件。

~$ mkdir project~$ cd project~/project$ kong config init~/project$ tree.└── kong.yml

接下来,我们将配置Kong的启动行为,以使用该kong.yml ,作为其配置。要做到这一点,我们需要复制Kong's kong.conf.default模板文件,将副本命名为kong.conf ,然后编辑它。

~/project$ cd /etc/kong/etc/kong$ sudo cp kong.conf.default kong.conf/etc/kong$ tree.├── kong.conf├── kong.conf.default└── kong.logrotate

现在,让我们编辑一下kong.conf,做一些修改。

#PATH: /etc/kong/kong.conf

有了这两处改动,Kong在启动时就知道在无数据库模式下工作,寻找我们所指定的声明性配置文件。把我们的注意力转回到声明性配置文件,让我们为Kong设置一个上游服务和一个路由。把下面的代码作为kong.yml 的全部内容粘贴进去。

_format_version: "2.1"_transform: trueservices:  - name: my-service    url: https://httpstat.us/200routes:  - name: my-lambda-route    service: my-service    paths:      - /lambda

上面,我们添加了一个上游服务(称为my-service )。这个上游服务只是指向一个用于HTTP状态码测试的网站,它将返回一个200 。 然后,我们创建一个路由(称为my-lambda-route),监听/lambda 路径上的请求,将这些请求转发给我们的上游服务。

启动Kong

有了我们的声明性配置文件,我们启动Kong。

~/project$ sudo kong start

用Curl请求进行测试

我们可以使用curl 来发送一个请求,看看Kong是否正确地监听了路径,并成功地将请求转发到上游服务。

~/project$ curl http://localhost:8000/lambda200 OK

很好。Kong已经启动并运行了。接下来,我们将对我们的kong.yml 文件做一些修改,加入AWS Lambda插件。

第5步:向Kong添加AWS Lambda插件

在其最简单的形式中,AWS Lambda插件可以在路由上启用,完全消除了路由与上游服务的关联。Kong监听该路由路径上的请求,然后用该请求的参数调用远程AWS Lambda函数。这就是我们在这里要采取的方法。

把你的kong.yml 的内容替换成以下内容。请确保使用你的IAM用户的访问密钥ID和秘密访问密钥。

_format_version: "2.1"_transform: trueroutes:  - name: my-lambda-route    paths:      - /lambdaplugins:  - name: aws-lambda    route: my-lambda-route    config:      aws_key: REPLACE-WITH-ACCESS-KEY-ID      aws_secret: REPLACE-WITH-SECRET-ACCESS-KEY      aws_region: us-west-2      function_name: my-lambda      forward_request_body: true      forward_request_headers: true      forward_request_uri: true

你会注意到,我们已经删除了我们的上游服务。我们的路由不再与服务相关联,因为请求将转到Lambda调用。我们添加了一个插件部分,其中有我们的aws-lambda plugin 。注意,aws-lambda 不是一个任意的名字,而是特指捆绑在Kong中的AWS Lambda插件。

我们可以将我们的插件与我们的路由(my-lambda-route)和我们在AWS部署的Lambda函数联系起来。我们包括我们IAM用户的凭证、部署区域和函数名称。接下来,我们配置该插件,将请求的正文、头信息和URI转发到Lambda调用,在修改函数代码后不久,我们将在函数调用的event 对象中看到这些信息。不过,首先我们要重启Kong,检查我们的Lambda是否被正确调用。

~/project$ sudo kong restart

Kong已经启动并运行了,如期调用了我们的Lambda。现在,让我们更新我们的Lambda函数代码以访问请求头、查询字符串参数和正文。回到AWS控制台的my-lambda ,将index.js 中的代码替换为以下内容。

exports.handler = function(event, context, callback) {    var res ={        "statusCode": 200,        "headers": {            "Content-Type": "*/*"        }    };    res.body = {      incoming_headers: event.request_headers,      incoming_query_parameters: event.request_uri_args,      incoming_body: event.request_body    };    callback(null, res);};

单击 "部署 "以部署更新的代码。我们将使用Insomnia来发送请求并检查结果。

首先,我们向我们的Kong代理和路径发出一个基本请求,没有参数或正文。我们设置一些我们自己的附加头信息(包括test-header1 ,值为test-value1 )。我们将请求发送到http://localhost:8000/lambda ,并收到以下响应。

响应体显示incoming_headers ,这是Insomnia客户端发送给我们的Kong代理的原始头信息,现在被转发到Lambda调用中。这些包括我们在Insomnia中明确设置的值。

接下来,我们发送另一个请求,这次是在我们的请求URL上添加查询字符串参数。

结果显示我们的查询字符串参数在响应体的incoming_query_parameters 关键中被回传给我们。

最后,我们发送一个POST 请求,并附加一个JSON主体,验证我们的Lambda函数是否收到主体数据。

在这个最后的请求中,我们看到incoming_headersincoming_query_parametersincoming_body ,都被正确填充。

我们的Lambda函数工作了,我们的Kong插件调用我们的Lambda函数也工作了。这为我们打开了无数可能性和使用模式的大门。

其他使用案例

使用 invocation_type=Event 的异步调用

invocation_type 该插件默认将我们的Lambda调用的RequestResponse 。这是一个同步调用,等待函数执行完成并返回一个结果。AWS Lambda函数的另一种常见的调用模式是异步调用,它启动或触发一个进程,而不等待响应。为此,你可以在插件中配置invocation_type=Event

使用Kong的AWS Lambda插件,无服务器安全变得更简单

在完成了这篇攻略之后,你可以通过Kong的AWS Lambda插件调用你自己的Lambda函数。你的用例可能要求将该插件与其他插件一起使用,以实现强大的请求处理,或者你可以将该路由和插件与其他路由和Kong管理的上游服务一起使用。

Kong的内置插件消除了Lambda无服务器函数执行的复杂性。开发人员只需从部署一个函数开始。然后,为了确保他们遵循无服务器安全的最佳实践,他们设置了一个IAM用户(或与服务相关的角色),拥有适当的权限来调用该函数并更新其代码。

有了无服务器安全,他们再写几行配置,告诉Kong使用插件来调用该功能。使用AWS Lambda的简单性--它使开发人员在编写和部署模块化代码时愉快地无忧无虑--刚刚升了级。

[本文最初发表于此] 。