이번엔 Canva앱을 만들어보게 되었습니다
간단히 설명하면 "[내 얼굴 사진]을 올리고 [중세시대 기사]라는 프롬프트를 작성하면 [중세시대 기사인 나]의 사진을 만들어주는 서비스" 입니다. Canva 앱이니까 언제든 접근되어야하고 여러 사용자를 대상으로 오래 구동될 서비스입니다.
즉, 불특정 다수의 사용자가 브라우저에서 S3 버킷으로 이미지를 업로드해야하는 서비스 입니다
S3버킷 접근 권한 관리 방법
(기본적으로 S3 퍼블릭 엑세스 차단 설정을 깔고 갑니다)
우선 결론부터 말하자면 다음과 같습니다.
결론. [ S3 pre-signed url + CloudFront 로 S3 접근 권한 관리 ] : aws에서 권장하는 방식
presigned url을 가진 사용자는 인증된 사용자로서 s3 접근(파일 업로드,다운로드 등)을 허용을 해 주는 것입니다.
이는 일시적인 보안 증명 방식으로 기본 방식은 15분동안 유효한 url을 발급받아 사용하는 것 입니다.
설정을 통해 url의 유효시간을 1분~12시간 까지 변경 가능합니다.
방식의 작동 흐름
0️⃣ 서버(또는 Lambda)에 IAM Role을 적용하여 S3에 pre-signed url을 요청 할 권한을 부여합니다.
1️⃣ 유저가 이미지를 업로드하면 api를 이용하여 S3에서 pre-signed url을 요청하여 받아옵니다.
( 저는 서버리스 방식으로서 API Gateway 의 API를 호출하고 이를 통해 Lambda 를 트리거하였습니다. Lambda 가 S3에게 pre-signed url을 요청하게 되고, response로 pre-signed url를 받습니다. )
2️⃣ 서버(또는 Lambda)가 이 pre-signed url을 다시 클라이언트로 전달합니다.
3️⃣ 클라이언트에서 pre-signed url 을 이용하여 S3로 직접 이미지를 업로드합니다.
근데 cloudfront는 갑자기 왜쓰나 ? 하면은 이걸 안쓰면 presigned url의 엔드포인트가 s3버킷 이 되기 때문입니다. 당연히 그래야되는거 아닌가? 싶은데 이렇게 되면... url에 버킷명이 고대로 노출됩니다.
그렇기 때문에 presigned url의 엔드포인트를 cloudfront로 설정하여 버킷명 노출을 막고, 보안을 강화하기 위함입니다
이와 관련해 잘 정리된 블로그가 있어 공유합니다!
✨ 블로그 추천 _ S3와 CloudFront로 이미지 업로드 구현하기 : https://www.earlgrey02.com/post/21
해당 방식의 장점
1️⃣ pre-signed url 생성 요청이 서버에서 진행되므로 클라이언트에서 엑세스 키 등이 사용되지 않아 보안을 강화할 수 있다
2️⃣ 용량이 큰 데이터인 이미지는 직접 클라이언트에서 S3로 업로드하기 때문에 서버로 이미지 formData를 전송하는 과정이 생략되어
서버 부하를 방지 할 수 있다
3️⃣ pre-signed url 은 한번 생성되면 설정해둔 기간이 만료되기 전에 무제한으로 재사용이 가능하다
4️⃣ 그래서 빠른 서비스를 제공할 수 있다
그런데 사실 3번은 악성 해커에 의해 사용되면 단점이 될 수도 있습니다. 누군가가 악의적으로 pre-signed url을 반복 호출한다면 과금이 발생할 것입니다.
그렇기에 pre-signed url을 Lambda에서 요청/관리하고 Client에서 Lambda에 업로드한 이미지를 Lambda가 Cloudfront에 업로드 하게 하면 어떨까? 라는 생각을 했지만.. 이는 Lambda의 부하가 너무 커지게 됩니다. Lambda 타임 아웃 등에 의해 UX 의 저하가 발생할 위험이 존재합니다.
결과적으로 이 부분은 API Gateway에서 호출 횟수를 제한하는 것으로 어느정도 해결이 가능합니다. 더 철저히 관리하려면 WAF를 사용하는 게 좋아보입니다.
또 다른 문제는 S3에 대한 접근권한을 관리함으로서 pre-signed url을 요청할 수 있는 주체에 대한 certificate는 진행되었지만, 그 요청을 요청하는 주체, 즉 API Gateway를 통해 post 요청을 보낼 클라이언트에 대한 인증 방법이 마련 되어있지 않습니다.
이 부분은 추후에 AWS Cognito를 통해 인증된 유저인지를 체크하는 것으로 해결해줄 수 있습니다.
+) 저는.. cloudfront 쓸거면 oac도 쓰는게 좋을거아닌가? 싶은 의문이 들었는데 그건 또 아닌 것 같습니다. 이중보안인 것 같더라구요.
OAC는 Origin Access Control 로 말그대로 cloudfront의 오리진인 s3버킷에 대해 관리해주는 아이 입니다. S3의 객체를 SSE-KMS로 암호화하여 보안을 강화해주기도 하구요. 지정된 목적지에서의 트래픽만 허용, 즉 uncertificated user(인증되지 않은 유저) 의 접근을 막아주고 certificated use (인증된 유저)만 접근을 허용합니다. 저는 presigned url을 사용할 예정이니 여기서 이미 인증된 주체가 S3 접근권한 url을 생성해 주고, AWS Cognito를 이용한 유저 확인도 가능하므로 oac 까지는 적용하지 않는 것으로 정했습니다.
++) OAC의 적절한 사용예시 : S3에서 정적 웹 호스팅을 할때에 cloudfront를 앞에 두고 OAC를 적용하여 cloudfront를 통해서만 S3(html 파일이 올라가있는 S3버킷)에 접근가능하도록 설정할 수 있습니다.
CORS 정책 설정
- 클라이언트 측에서 이미지를 업로드 하면, CORS 오류가 발생합니다.
CORS 오류란 ?
- 브라우저에는 CORS(Cross Origin Resource Sharing) 정책이 있습니다. 이는 말그대로 서로 다른 Origin( url 에서 프로토콜, 도메인 네임, 포트번호 부분) 끼리는 데이터를 주고받을 수 없다는 것 입니다. S3와 웹사이트는 Origin( == https://도메인이름:포트번호 )이 다르기 때문에 서로 데이터를 주고받을 수 없습니다. 그렇기에 CORS오류가 발생합니다.
문제 해결
- S3에서 CORS를 활성화 해주어야 합니다. 이를 통해 Canva 웹페이지 url에서 출발하는 트래픽은 S3에 대한 접근이 허용된 오리진이 되어 CORS 에러를 해결할 수 있습니다
+) CORS가 브라우저 자체 정책이므로, 서버에서 이미지를 보낼 때는 신경을 안써도 됩니다.
Canva 앱 개발하기
이제 캔바앱을 개발해볼텐데, Lambda를 이용해 서버리스 방식으로 구현을 해보려 합니다. API Gateway로 Api 를 생성하여 클라이언트의 요청을 받습니다. 그리고 서버 대신 Lambda로 pre-signed url을 요청해보겠습니다.
AWS 서비스 사용 비용 계산하기
aws 테스트를 위해 내 계정으로 서비스를 만들어서 써야하는데...비용이 무서워졌습니다
몰라서 무서운 것이니 안전한 실험을 위해 s3, lambda, gateway, cloudfront 의 비용 발생에 대해 알아보겠습니다
이미지가 용량이 큰 데이터이다보니, 압축 후 전송하는 것이 전송량을 줄일 수 있어 좋다는건 익히 알고있었습니다. 근데 얼마나 구체적으로 좋은지도 이 기회에 알아보기 위해 이미지 압축 전 / 후 두 경우로 나눠 알아보았습니다
설정 상황 _ 1년 기준 이미지 전송 횟수 1000회 (사용자가 그렇게 많지 않은 서비스라고 가정)
항목 | 압축 전 | 압축 후 |
이미지 크기 | 3MB | 1.5MB |
전송 횟수 | 1000회/년 | 1000회/년 |
총 데이터 전송량 | 3GB | 1.5GB |
사용되는 AWS 서비스 및 총 비용 합계 비교
항목 | 이미지 압축 전 | 이미지 압축 후 | 비용 절감 효과 |
Lambda 비용 | $0.000213 | $0.000213 | - |
API Gateway 비용 | $0.003500 | $0.003500 | - |
S3 요청 비용 | $0.005900 | $0.005900 | - |
S3 데이터 전송 비용 | $0.270000 | $0.135000 | $0.135000 (약 50%) |
CloudFront 비용 | $0.378750 | $0.189750 | $0.189000 (약 50%) |
총 비용 합계 | $0.913363 (1223.34원) | $0.461863 (618.46원) | $0.451500 (약 49.43%) |
항목별 설명
1. Lambda 비용
- Lambda : AWS Lambda는 서버를 관리하지 않고 코드를 실행할 수 있는 서버리스 컴퓨팅 서비스입니다. 사용자가 정의한 함수(코드)를 트리거 하여 실행할 때마다 비용이 부과됩니다.
- 비용 산정: 메모리 크기(128MB)와 실행 시간(100ms)을 기준으로 1000번 호출 시 발생하는 비용을 계산했습니다. 이 경우 압축 여부에 따른 비용 차이는 없습니다. 참고로 람다는 월 100만건 까지는 무료입니다.
2. API Gateway 비용
- API Gateway : API Gateway는 API를 만들고 관리할 수 있는 AWS 서비스입니다. HTTP 및 WebSocket API를 생성하여 클라이언트와 서버 간 통신을 관리할 수 있습니다.
- 비용 산정: API 호출 수 에 따라 비용이 부과됩니다. 1000번의 API 호출을 기준으로 비용을 계산했습니다. API 호출 횟수는 이미지 압축 여부와 무관하기 때문에, 비용도 동일합니다.
3. S3 요청 비용
- S3 : Amazon S3(Simple Storage Service)는 객체 스토리지 서비스로, 데이터를 인터넷 규모로 저장 및 검색할 수 있습니다.
- 비용 산정: PUT, GET 요청 수에 따라 비용이 부과 됩니다. 3MB 이미지 파일을 1000번 업로드(저장)하고 1000번 다운로드(읽기)했을 때 발생하는 비용을 계산했습니다. 이미지 압축은 요청 수에 영향을 주지 않으므로, 압축 전후 비용이 동일합니다.
4. S3 데이터 전송 비용
- S3 데이터 전송 비용 : S3에서 데이터를 다른 AWS 서비스나 인터넷으로 전송할 때 발생하는 비용입니다.
- 비용 산정: 주로 다운로드 또는 데이터 이동 시에 비용이 부과 됩니다. 3GB 데이터를 전송하는 경우와 1.5GB 데이터를 전송하는 경우의 비용을 비교했습니다. 이미지 압축 후 데이터 전송량이 줄어들어 비용 절감 효과가 나타났습니다.
5. CloudFront 비용
- CloudFront 비용 : CloudFront를 통해 처리되는 HTTP/HTTPS 요청의 수와 최종 사용자에게 전송되는 데이터의 양에 따라 비용이 부과됩니다. HTTP 요청 비용은 10,000 요청당 약 $0.0075, HTTPS 요청 비용은 10,000 요청당 약 $0.0100 입니다. 각 리전별로 데이터 전송량에 대한 비용도 조금씩 달라집니다. 한국 리전의 데이터 전송 비용은 약 $0.126/GB 입니다.
- 비용 산정: 전송되는 데이터의 양과 요청 수에 따라 비용이 부과 됩니다. 클라이언트에서 CloudFront로 3MB 이미지를 1000번 전송한 경우와 1.5MB로 압축된 이미지를 1000번 전송한 경우를 비교했습니다. 이미지 압축으로 인해 데이터 전송량이 감소하여 비용 절감이 발생 했습니다.
결론 : 이미지를 압축했을 때 비용이 절반이나 절감된다니 생각보다 효과가 큽니다. 이미지 압축은 특별한 경우가 아니고서야 꼭 해야겠습니다. 그리고 이미지 압축을 안해도 1224원 정도 비용이 발생합니다. 너무 보수적으로 비용을 측정했나? 싶기는 한데 그래도 이제 겁은 안납니다. 용감하게 s3테스트를 해볼 수 있을 것 같습니다...
+) 실제로 이미지를 압축해서 전송해본 결과 절반 이상으로 압축되는걸 확인했습니다
이미지 업로드 테스트하기
이제 정리한 방법대로 하면 pre-signed url이 제대로 생성되는지, S3 이미지 업로드 시 정상적으로 작동하는지 확인하기 위해 AWS 서비스를 사용하여 테스트를 진행해보았습니다. 필수적 세팅으로만 진행하여 root계정을 사용했습니다
IAM 정책 만들기
특정 S3버킷에 대한 권한 부여를 해주는 정책입니다. 공통적으로 사용될 IAM정책입니다. 아래에서 Lambda함수에 적용해줍니다.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:PutObject",
"s3:GetObject"
],
"Resource": "arn:aws:s3:::버킷명/*"
}
]
}
S3 버킷 생성하기
presigned url 을 생성하며, 업로드 된 이미지를 저장할 버킷입니다.
기본설정 그대로 둡니다
S3 CORS 정책은 다음과 같이 설정해줍니다.
[
{
"AllowedHeaders": [
"*"
],
"AllowedMethods": [
"GET",
"POST",
"PUT",
"DELETE",
"HEAD"
],
"AllowedOrigins": [
"*"
],
"ExposeHeaders": [
"ETag"
],
"MaxAgeSeconds": 3000
}
]
Lambda 함수 생성하기
S3에 presigned_url을 요청할 Lamdba 함수를 생성합니다.
Lambda 함수에 IAM Role 을 부여합니다. IAM Role 에는 위에서 만든 S3버킷 접근을 허용해줄 IAM 정책을 적용하여 S3에 대한 요청 권한을 부여합니다
(IAM Role 내부에서 인라인 정책으로 설정해도 되지만 저는 관리가 용이하게 따로 정책으로 만들어봤습니다)
그리고 Lambda 함수를 작성해줍니다. Lambda 함수를 보기전에 CORS 관련하여 Lambda에서도 설정해줘야하는 부분이 있습니다. 바로 Lambda 함수에서도 CORS 헤더를 return 해줘야한다는 것입니다.
pre-signed URL 생성에 성공한 경우의 return 문에 다음의 headers를 넣어줘서 CORS 문제를 해결했습니다. 이걸 안넣어줘서 반나절을 헤맸네요...
'Access-Control-Allow-Origin': 'https://app-aaglrukv8e8.canva-apps.com',
'Access-Control-Allow-Headers': 'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token',
'Access-Control-Allow-Methods': 'GET,OPTIONS'
관련 스택 오버플로우 글 : https://stackoverflow.com/questions/35190615/api-gateway-cors-no-access-control-allow-origin-header
API Gateway CORS: no 'Access-Control-Allow-Origin' header
Although CORS has been set up through API Gateway and the Access-Control-Allow-Origin header is set, I still receive the following error when attempting to call the API from AJAX within Chrome:
stackoverflow.com
Lambda 함수 전문
import json
import boto3
import os
s3_client = boto3.client('s3')
def lambda_handler(event, context):
# 요청에서 필요한 매개변수 가져오기
bucket_name = os.getenv('BUCKET_NAME') # 환경 변수로 S3 버킷 이름 설정
object_name = event['queryStringParameters'].get('object_name', 'default-object') # 업로드할 파일 이름
expiration = int(event['queryStringParameters'].get('expiration', 3600)) # URL 유효 기간 (초)
try:
# S3 pre-signed URL 생성
presigned_url = s3_client.generate_presigned_url(
'put_object',
Params={'Bucket': bucket_name, 'Key': object_name},
ExpiresIn=expiration
)
return {
'statusCode': 200,
'body': json.dumps({'url': presigned_url}),
'headers': {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': 'https://app-aaglrukv8e8.canva-apps.com',
'Access-Control-Allow-Headers': 'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token',
'Access-Control-Allow-Methods': 'GET,OPTIONS'
}
}
except Exception as e:
print(e)
return {
'statusCode': 500,
'body': json.dumps('Lambda Error generating pre-signed URL'),
'headers': {
'Content-Type': 'application/json'
}
}
API Gateway 의 Rest API 생성하기
클라이언트의 url 요청을 받고, Lambda 함수를 트리거할 API Gateway 의 Rest API 를 생성합니다.
이때 API Gateway는 클라이언트와 요청을 주고받아야하므로 마찬가지로 CORS 를 활성화해줍니다.
CORS 를 활성화해주는 법
https://docs.aws.amazon.com/ko_kr/apigateway/latest/developerguide/how-to-cors-console.html
저는 바로 Lambda 콘솔창에서 [트리거 추가 - API Gateway - 새 API 추가 - Rest API] 로 생성해줬습니다.
나머지는 인터넷 보면서 하면 그냥 바로바로 되는 부분들입니다.
+) 캔바 개발에 필요한 기본적인 부분은 이 분의 블로그를 참고했습니다. 팀장님이 보내주신 자료입니다🙇🏻♀️🙇🏻♀️
https://hello-bryan.tistory.com/586
6. Canva에 앱 만들기. api 호출 using axios
Canva App 에서 axios 사용 1. axios 설치npm 으로 axios 를 설치합니다.npm install axios그럼 설치가 완료되고 package.json 에 axios 가 추가된 것을 확인 할 수 있습니다.이렇게 해야 앱을 캔바에 배포할 때, 필
hello-bryan.tistory.com
다시 S3 접근 권한 관리로 돌아와서...
s3 버킷 접근 관리하는 방법이 (처음 보는 입장에서) 너무 여러개고 그 중에 나의 상황에 안맞는 방법들을...붙잡고 삽질을 해대서 삽질 케이스를 공유합니다
(내 상황에서만) 뻘짓1. [ STS를 이용한 임시보안자격증명 사용하기 ]
이 방식은 "유저가 IAM role 부여받고 STS로 임시보안자격증명 생성하고 aws 리소스에 접근하기" 이고 aws에서 권장하는 보안적으로 뛰어난 방식입니다.
그래서 처음에는 이걸 사용해야되는 줄 알고 열심히 적용해보다가 알아차린게... 얘는 사용처가 좀 다릅니다
결론 부터 말하면, 얘는 클라이언트에 일일이 role을 부여해야하기 때문에 번거롭습니다. 따라서 다수의 사용자를 가지는 서비스에는 맞지 않아 보입니다.
앞에서 본 pre-signed url방식은 서버(혹은 Lambda)에만 IAM Role을 사용하면 됐습니다. 그래서 절차가 간단하고 유저가 불특정 다수인 서비스에 적절한 방식이라고 판단한 것이죠
우선 이 방식의 작동과정을 알아봅시다
임시보안자격증명의 원리
IAM role에 aws 리소스 접근 권한 정책을 부여한다. -> 이 IAM role을 외부 유저에게 부여한다. -> 외부 유저는 이 IAM 역할이 부여 (= assume = 수임)된 상태에서 AWS Security Token Service(STS)의 AssumeRole API 호출이 가능해지고, 임시 보안 자격증명을 얻어 AWS 리소스에 접근해 작업을 한다. -> 이때 임시보안자격증명은 보통 15분~12시간(최대) 사이로 설정되며 그 이후 자동 파기되어 해당 임시보안자격증명으로는 aws리소스 접근이 더이상 불가능해진다
그런데 (내 상황에서) 문제는 AssumeRole API 를 호출해서 임시보안자격증명을 얻으려면, 이미지를 업로드 하는 유저마다 IAM Role을 부여받고 자격증명을 요청해야 한다는 것입니다. 이는 불특정다수의 사용자가 발생하는 어플리케이션에는 적절하지 못하고 복잡한 방법이라고 판단했습니다.
그리하여 이 방식은 권한을 위임받아야 하는 사용자가 적은 경우 [ ex. 외부 서비스(사용자)가 우리 aws 리소스를 일정 기간 관리해줘야하는 경우 등 ] 에 적절하다고 판단했다. 사용 예시들도 이런 결이었습니다.
+) AssumeRole API를 호출 후 얻은 임시보안자격증명을 유효 기간동안 캐싱하여 재사용하는 방법도 생각했지만, 이건 너무 서비스의 취지에 맞지 않는다고 판단하여... 패스~
(그리고 흥미로운 기사를 보았습니다... IAM Role과 STS를 사용하는 방식이 해커들의 공격통로 된다는 것을 밝혀냈다고 합니다. 원래 완전한 보안은 불가능한 것이라고 하지만... )
뻘짓2. [ sdk를 사용하여 클라이언트 측에서 직접 s3에 업로드하기 ]
클라이언트 쪽에서 S3에 업로드 하자.. 이때 임시 자격 증명 보안 이런거 하지 말고 장기 자격 증명 쓰자.. 할 수가 있습니다
이런 경우 SDK를 사용하고 S3 버킷 권한 설정에서 '버킷권한'과 'CORS 정책'을 설정해주면 됩니다. 그리고 access key, secret access key는 .env 파일로 별도 관리해주면 됩니다
그치만 access key, secret access key를 따로 관리한다고는 했는데 실수로 업로드가 된다면, 영구적으로 유효한 access key, secret access key가 그대로 외부에 노출되고 이는 s3과금이 발생하기 딱 좋은 상황
그리고 access key와 secret access key에 문제가 생기는 경우(노출되는 경우), 이걸 가진 모든 유저를 대상으로 이 key를 갱신해줘야 한다. 관리가 번거롭습니다.
보안에 대해 어느정도가 적정선인지 아직 모르겠지만... 여러 정보를 찾아봐도 이 방법은 실수로 키가 업로드 되는 등 어쨌든 내가 잘 관리를 해줘야하는 것이기 때문에 불안감이 존재하는 것 같습니다.
그래도... 써먹을 곳이 있을 수도
클라이언트에 s3이미지 업로드 하는 방법 : https://velog.io/@rlorxl/%ED%81%B4%EB%9D%BC%EC%9D%B4%EC%96%B8%ED%8A%B8%EC%97%90%EC%84%9C-aws-s3%EC%97%90-%EC%9D%B4%EB%AF%B8%EC%A7%80%ED%8C%8C%EC%9D%BC-%EC%97%85%EB%A1%9C%EB%93%9C%ED%95%98%EA%B8%B0
클라이언트에서 aws s3에 이미지파일 업로드하기
프로젝트에서 사용자가 각자 직접 이미지 파일을 업로드하고 수정할 수 있는 게시물 업로드 방식을 구현하는데 s3를 사용했다. aws s3(Simple Storage Service)는 aws에서 제공하는 데이터를 저장할 수 있
velog.io
S3 이미지를 Canvas API에서 사용할 경우 발생하는 CORS 문제 해결 방법
S3에서 이미지를 불러와 Canvas API에서 사용하는 경우, No 'Access-Control-Allow-Origin' header is present on the requested resource 라는 에러 메시지가 발생한다. 어떻게 해결하면 좋을지 알아보자.
velog.io
버킷 정책 설정 : https://docs.aws.amazon.com/ko_kr/sdk-for-javascript/v2/developer-guide/s3-example-photo-album.html
브라우저에서 Amazon S3에 사진 업로드 - AWS SDK for JavaScript
인증되지 않은 사용자의 액세스를 활성화하면 버킷과 해당 버킷의 모든 객체, 전 세계 모든 사람에게 쓰기 권한을 부여하게 됩니다. 이러한 보안 태세는 이 사례의 기본 목표에 초점을 맞추는
docs.aws.amazon.com
틀린 부분 발견하시고 알려주시면 감사히 배우겠습니다 💪🏻
'프로젝트' 카테고리의 다른 글
개인 프로젝트 | 02 _ 홈서버 구축하기 with 도커 (3) | 2025.01.03 |
---|---|
개인 프로젝트 | 01 _ 웹앱 도입에 대하여 (0) | 2024.09.28 |
개인 프로젝트 | 00 _ 개인 프로젝트 아키텍처 (3) | 2024.09.06 |