[AWS] S3를 통해 정적인 Asset 호스팅하기
S3를 사용해서 정적인 Asset을 호스팅해보자
S3는 99.99%의 가용성을 제공해줍니다. 단순히 Asset을 업로드하는 용도로 쓸 수도 있고 웹사이트를 호스팅할 수도 있고 다양한 용도로 사용할 수 있습니다.
Storage 요금(요청 요금은 별도)은 1TB 당 25달러 정도라 저렴합니다.
이번 글에서는 S3를 통해 정적인 Asset을 배포해봅시다.
다음 글에서는 AWS의 CDN 서비스인 CloudFront랑 S3 bucket을 연동해서 CloudFront로 배포되도록 설정하는 것을 적어보겠습니다.
주의
이미지, 폰트처럼 변하지 않는 정적 Asset 배포용 방법입니다. 개발하면서 변하는 마크업 파일이나 정적 사이트 호스팅은 조금 다른 설정을 가져가셔야 합니다.
준비물
- S3와 CloudFront의 권한을 가진 AWS 계정
- 테스트용으로 호스팅할 Asset 하나
- 저는 coding-groot.jpeg파일 하나를 준비했습니다.
S3로 배포하는 방법
0. S3 서비스 페이지로 이동합니다.
S3를 검색합니다.
Bucket 페이지로 이동합니다.
Create bucket을 눌러서 Bucket 생성을 해봅시다.
1. S3에서 Bucket을 만듭니다
도메인을 붙여서 정적 호스팅할 것이라면 static.mydomainname.com 이런식으로 이름을 지어주는 것도 괜찮습니다.
우리는 이 Bucket에 있는 것들을 외부에 공개할 것이기 때문에 Block Public Access settings for this bucket을 모두 해제합니다.
밑에 경고로 뜨는 것도 체크하셔서 나는 이 위험성을 알고 있다는 사실을 한번 더 인증해주셔야 합니다.
다른 설정은 하지 않고 Create bucket을 눌러서 생성해주겠습니다.
2. Asset 업로드
그럼 만들어진 버킷에 테스트용으로 준비한 사진을 업로드합니다.
Upload 버튼을 눌러봅시다.
파일로 업로드할 수도 있고 폴더로 업로드할 수 있습니다. 여기 창에 drag and drop을 해도 됩니다.
Add files를 눌러서 이미지 하나를 올려보겠습니다.
Upload를 눌러서 이미지 업로드를 마무리합니다.
아래 창과 같이 Upload succeeded가 뜨면 성공입니다.
coding-groot.jpeg을 클릭해봅시다.
그럼 그 이미지에 대한 상세 정보가 나옵니다.
Etag, Last modified, (Content-)Type 등 HTTP 요청에서 쓰일법한 것들도 보이고 Object URL도 보고 이 자원에 관한 여러 정보들이 보입니다. 심지어 Object URL은 HTTPS로 제공해줍니다.
3. S3 Bucket 정책 설정하기
Object URL 복사해서 접속한다면 이 이미지를 바로 불러올 수 있을까요?
AccessDenied라고 뜹니다.
우리가 처음에 Block Public Access settings for this bucket을 모두 해제했지만 왜 이런 현상이 발생하는 걸까요?
Block Public Access settings for this bucket을 설정해놓는 것은 근본적으로 이 Bucket 자체를 외부에서 인터넷을 통해 접근할 수 없게 하는 것입니다.
누가 다른 작업을 하다가 실수로 "이 Asset을 공개해줘!"라고 어떤 작업을 하더라도 무조건 차단하게 됩니다.
(물론 ACL을 사용하도록 설정하면 이게 원천 차단됐더라도 다른 사람이 접근할 수 있게 할 수 있긴합니다.)
Block Public Access settings for this bucket를 모두 해제했다는 것은 "이제 너는 Pulic으로 공개될 수 있는 능력을 가졌어. 내가 막지않을게"라고 말하는 것과 비슷합니다.
우리는 이 버킷이 외부로 공개해도 되게 설정했지만 실질적으로 외부로부터 자원에 대한 요청이 왔을 때 어떻게 하라는 것은 설정한 적이 없습니다.
정리하자면, AccessDenied가 뜬 이유는 Public에 대한 접근은 뚫어줬지만 "누가 외부에서 이 버킷의 자원인 coding-groot.jpeg에 접근했을 때 읽는 행위를 허용해줘"라는 것은 설정하지 않았기 때문입니다.
"누가 외부에서 버킷 자원에 접근했을 때 이런건 허용해줘" 이런 것은 S3 Bucket 정책에서 설정합니다. 이것까지 설정해줘야지 외부에서 접근해서 자원을 볼 수 있게 됩니다.
요약
- Block Public Access settings for this bucket으로 차단
- = 외부에서 인터넷으로 들어오는 모든 자원에 대한 요청을 원천 차단하겠다는 뜻
- 아무리 내부적으로 공개 설정을 하든 말든 Public에서(인터넷으로) 접근 못함
(물론 예외는 존재) - 정책으로 모든 사람에게 공개하도록 설정해도 Public에서(인터넷으로) 접근 불가
- Block Public Access settings for this bucket을 해제해서 외부 접근 허용
- = 외부에서 인터넷으로 접근하는 걸 원천 차단하진 않겠다는 뜻
- 외부에서 요청을 받을 수 있는 상태
- 정책을 통해 Public으로 자원을 제공해줄 수 있는 상태
- 어떤 것을 허용할지 버킷 정책으로 설정하면 외부에서도 접근 가능
- S3 버킷 정책 (Bucket policy)
- 실질적으로 Bucket 자원에 대한 접근을 제어
- "어떤 사람이 버킷 자원에 접근했을 때 이런건 허용해줘, 이런 사람에게는 이런건 허용하지마"
- 이게 없으면 Block Public Access settings for this bucket을 해제했더라도 AccessDenied 뜸
- 왜냐면 버킷 자원 접근에 대한 걸 정의한 게 없으니 일단 차단하는 것
그럼 버킷 정책을 설정해봅시다.
S3 Bucket으로 가서 우리가 생성한 Bucket을 클릭합니다. 그리고 Permissions 탭으로 이동합니다.
아래에 내리셔서 Bucket Policy에 Edit 버튼을 클릭합니다.
먼저 Bucket policy 편집창에 보시면 공백이 하나 있을텐데 그걸 지워줍니다.
(주의) space가 하나 추가로 들어가거나 오타 하나만 나도 오류가 납니다. 이것 때문에 처음에 고생 많이 합니다.
제가 미리 만들어둔 정책을 복붙합니다.
(Bucket Policy에 관련한 것은 https://awspolicygen.s3.amazonaws.com/policygen.html이걸 쓰시면 편합니다. 어떤게 들어가는지 금방 느낌 오실 겁니다)
복붙하실 때 "Resurce"의 자신의arn주소 부분은 BucketPolicy 상단에 떠있는 Bucket ARN을 입력하셔야 됩니다.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "allowGetObjectCodingGroot",
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": "자신의arn주소/*"
}
]
}
space가 들어가거나 comma가 마지막 필드에 남아있거나 오타가 하나라도 나면 오류가 뜰 겁니다.
이 정책을 간략하게 설명해보겠습니다.
먼저 Effect는 Allow이므로 다음에 나오는 것들을 허용하는 정책으로 사용하겠다는 의미입니다.
Action의 s3:부분은 s3와 관련된 액션이라는 것을 의미합니다. s3 액션의 GetObject는 버킷의 오브젝트를 가져오는(Read) 것을 의미합니다.
그리고 Resources에서는 어떤 오브젝트를 의미하는지 적습니다. 저는 여기에 자신의 버킷 arn주소 뒤에 "/*"을 붙이도록 했습니다.
arn은 AWS 리소스를 고유하게 식별하게 해주는 값입니다. 자신의 버킷 arn을 적었으면 자신의 bucket을 의미하게 됩니다. *은 모든 이라는 와일드 문자입니다. "버킷arn/*"은 버킷에 있는 모든 자원을 뜻하게 됩니다.
합치면 S3:GetObject를 내 버킷에 있는 모든 자원에 대하여 허용하라는 의미가 됩니다.
이제 다 되셨으면 Save changes를 눌러줍니다.
그리고 이전에 업로드했던 이미지의 Oject URL에 다시 접속해볼까요?
4. CORS 설정
우리가 이렇게 S3를 통해 정적 Asset을 호스팅하는 이유는 다른 사이트에서 이 Asset을 불러와서 사용하고 싶기 때문일겁니다.
그렇다면 자연스럽게 CORS에 대한 걱정이 들겁니다.
(만약 단순히 이미지 src로만 쓰거나 URL을 공유해서 다른 사람과 자원을 공유하는 용도이면 CORS 설정은 필요 없습니다.)
한번 테스트로 삼아 다른 사이트에서 크롬 console창을 열어서 AJAX요청을 보내봅시다.
내가 웹사이트에서 보낸 Request
GET /static.내S3오브젝트주소.com/coding-groot.jpeg HTTP/1.1
Accept: */*
Accept-Encoding: gzip, deflate, br
Accept-Language: ko-KR,ko;q=0.9,en;q=0.8
Cache-Control: no-cache
Connection: keep-alive
Host: s3.ap-northeast-2.amazonaws.com
Origin: https://www.내웹사이트.com
Pragma: no-cache
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: cross-site
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36
sec-ch-ua: "Chromium";v="104", " Not A;Brand";v="99", "Google Chrome";v="104"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "macOS"
S3 호스팅 서버로부터의 Response
HTTP/1.1 200 OK
x-amz-id-2: kSlyda4xxYlqQ9gCCNakbj5Fragr2QEOkxCxIts3FCWMLXk+kE3C8tii6AbRY/BXdkm+QA=
x-amz-request-id: RYB8Q8JKSDZF
Date: Mon, 22 Aug 2022 08:24:45 GMT
Last-Modified: Mon, 22 Aug 2022 07:03:38 GMT
ETag: "dad7e1df9f3b106dfb46d352957cae88"
Accept-Ranges: bytes
Content-Type: image/jpeg
Server: AmazonS3
Content-Length: 8665
S3도 CORS 설정을 지원해줍니다. 한번 고쳐봅시다.
다시 Bucket의 Permission으로 가줍니다.
제일 아래에 Cross-origin resource sharing (CORS)에서 Edit을 눌러줍니다.
다음의 정책을 붙여넣습니다.
[
{
"AllowedHeaders": [
"*"
],
"AllowedMethods": [
"HEAD",
"GET"
],
"AllowedOrigins": [
"*"
],
"ExposeHeaders": [
"ETag"
]
}
]
일단 임시로 모든 Origin에 대하여 GET과 HEAD 메서드만 허용하겠습니다.
다 되셨으면 Save changes를 눌러서 저장해줍니다.
다시 한번 AJAX 요청을 보내볼까요?
S3 호스팅 서버로부터 받은 Response
HTTP/1.1 200 OK
Date: Mon, 22 Aug 2022 08:33:53 GMT
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: HEAD, GET
Access-Control-Expose-Headers: ETag
Last-Modified: Mon, 22 Aug 2022 07:03:38 GMT
ETag: "dad7e1df9f3b106dfb46d352957cae88"
Accept-Ranges: bytes
Content-Type: image/jpeg
Server: AmazonS3
Content-Length: 8665
우리가 설정한대로 Access Control Allow Origin 헤더값이 모든 오리진("*")으로 오네용.
따라서 CORS에 의해 차단되지도 않았습니다.
특정 사이트만 설정해볼까요?
잘 옵니다.
S3 Response
HTTP/1.1 200 OK
Date: Mon, 22 Aug 2022 08:42:54 GMT
Access-Control-Allow-Origin: https://www.웹사이트.com
Access-Control-Allow-Methods: HEAD, GET
Access-Control-Expose-Headers: ETag
Access-Control-Allow-Credentials: true
Vary: Origin, Access-Control-Request-Headers, Access-Control-Request-Method
Last-Modified: Mon, 22 Aug 2022 07:03:38 GMT
ETag: "dad7e1df9f3b106dfb46d352957cae88"
Accept-Ranges: bytes
Content-Type: image/jpeg
Server: AmazonS3
Content-Length: 8665
5. 제대로 정책 설정
S3의 장점은 정책을 세분화해서 걸 수 있다는 겁니다.
모두 허용하지 말고 내가 의도한 사이트에서만 접근 가능하도록 제대로 정책을 설정해 봅시다.
댓글
이 글 공유하기
다른 글
-
[AWS] AWS SES API를 사용해서 메일 보내기 (feat. NodeJS)
[AWS] AWS SES API를 사용해서 메일 보내기 (feat. NodeJS)
2022.10.09 -
[AWS] VPC의 NAT 비용을 줄여보자 :: Ubuntu로 NAT 인스턴스 만들기
[AWS] VPC의 NAT 비용을 줄여보자 :: Ubuntu로 NAT 인스턴스 만들기
2022.09.25 -
[AWS] VPC에 Subnet, NAT Gateway, Internet Gateway를 구성해보자
[AWS] VPC에 Subnet, NAT Gateway, Internet Gateway를 구성해보자
2022.08.07 -
[AWS] NestJS 프로젝트를 Code Pipeline을 사용해서 Elastic Beanstalk으로 배포하는 법
[AWS] NestJS 프로젝트를 Code Pipeline을 사용해서 Elastic Beanstalk으로 배포하는 법
2022.07.22