AWS 기술 블로그

AWS Lambda 함수 URL을 이용하여 편리하고 안전한 API 서버와 클라이언트 만들기

AWS Lambda 함수 URL 소개

AWS의 대표적인 Serverless 서비스인 AWS Lambda는 인프라에 대한 고민없이 개발에만 집중할 수 있는 환경을 제공하여 편리하게 사용할 수 있습니다. 또한, Concurrency에 기반한 오토 스케일링을 통해 부하의 변동에 쉽게 대응할 수 있으며, 서비스를 사용하지 않을 경우에는 비용이 발생하지 않아 경제적입니다.

아래 그림에서는 Lambda 함수를 이용하여 Amazon DynamoDB를 조회하는 매우 간단하지만 일반적으로 사용되는 API 구현 방법을 보여주고 있습니다. Amazon API Gateway는 다양한 인증과 편리한 기능을 제공하여 실제적으로 많이 사용되지만, 지금까지 Lambda 함수를 Endpoint로 직접 호출 할 수 없었기 때문에, 아래처럼 하나 또는 소수의 API를 간단히 구현하여 사용하는 경우에도 API Gateway를 Endpoint로 사용해야 했습니다.

AWS Lambda 함수 URL이 2022년 4월에 공식적으로 지원됨에 따라, API Gateway없이 Lambda를 HTTPS Endpoint로 사용할 수 있게 되었습니다. 아래 그림은 Lambda 함수 URL을 통해 DynamoDB를 조회하는 아키텍처를 보여줍니다. API Gateway 없이 Lambda 함수로 직접 접속할 수 있으므로, 간단하고 편리하게 API를 제공할 수 있으며, 비용과 지연 시간을 줄일 수 있습니다.

AWS Management Console에서 Lambda 함수 URL 생성하기

Lambda 함수 URL은 Lambda 함수 콘솔에서 [Advanced settings -> Enable function URL]을 이용하거나, 아래 그림과 같이 Lambda 콘솔에서 [Configuration -> Function URL -> Create function URL]로 Lambda 함수 URL을 생성할 수 있습니다.

Lambda 함수 URL을 Enable한 후, 아래와 같이 https endpoint로 사용할 수 있는 URL을 확인할 수 있습니다.

Lambda 함수 URL의 Endpoint는 아래와 같은 포맷이며, IPv4와 IPv6 모두에서 https를 지원하고, Cross-Origin Resource Sharing (CORS)도 지원하고 있습니다.

https://<url-id>.lambda-url.<region>.on.aws

AWS CDK를 이용한 Lambda 함수 URL 생성

클라우드 인프라를 효율적으로 관리하기 위해서는 IaC(Infrastructure as Code)를 활용하는 것이 좋습니다. AWS CDK는 Typescript, Node.JS, Python, Go와 같은 다양한 개발 언어를 이용하여 인프라를 코드(IaC)로 정의할 수 있는 오픈 소스 소프트웨어 개발 프레임워크입니다. AWS CDK를 사용하기 위해서는, 아래와 같이 AWS CDK를 설치하고, bootstrapping을 수행합니다.

npm install -g aws-cdk

AWS CDK개발 환경이 구성되면, 아래와 같이 Typescript를 사용할 수 있도록 프로젝트를 생성합니다.

cdk init app --language typescript

이제 아래와 같이 AWS CDK를 이용하여 간단하게 DynamoDB 테이블과 Lambda 함수 URL을 정의할 수 있습니다.

import * as cdk from 'aws-cdk-lib';
import * as lambda from 'aws-cdk-lib/aws-lambda';
import * as dynamodb from "aws-cdk-lib/aws-dynamodb";
import * as iam from "aws-cdk-lib/aws-iam";

export class CdkLambdaStack extends Stack {
  constructor(scope: Construct, id: string, props?: StackProps) {
    super(scope, id, props);

    const tableName = 'dynamo-table';

    // create DynamoDB
    const dataTable = new dynamodb.Table(this, 'dynamodb', {
      tableName: tableName,
        partitionKey: { name: 'user-id', type: dynamodb.AttributeType.STRING },
        sortKey: { name: 'name', type: dynamodb.AttributeType.STRING },
        billingMode: dynamodb.BillingMode.PAY_PER_REQUEST,
        removalPolicy: cdk.RemovalPolicy.DESTROY,
    });
    
    // create Lambda 
    const lambdaFunctionUrl = new lambda.Function(this, "LambdaFunctionUrl", {
      description: 'lambda function url',
      runtime: lambda.Runtime.NODEJS_14_X, 
      code: lambda.Code.fromAsset("../lambda-function-url"), 
      handler: "index.handler", 
      timeout: cdk.Duration.seconds(3),
      environment: {
        tableName: tableName,
      }
    });

아래와 같이 grantReadWriteData를 이용하여 Lambda 함수가 DynamoDB 테이블을 읽기/쓰기 할 수 있도록 권한을 부여합니다. 이후, addFunctionUrl을 이용하여, Lambda 함수 URL을 정의하는데, 여기서는 Lambda 함수 URL의 인증방식으로 IAM (Identity and Access Management) 방식을 사용하며, 아래와 같이 “authType”을 지정합니다. 또한 Lambda 함수 URL이 Resource-based policies를 사용하기 위해 fnUrlRole을 정의합니다.

좀 더 자세한 AWS CDK를 이용한 Lambda 함수 URL 생성은 Typescript 예제를 참조할 수 있습니다.

    // DynamoDB permission
    dataTable.grantReadWriteData(lambdaFunctionUrl);

    // define funtional function url
    const fnUrl = lambdaFunctionUrl.addFunctionUrl({
      authType: lambda.FunctionUrlAuthType.AWS_IAM 
    });

    // define the role of function url
    const fnUrlRole = new iam.Role(this, 'fnUrlRole', {
      assumedBy: new iam.AccountPrincipal(cdk.Stack.of(this).account),
      description: 'Role for lambda function url',
    });    

    // apply the defined role
    fnUrl.grantInvokeUrl(fnUrlRole);

    // check the address of lambda funtion url
    new cdk.CfnOutput(this, 'EndpointUrl', {
      value: fnUrl.url,
      description: 'The endpoint of Lambda Function URL',
    });
  }
}

위와 같이 Lambda 함수 URL과 DynamoDB 테이블을 선언한 후, 아래 CDK 명령어를 실행하여 인프라를 손쉽게 생성할 수 있습니다.

cdk deploy

인프라 생성 및 배포가 끝나면, 아래와 같이 Lambda 함수의 Endpoint와 fnUrlRole의 ARN(Amazon Resource Number)을 cdk.CfnOutput에서 확인할 수 있습니다.

Outputs:
CdkLambdaStack.EndpointUrl = https://atkqt4btjeqnh3sarsdd5rhklm0mftdy.lambda-url.ap-northeast-2.on.aws/
CdkLambdaStack.fnUrlRoleArn = arn:aws:iam::123456789012:role/CdkLambdaStack-fnUrlRoleF3FB2EB9-1GN82O6QTTIND

Lambda 함수

이 예제에서 AWS CDK로 인프라를 배포하면, Lambda 함수 URL과 DynamoDB Table이 자동으로 만들어지고, Lambda함수 코드도 함께 배포됩니다. Lambda 함수는 HTTPS POST로 JSON 데이터가 들어오면, DynamoDB에 저장하고, HTTPS GET으로 조회하는 요청이 들어오면, DynamoDB 정보를 JSON 형태로 전달합니다.

Lambda 함수 URL 보안

Lambda 함수 URL은 아래와 같이 AWS IAM 인증방식만을 제공하므로, 외부 접속을 제한하기 위해서는 IAM을 사용하여야 합니다.

Access keys은  Access Key ID와 Secret Access Key로 구성되는데, 외부에 공개되지 않도록 세심한 주의가 필요합니다. 따라서, Client에서 Access keys을 직접 사용하기 보다는 Temporary security credential을 생성하여 사용하는 것이 바람직합니다. Temporary security credential의 만료시간은  최소 15분에서 최대 36시간까지 설정할 수 있으며, 기본값은 12시간입니다. 시간이 만료되면 더 이상 사용할 수 없습니다.

Temporary security credential 은 STS(Security Token Service)을 통해 획득하는데, Resource-based policies를 따르고 있습니다.

Temporary Security Credential로 Lambda 함수 URL을 호출하는 Client 만들기

Temporary security credential 을 이용하여 Lambda 함수 URL에 접속하기 위해서는 Client가 직접 Temporary security credential을 생성하고 request를 보낼 수 있어야 합니다. 이것은 Signing AWS requests with Signature Version 4을 이용하여 Crypto로 직접 인증을 수행하거나, AWS SDK를 통해 구현이 가능합니다. 아래에서는 AWS SDK를 이용해서 Temporary security credential 을 생성하고, 이를 이용하여 요청을 signing하는 과정을 설명합니다.

AWS CDK로 인프라를 생성할 때에 Lambda 함수 URL의 Role(여기서는 fnUrlRole)을 이미 생성하였고, Client는 해당 Role을 이용하여, STS를 통해 Temporary security credential을 생성합니다.

AWS CDK로 인프라 생성시 확인된 Lambda 함수 URL의 Endpoint 주소와 fnUrlRole의 ARN을 아래와 같이 업데이트 합니다. 이때, domain의 경우에 Endpoint 주소에서 “https://”를 제외한 domain만을 입력합니다.

const domain = 'atkqt4btjeqnh3sarsdd5rhklm0mftdy.lambda-url.ap-northeast-2.on.aws';
const roleArn = 'arn:aws:iam::123456789012:role/CdkLambdaStack-fnUrlRoleF3FB2EB9-1GN82O6QTTIND';

이제 아래와 같이 AWS STS를 접속하여 Temporary security credential을 가져온 후에 AWS config에 업데이트를 합니다.

const params = {
    RoleArn: roleArn,
    RoleSessionName: 'session',
};
const assumeRoleCommand = new AssumeRoleCommand(params);
    
let data;
try {
    data = await sTS.send(assumeRoleCommand);
    
    console.log('data: %j',data);
} catch (error) {
    console.log(error);
}
    
aws.config.credentials.accessKeyId = data.Credentials.AccessKeyId;
aws.config.credentials.secretAccessKey = data.Credentials.SecretAccessKey;
aws.config.credentials.sessionToken = data.Credentials.SessionToken;
console.log("credentials: %j", aws.config.credentials);

이제 HTTP request를 Signing 한 후, request에 대한 signature를 구합니다. 이후 아래와 같이 request를 Lambda 함수 URL의 Endpoint로 보내면 IAM을 이용하여 API 서버를 호출 할 수 있습니다.

var myService = 'lambda';
var myMethod = 'GET';
var myPath = '/';
var body = '';
    
// Create the HTTP request
var request = new HttpRequest({
    headers: {
        'host': domain
    },
    hostname: domain,
    method: myMethod,
    path: myPath,
    body: body,
});
console.log('request: %j', request);

// Sign the request
var signer = new SignatureV4({
    credentials: aws.config.credentials,
    region: region,
    service: myService,
    sha256: Sha256
});
console.log('signer: %j', signer);
    
var signedRequest;
try {
    signedRequest = await signer.sign(request);
    console.log('signedRequest: %j', signedRequest);
} catch(err) {
    console.log(err);
}
    
// request
var options = {
    host: domain,
    port: 443,
        path: myPath,
        method: myMethod,
    headers: signedRequest.headers
};
    
var req = https.request(options, function(res) {
    res.setEncoding('utf-8');
    
    var responseString = '';
    
    res.on('data', function(data) {
        responseString += data;
    });
    
    res.on('end', function() {
        console.log(responseString);            
    });
});
    
req.write(signedRequest.body);
req.end();

HTTPS POSTGET에서는 Node.JS로 HTTP 요청을 실행하는 예제를 보여주고 있습니다. Node.js 시작하기를 참조하여 AWS SDK를 설치하고, 아래와 같이 실행하면, 배포된 Lambda 함수 URL을 통해 API Gateway없이 직접 Lambda 함수를 호출할 수 있습니다.

node client-post.js 
node client-get.js 

리소스 정리하기

AWS CDK로 생성된 인프라를 삭제할 때에는 아래와 같이 CDK 명령어로 쉽게 삭제 할 수 있습니다.

cdk destroy

결론

지금까지 Lambda 함수 URL에 대해 설명하고, AWS Console 또는 AWS CDK를 통해 쉽게 API 서버 인프라를 생성하는 방법과 Temporary security credential을 이용하여 Client에서 안전하게 Lambda 함수 URL을 사용하는 방법에 대해 알아 보았습니다.

Lambda 함수 URL은 API Gateway의 Lambda proxy integration처럼 동작하므로, 클라이언트가 다른 경로(Resource)나 POST/GET 등 다른 method를 사용하더라도 모두 Lambda 함수에서 처리할 수 있습니다. 즉, Lambda 함수 URL을 사용하여 구조를 단순화하고 쉽고 편리하게 API 서버를 생성하고 이용할 수 있습니다. 하지만, Lambda 함수 URL은 Custom URL을 생성할 수 없는 단점도 있으므로, Webhook 핸들러와 같이 Lambda로 간단히 구현하여 쓸 수 있는 경우에 유용하다고 볼 수 있습니다.

지금까지 알아 본 것과 같이, Lambda 함수 URL은 편리한 서버리스 서비스인 Lambda 의 용도를 더욱 확장하여, API 통신이 필요한 사용자의 비즈니스에 유용하게 이용될 수 있습니다.

Kyoung-Su Park

Kyoung-Su Park

박경수 솔루션즈 아키텍트는 다양한 워크로드에 대한 개발 경험을 바탕으로 고객이 최적의 솔루션을 선택하여 비즈니스 성과를 달성할 수 있도록 고객과 함께 효율적인 아키텍처를 구성하는 역할을 수행하고 있습니다. 현재 AWS의 Machine Learning, IoT, Analytics TFC에서 활동하고 있습니다.