概述
樓主最近做了一個項目,需要Lambda去連接一個DynamoDB,難度在于Lambda和DynamoDB分別在兩個AWS賬戶中,同時Lambda處于也要連接RDS的需要,跟RDS也放在同一個VPC中,這無異于再增加了一層難度,也就是說Lambda的traffic需要通過VPC Endpoint先出VPC才能連接DynamoDB。與此同時還有權限問題和身份問題,Lambda需要assume role的權限來操控另一個賬戶的DynamoDB, 樓主將一步一步來解析這些是如何實現。
建立權限
首先需要給Lambda Function建立一個單獨的身份(role), 除了給到你Lambda本身需要的那這些基本權限諸如RDS/CloudWatch的讀寫權限以外,還需要增加兩個規則,一個是AWS提供的VPC讀寫權限,一個是自定義的AssumeRole Police在Lambda所在的賬戶中(這里假定是A賬戶),因為Lambda本身是沒有權限去讀寫在另一個賬戶的任何資源的,只能通過assume role到DynamoDB所在的AWS賬戶中(這里假定是B賬戶)的DynamoDB所綁定的角色來進行讀寫操作.
以下是賬戶A中Lambda的身份(role)中擁有的VPC權限和Assume Role權限, 注意Resource指定的Arn號碼對應的是B賬戶中的賬戶B中DynamoDB所依附的身份(role):
以下是賬戶B中DynamoDB所依附的身份(role)中所依附的權限規則, 包含了要顯式信任的賬戶A的Lambda身份資源號碼(ARN):
以下是賬戶B中DynamoDB所依附的身份(role)中所依附的權限規則, 包含了對應DynamoDB表的讀寫權限:
到這一步在IAM中創建權限和身份的工作就完成了,下一步就是進行VPC網絡配置。
VPC網絡配置
這個章節講述如何將Lambda的網絡請求和數據訪問到DynamoDB。需要注意的是我設置Lambda是放在VPC之中,默認只能訪問到所在VPC里面的資源,比如同處于一個VPC的RDS. 默認配置下AssumeRole和Lambda對DynamoDB的讀寫請求都無法通過VPC到達公網。這里就要用到兩個新的網管組件,一個是NAT(network address translation)和VPC Endpoint, NAT可以講VPC內網請求連接到公網,VPC Endpoint則是專門給DynamoDB提供網絡請求的數據通道,這樣DynamoDB的讀寫請求和數據交互就不用通過公網了,保證了數據的安全。
1.創建VPC
在AWS VPC服務中創建VPC, 并且給Lambda指定這個剛剛創建的VPC.
2.創建子網Subnet
VPC是由子網作為基本單元所組成的,我們需要最少要創建三個子網subnet,一個子網subnet-a給IGW(internet gateway)用于連接公網所用,另外兩個(subnet-b和subnet-c)給Lambda用。創建subnet的時候,CIDR可以用下圖的組合:
將創建好的subnet-b和subnet-c綁定到Lambda
3.創建igw公網網關
創建internet gateway,并且綁定到我們一開始創建的VPC上即可。
4.創建NAT Gateway
子網subnet選擇subnet-a,就是那個公共子網(public subnet), 下面彈性IP ID選擇創建新的EIP按鈕即可。
5.創建路由表route table
創建兩個新的路由表(route table),為了理解方便,分別叫route-table-a和route-table-b,?
route-table-a作為默認主要路由表,默認所有外網請求通過internet gateway網關來請求VPC外的資源。
route-table-b則是這個項目最為精華的部分了,如下圖配置,0.0.0.0/0地址規則表明默認流量是去到NAT的,NAT會又會自動把流量導到Internet gateway網關,這個規則主要是為了assume role的流量服務的,倒數第二行的規則在創建目的地的時候下拉選項里面會自動彈出來,選擇我么剛剛創建的VPC endpoint對應的目的地,這樣Lambda看到要去到dynamoDB的流量請求就會走VPC endpoint。
同時將這個路由表route-table-b跟我們之前創建的兩個私有子網subnet進行關聯.
6.創建安全組Security Group
并且把security group綁定到Lambda.
至此,所有網絡設置都完成了,下面將講解如何在代碼中實現連接。
代碼
const assumeRoleOnDynamoDB = function (results, sts = new AWS.STS({apiVersion: '2012-10-17'})) {
? ? const params = {
? ? ? ? RoleArn: process.env.AssumedDDBRole,
? ? ? ? RoleSessionName: process.env.RoleSessionName
? ? };
? ? return new Promise((resolve, reject) => {
? ? ? ? sts.assumeRole(params, function (err, stsEncryptedData) {
? ? ? ? ? ? if (err) {
? ? ? ? ? ? ? ? console.error("[ERROR] Unable to assume role on Freelunch DynamoDB. ");
? ? ? ? ? ? ? ? return reject(err);
? ? ? ? ? ? }
? ? ? ? ? ? return resolve([results, stsEncryptedData]);
? ? ? ? });
? ? });
};
const upsertDynamoDBTable = function (results, stsEncryptedData) {
? ? const dynamodb = new AWS.DynamoDB({
? ? ? ? apiVersion: '2012-08-10',
? ? ? ? accessKeyId: stsEncryptedData.Credentials.AccessKeyId,
? ? ? ? secretAccessKey: stsEncryptedData.Credentials.SecretAccessKey,
? ? ? ? sessionToken: stsEncryptedData.Credentials.SessionToken,
? ? ? ? region: process.env.REGION
? ? });
? ? const paramsForDDB = {
? ? ? ? TableName: 'TableName',
? ? ? ? Item: { 'data': { "S": "data" }}
? ? };
? ? return new Promise((resolve, reject) => {
? ? ? ? dynamodb.putItem(paramsForDDB, function (err, data) {
? ? ? ? ? ? if (err) {
? ? ? ? ? ? ? ? console.error("[ERROR]: Failed to upsert info into DynamoDB. ");
? ? ? ? ? ? ? ? return reject(err);
? ? ? ? ? ? }
? ? ? ? ? ? resolve([results, dynamodb])
? ? ? ? });
? ? });
};