https://ctrl-shit-esc.tistory.com/187
EC2는 S3에 어떻게 접근할 수 있을까?
사건의 발단최근 AWS 비용을 줄이기 위해 아키텍처 구조에 대한 대대적인 점검에 들어갔다.그러던 중, 엔드포인트 점검을 하던 중 이상한 것을 발견했다. 저 ssm-endpoint, ec2messages-endpoint, ssmmessages-
ctrl-shit-esc.tistory.com
위 글에 이어서 백엔드 코드에서 S3를 호출하는 여러 가지 방법에 대해 다뤄 보려고 한다.
그 전에...
코드 분석을 하기 전에 EC2에 부여된 S3 권한부터 살펴보았다.
우리 EC2에는
- 저장소 접근을 위한 ElastiCache, S3 서비스에 대한 Full Access
- Session Manager 사용은 위한 SSM~~ 서비스에 대한 Full Access
- Auto Scaling Full Access
- CloudWatch Admin과 Server 서비스에 대한 Full Access
권한이 부여되어 있었다. 최소 권한 부여가 되어 있지 않기 때문에 이것도 추후 수정하는 것으로...
여기서 중요한 점은 EC2에 S3FullAccess 권한이 있었기 때문에 어떤 방식으로 S3에 접근하려고 시도해도 권한이 전부 부여되어 있어 가능했다는 것을 명심해야 한다.
권한 정책으로 FullAccess를 넣는 것은 처음에는 편리할 수 있지만 이렇게 나도 모르는 틈을 내주는 것일 수도 있다.
근데 왜 또 여기에 RDS Access 권한은 없는거지? IAM 접근이라 권한이 없나?
1. Public S3 URL 생성
먼저, Public 상태인 버킷의 URL을 통해 접근하는 방법이 있다.
기본적으로 S3 버킷은 생성할 때 퍼블릭 액세스 차단이 되어 있다.
해당 설정을 끄면 타 사용자도 S3 버킷에 접근할 수 있게 된다.
이렇게 버킷을 퍼블릭 엑세스를 허용해두면, 다음과 같은 코드로 버킷에 접근할 수 있게 된다.
String imageUrl = String.format("https://%s.s3.ap-northeast-2.amazonaws.com/%s", bucket, banner.getImagePath());
버킷 이름과 객체 키를 조합하여 https://my-public-bucket.s3.ap-northeast-2.amazonaws.com/images/banner1.jpg url을 생성하고, 이 URL이 클라이언트에 반환된다.
그래서 실제로 퍼블릭 URL을 만들어서 요청을 보내면 개발자 도구에서 다음과 같이 버킷 이름, 경로, 파일이름 등이 전부 노출되는 것을 확인할 수 있다.
퍼블릭 URL을 사용하면 S3 버킷의 객체가 누구에게나 공개된다. 따라서 버킷의 URL 노출되는 취약점이 발생할 수 있다.
또한 인증 없이 접근 가능하기 때문에 누구나 파일을 확인하고 다운받을 수 있어 데이터 유출의 위험이 있다.
그리고 S3는 데이터를 다운받는 요청을 전송할 때, 그리고 실제로 다운받을 때 비용이 발생한다.
따라서 악의적인 목적을 가진 사용자가 노출된 URL로 데이터 다운로드 요청을 보내면 과금 폭탄을 받을 수 있다.
악의적인 목적을 가진 사용자가 아니더라도 크롤링에 걸려서 크롤러에서 꾸준히 요청을 보내면 해당 비용도 버킷 주인이 부담하게 된다.
이전 글의 유튜브 링크가 정확히 해당 상황에서 발생한 문제이다.
따라서 웬만하면 퍼블릭 액세스는 차단하는 것을 추천한다.
2. AWS SDK 사용
서버에서 AWS SDK를 사용해 S3 객체를 직접 다운로드 후 클라이언트에게 반환하는 방법이다.
클라이언트 → 서버 → S3로, 클라이언트가 S3와 연결되지 않고 서버를 거쳐서 전달된다.
SDK를 사용하기 위해서는 자격 증명이 필요하다.
자격 증명을 두 가지 방식으로 할 수 있다.
1. AWS Credentials를 통한 자격 증명
2. IAM Role을 이용한 자격 증명
AWS Credentials는 user로 접속하여 본인의 Access Key와 Secret Access Key를 입력하는 방식이다.
IAM에서 파일을 다운받고 입력하는 것으로 간단히 인증할 수 있으나 해당 정보가 유출될 수 있기에 추천하지 않는다.
EC2 인스턴스에 S3에 접근할 수 있는 IAM Role을 이용하여 SDK를 사용할 수도 있다.
AWS SDK를 통해 직접 S3에 접근하므로, 보안적으로 안전하고 IAM Role을 활용할 수 있으므로 해당 방법을 추천한다.
해당 방식을 사용하면 S3 URL이 클라이언트에게 노출되지 않아 보안적으로 매우 안전하다.
하지만 모든 요청을 서버가 처리하기 때문에 서버에 부하가 발생할 수 있고, 서버와 클라이언트 간 트래픽 비용이 발생할 수 있다.
다만 해당 방식을 사용하면 Gateway Endpoint를 이용하여 EC2와 S3 사이의 통신에서는 비용이 발생하지 않도록 구현할 수 있다.
3. Presigned URL 생성
세 번째 방법은 Presigned URL을 생성하여 접근하는 방법이다.
Presigned URL은 AWS S3의 객체에 대한 임시 접근 권한을 부여하는 서명된 URL이다.
이 URL을 사용하면 S3 버킷의 객체에 대한 GET(다운로드) 또는 PUT(업로드) 요청을 할 수 있으며, 일정 시간이 지나면 만료된다.
Presigned URL을 사용하면 S3에 직접 파일을 업로드를 할 수 있다.
따라서 클라이언트가 서버를 거치지 않고 S3에 직접 파일을 업로드할 수 있어, 서버 부하를 줄이고 데이터 전송 비용을 절감할 수 있다.
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.AmazonS3ClientBuilder;
import com.amazonaws.services.s3.model.GeneratePresignedUrlRequest;
import java.net.URL;
import java.util.Date;
public class S3Service {
private final AmazonS3 s3Client = AmazonS3ClientBuilder.defaultClient();
public String generatePresignedUrl(String bucketName, String key) {
GeneratePresignedUrlRequest request = new GeneratePresignedUrlRequest(bucketName, key)
.withExpiration(new Date(System.currentTimeMillis() + 3600 * 1000));
URL url = s3Client.generatePresignedUrl(request);
return url.toString();
}
}
AWS 자격 증명을 사용하여 인증된 사용자가 AWS SDK를 이용해 AmazonS3 클라이언트를 생성한다.
그 뒤 해당 클라이언트를 사용해 S3 버킷(bucketName)과 객체 키(key)를 지정하여 Presigned URL 요청을 생성한다.
Presigned URL의 유효 기간을 3600 * 1000, 즉 1시간 동안으로 지정했기 때문에 1시간 동안 해당 URL로 S3 버킷에 접근할 수 있게 된다.
URL이 일정 시간 후 자동으로 만료되기 때문에 보안이 강화되고, 클라이언트가 직접 S3와 통신하기 때문에 서버 부하가 감소한다는 장점이 있어 가장 대중적으로 사용되는 방법이다.
Reference
https://docs.aws.amazon.com/ko_kr/sdk-for-javascript/v2/developer-guide/s3-examples.html
https://docs.aws.amazon.com/ko_kr/AmazonS3/latest/userguide/PresignedUrlUploadObject.html
https://velog.io/@invidam/S3-Presigned-Url-%EB%8F%84%EC%9E%85%ED%95%98%EA%B8%B0