하고 싶은 것
VPN으로 회사 네트워크에 접속해 서버1에 접속하고 서버2에 접속해야 리퀘스트에 성공하는 API-1를 Lambda함수에서 사용하고 싶다.
배경
- API는 ECS로 관리되고 있다.
- 사용하고 싶은 API-1은 API-0을 통해서만 리퀘스트를 보낼 수 있다.
- API-0은 AWS의 LoadBalancer와 VPC를 이용해 구축되어있다.
- API-1도 AWS의 LoadBalancer와 VPC를 이용해 구축되어있다.
- API-1의 LoadBalancer는 API-0의 LoadBalancer에 설정되어있는 SecurityGroups를 통해서만 접속할 수 있다.
- 따라서 두 LoadBalancer의 TargetGroup의 VPC는 동일하다.
- Lambda함수은 API-0을 통해서 API-1을 사용하고 싶지 않다.
VPC, Subnet, SecurityGroups에 대해 알기 쉽게 정리하신 분이 있음
만들면서 배우는 아마존 버추얼 프라이빗 클라우드(Amazon VPC)
방법
- API-1의 LoadBalancer에 API-0의 LoadBalancer와 상관 없는 SecurityGroup을 만들어 InbountRule에 추가한다.
- InboundRule에 추가한 SecurityGroup을 Lambda의 SecurityGroup에 추가한다.
- Lambda의 VPC는 API-1의 ECS에 설정되어있는 VPC를 설정한다.
- Lambda의 subnet은 API-1의 ECS에 설정되어있는 subnet을 전부 설정한다.
CDK로 구현
// lambda-security-group.ts
import * as ec2 from "@aws-cdk/aws-ec2";
import * as cdk from "@aws-cdk/core";
import { toKebabCase } from "../../../../utils/utils";
export function createLambdaSecurityGroup(
stack: cdk.Stack,
vpc: ec2.IVpc
): ec2.ISecurityGroup {
const resourceName = "LambdaSecurityGroup";
const securityGroupName = toKebabCase(resourceName);
const securityGroup = new ec2.SecurityGroup(stack, securityGroupName, {
vpc: vpc,
allowAllOutbound: true,
securityGroupName: securityGroupName,
});
cdk.Tags.of(securityGroup).add("Name", securityGroupName);
return securityGroup;
}
// api-one-load-balancer-security-group.ts
export function createApiOneLoadBalancerSecurityGroup(
stack: cdk.Stack,
prefix: string,
baseName: string,
apiZeroSecurityGroupId: string,
lambdaSecurityGroup: ec2.ISecurityGroup,
vpc: ec2.IVpc
): ec2.ISecurityGroup {
const resourceName = "ApiOneSecurityGroup";
const securityGroupName = toKebabCase(resourceName);
const apiZeroSecurityGroup = ec2.SecurityGroup.fromSecurityGroupId(
stack,
"api-zero-security-group",
apiZeroSecurityGroupId
);
const securityGroup = new ec2.SecurityGroup(stack, securityGroupName, {
vpc: vpc,
allowAllOutbound: true,
securityGroupName: securityGroupName,
});
securityGroup.addIngressRule(apiZeroSecurityGroup, ec2.Port.tcp(80));
securityGroup.addIngressRule(apiZeroSecurityGroup, ec2.Port.tcp(443));
securityGroup.addIngressRule(lambdaSecurityGroup, ec2.Port.tcp(80));
securityGroup.addIngressRule(lambdaSecurityGroup, ec2.Port.tcp(443));
cdk.Tags.of(securityGroup).add("Name", securityGroupName);
return securityGroup;
}
빠질수도 있는 함정
apiZeroSecurityGroup
처럼 lambdaSecurityGroup
의 securityGroupId
를 취득해서 fromSomeServiceId
할 수 있지 않나? 할 수도 있지만 그러면 아래와 같은 에러가 발생한다.
이미 동일 Stack 안에 정의되어있는 리소스는 직접 전해받아 사용해야 한다는 것이다.
There is already a Construct with name 'lambda-security-group' in SomeStack [some-stack]
Subprocess exited with error 1
Lambda에 구현
import requests
def lambda_handler(event, context):
return get('api_url')
def get(self, api_url: str) -> Dict:
response = requests.get(api_url).text
return json.loads(response)
필요한 권한
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"ec2:CreateNetworkInterface",
"ec2:DescribeNetworkInterfaces",
"ec2:DeleteNetworkInterface"
],
"Resource": [
"arn:aws:ec2:{region}:{account_id}:network-interface/{vpc_id}",
"arn:aws:ec2:{region}:{account_id}:network-interface/{ecs_subnet_id_0}",
"arn:aws:ec2:{region}:{account_id}:network-interface/{ecs_subnet_id_1}",
"arn:aws:ec2:{region}:{account_id}:network-interface/{lambda_security_group_id}"
]
}
]
}
fyi
Lambda 레이어에 requests를 추가해도 import할 수 없다는 에러가 나올 수 있는데 Slacker를 대신 레이어로 추가하면 해결된다(hack이라 근본적인 이유를 찾는게 나을 수 있지만 난 아직 대안을 찾지 못했다).