Programming/Django

[Django, AWS S3] S3를 사용해서 django static파일 관리하기

stein 2022. 1. 21. 17:31

배경 이야기

지금 운영중인 서비스에는 대용량 파일(10MB~2GB)을 업로드/다운로드 해야하는 작업이 있다. 이 작업을 기존에는 django의 static으로 자체 관리하였는데 두 가지 문제가 있었다.

 

1. 백엔드(api) 서버의 저장공간의 부족을 일으킬 수 있다.

2. 대용량 업로드/다운로드는 네트워크 비용을 증가시킨다.

 

이 2가지 문제는 Cloud Server가 제공해주는 Storage 서비스를 통해 쉽게 해결 될 수 있다. 따라서 우리는 Azure의 'Azure Storage' 서비스를 사용하고 있었는데, 이번에 aws로 서버를 옮기면서 Storage 서비스도 'Amazon S3'로 변경하기로 했다. 본 게시글은 이 이전작업에 기초하여 작성됨을 알고 읽기를 바란다.

 

참고한 글

aws s3 미디어 서버 설정 & django 설정

 

aws s3 미디어 서버 설정 & django 설정

미디어, 정적파일을 공유할 수 있도록 아마존 S3(simple storage service)를 설정해봅시다~! s3는 버킷이란 단위를 사용한다! (RDS=인스턴스)aws 서비스 선택에서 s3를 찾아 클릭하면 버킷만들기 버튼을 찾

velog.io

s3 referer 제한

 

AWS S3 Public 으로 사용하면서 보안 강화하는 방법 - 팁

목차 S3 Public으로 사용하면서 보안강화하는 방법 개요 1. S3 Public으로 사용하면서 보안강화하는 방법 https://aws.amazon.com/ko/premiumsupport/knowledge-center/block-s3-traffic-vpc-ip/ https://doc..

st-soul.tistory.com

필요한 패키지들

boto3, django-storages를 다운받아준다.

그리고 settings.py의 INSTALLED_APPS에 storages를 추가해준다.

 

S3 Block public access (bucket settings)

언어를 꼭 영어로 해놓고 읽자

각각의 옵션에 대해서 설명하면,

1. Block public access to buckets and objects granted through new access control lists (ACLs) (관리 옵션)

    acl을 새로 만들지 못 하도록 막는 옵션이다. 이렇게 하면 관리자 콘솔에서 acl을 수정, 추가하려해도 권한이 없어 변경되지 않는다.

2. Block public access to buckets and objects granted through any access control lists (ACLs)

    설정한 acl을 모두 무시한다. acl을 통해 public으로 오픈되었다면 모두 막히게 된다.

3. Block public access to buckets and objects granted through new public bucket or access point policies (관리 옵션)

    bucket policy나 access point poilcy를 새로 만들지 못 하도록 막는 옵션이다. 이렇게 하면 관리자 콘솔에서 bucket policy나 access point poilcy를 수정, 추가하려해도 권한이 없어 변경되지 않는다.

4. Block public and cross-account access to buckets and objects through any public bucket or access point policies

    설정한 bucket policy나 access point poilcy를 모두 무시한다. bucket policy나 access point poilcy를 통해 public으로 오픈되었다면 모두 막히게 된다.

 

👎 사족인데 1, 3번의 옵션 이름 참 별로인것 같다. 어차피 네트워크에 대한 변경사항을 만들지 않고, 추가적인 수정 권한을 잠그는 옵션이니 Block publish access to ~~ 보다, 그냥 Lock ACL~~ 거나 Diable edit ACL~~ 로 했으면 좀 더 좋지 않았을까. 저렇게 두니 뭔가 권한의 제한이 아니라 네트워크의 제한 느낌이지 않는가. 개인적인 참견이다.

 

위와 같이, 퍼블릭으로 모두 열어준다(2, 4번에 대해서 열어야하는 이유는 아래에서 하나씩 살펴보자).

이 부분은 안전한 방식이 아닌 것을 확실히 짚고 넘어가겠다.

 

가장 좋은 방법은 매번 key값을 확인하고 그 key가 맞는지 확인을 한 후에 파일을 제공해주는 것이다. 하지만 key값을 프론트(클라이언트)에 넣어놓을 수는 없기 때문에, 요청이 들어올 때 사용할 임시 key(짧은 수명)를 발급하고 그 key로 요청하는게 베스트일 것이다. 하지만 그렇게 할 만큼 백엔드에 시간을 투자할 수 없는 상황이라 public access를 열 수 밖에 없었다.

 

그래도 referer를 통해서 접근을 막는 방법이 있기 때문에 이 설정을 추가하면서 왜 ACL과 Bucket polices를 열어줘야하는지 알아보자.

https://st-soul.tistory.com/166

 

1. refer제한(bucket policy를 열어야하는 이유)

다음 컨디션을 bucket policy에 추가하면 된다.

"Condition":{ 
    "StringLike":{ 
        "aws:Referer":[ 
            "http://www.example.com/*", 
            "http://example.com/*" 
            ] 
     }
}

만약 bucket policy 세팅이 되지 않았다면. 다음 페이지에서 생성할 수 있다.

https://awspolicygen.s3.amazonaws.com/policygen.html

 

AWS Policy Generator

Click below to edit. To save the policy, copy the text below to a text editor. Changes made below will not be reflected in the policy generator tool.

awspolicygen.s3.amazonaws.com

 

2. django admin static 전달 허용(ACL을 열어야하는 이유)

위 설정을 통해서 우리의 프론트 페이지에서 s3로 접근은 가능해졌다. 하지만 django admin으로 들어가니 static 파일들이 전달되지 않고 있다. django admin url을 referer에도 추가 해주었지만 결과는 같았다. 🤔 왜 그런걸까?

referer가 없다..!

django의 static file들은 script로 가져오기 때문에 referer가 없는 것이다! 왜 그런지 정확히는 모르겠지만 default가 no-referrer로 되어있어 보인다.

https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy

 

Referrer-Policy - HTTP | MDN

The Referrer-Policy HTTP header controls how much referrer information (sent with the Referer header) should be included with requests. Aside from the HTTP header, you can set this policy in HTML.

developer.mozilla.org

그래도 다행인건 django의 static source들은 퍼블릭으로 공개해도 크게 문제가 될 용량이 아니기 때문에(물론 좋지는 않다만) acl을 이용해서 열어주기로 하자.

static을 클릭 > actions > make public
make public으로 acl 변경

위 과정을 따르면 static 폴더 및 하위 파일들이 모두 public에서 접근가능해진다.

everyone의 object에 read 옵션이 추가되어있다.

여기서 주의할점은 한 번 추가한 ACL을 일괄적으로 제거할 방법은 찾아도 보이지 않더란 것...😨. 따라서 해당 과정을 진행할 때 폴더를 제대로 설정한지 제대로 확인 후에 진행하자. 만약 변경했다면 해당 폴더를 그대로 다운받고 다시 업로드하면 acl이 사라져있을 것이다(필자가 그러했다).

 

자 이제 s3에서 설정할 것은 모두 끝났다.

Django settings에 추가 변수들

# AWS
AWS_S3_ACCESS_KEY_ID = os.environ.get(
    "AWS_S3_ACCESS_KEY_ID"
)  # .csv 파일에 있는 내용을 입력 Access key ID
AWS_S3_SECRET_ACCESS_KEY = os.environ.get(
    "AWS_S3_SECRET_ACCESS_KEY"
)  # .csv 파일에 있는 내용을 입력 Secret access key
AWS_REGION = "ap-northeast-2"

###S3 Storages
AWS_STORAGE_BUCKET_NAME = os.environ.get("AWS_STORAGE_BUCKET_NAME")  # 설정한 버킷 이름
AWS_S3_CUSTOM_DOMAIN = "%s.s3.%s.amazonaws.com" % (AWS_STORAGE_BUCKET_NAME, AWS_REGION)
AWS_S3_OBJECT_PARAMETERS = {
    "CacheControl": "max-age=86400",
}


# AWS_DEFAULT_ACL = 'public-read'
AWS_LOCATION = 'static'
STATIC_URL = 'https://%s/%s/' % (AWS_S3_CUSTOM_DOMAIN, AWS_LOCATION)
STATICFILES_STORAGE = 'storages.backends.s3boto3.S3Boto3Storage'
STATICFILES_DIRS = [
    os.path.join(BASE_DIR, 'static')
]

여기서 주의해야하는 건 AWS_DEFAULT_ACL을 설정하지 않는 것이다.(None으로 설정)

https://django-storages.readthedocs.io/en/latest/backends/amazon-S3.html

 

AWS_DEFAULT_ACL (optional; default is None which means the file will inherit the bucket’s permission)

Use this to set an ACL on your file such as public-read. By default the file will inherit the bucket’s ACL. If the ACL parameter is set in AWS_S3_OBJECT_PARAMETERS, then this setting is ignored.

이 설정을 public-read로 넣는다면, 업로드하는 모든 파일이 public에서 접근가능하도록 ACL이 설정된다(개별 object별로 추가된다).

그러면 위에서 설정한 referer 제한이 무용지물이 되니 주의하기 바란다.

Django conf에 S3 storage class 추가

conf/asset_storage.py를 만들어서 아래와 같이 코드를 넣어준다

from storages.backends.s3boto3 import S3Boto3Storage

class MediaStorage(S3Boto3Storage):
    location = ''
    file_overwrite = False

이 때 location에 원하는 문구를 넣으면 해당 경로 하위로 모든 파일들이 생성된다.

 

Django model에 s3 정의해주기

파일을 업로드할 모델에 다음과 같이 설정해준다.

여기서 upload_to에 이름을 설정하면 db와 sotrage에 그에 맞게 저장된다.

 

📌 location을 dynamic하게 사용해서는 안된다!

static, media 이정도로 분기하는건 괜찮지만 날짜나 시간과 같은 값을 사용하기 위해서는 upload_to를 사용해야한다. 그렇지 않으면 생성될 떄는 문제가 없지만, 읽어들일때도 해당 날짜(시간)을 사용하기 떄문에 올바른 경로를 읽지 못한다. 그리고 location은 접두사로 붙기 떄문에 경로가 중복으로 들어간다 (저장할 때)media/2021 -> (읽을 때)media/2022/media/2021.

 

체크리스트

다음의 과정을 확인해보면된다.

✅ collectstatic시 로컬에 저장되지 않고 s3로 제대로 올라가는가?

✅ django 업로드시 s3로 제대로 올라가는가?

✅ 프론트 페이지에서 제대로 읽어 오는가? 권한 문제는 없는가? (Referer)

✅ django admin 페이지에서 제대로 읽어오는가? (ACL)

 

다 잘 되는가? 축하한다! 🎉

 

마무리하며

이번 글은 django 세팅보다 s3에 관해서 힘을 주어 작성하였다. django 부분은 upload_to를 제외하고는 다른 분들이 설명을 잘 해주셨지만 아무래도 ACL과 Bucket Policy에 대한 설명은 찾기 힘들어서 직접 작성해 보았다. 그래도 이렇게 정리하니 내가 한 작업에 대해서는 확실히 이해를 했다. 👍