서승완
Builds for 1 pipeline passed in 29 minutes 56 seconds

feat: add s3 events lambda function

deprecate cloudfront from file download due to cache issue
add lambda function which triggered when s3 put events
update file size to database when upload
update file name in s3 metadata when rename
...@@ -23,7 +23,7 @@ BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) ...@@ -23,7 +23,7 @@ BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
23 SECRET_KEY = 'wfs076redt^-5_*dw6!_dqz*2z!a_#()33y@r_q7&+4r_40h9$' 23 SECRET_KEY = 'wfs076redt^-5_*dw6!_dqz*2z!a_#()33y@r_q7&+4r_40h9$'
24 24
25 # SECURITY WARNING: don't run with debug turned on in production! 25 # SECURITY WARNING: don't run with debug turned on in production!
26 -DEBUG = True 26 +DEBUG = False
27 27
28 ALLOWED_HOSTS = ['127.0.0.1', 'khubox-api.khunet.net'] 28 ALLOWED_HOSTS = ['127.0.0.1', 'khubox-api.khunet.net']
29 29
...@@ -49,7 +49,7 @@ MIDDLEWARE = [ ...@@ -49,7 +49,7 @@ MIDDLEWARE = [
49 'django.contrib.auth.middleware.AuthenticationMiddleware', 49 'django.contrib.auth.middleware.AuthenticationMiddleware',
50 'django.contrib.messages.middleware.MessageMiddleware', 50 'django.contrib.messages.middleware.MessageMiddleware',
51 'django.middleware.clickjacking.XFrameOptionsMiddleware', 51 'django.middleware.clickjacking.XFrameOptionsMiddleware',
52 - 'khubox.auth.AuthMiddleware', 52 + 'khubox.middlewares.auth.AuthMiddleware',
53 ] 53 ]
54 54
55 ROOT_URLCONF = 'config.urls' 55 ROOT_URLCONF = 'config.urls'
...@@ -129,9 +129,6 @@ STATIC_URL = '/static/' ...@@ -129,9 +129,6 @@ STATIC_URL = '/static/'
129 129
130 # Custom Settings 130 # Custom Settings
131 S3_BUCKET = 'khubox-files' 131 S3_BUCKET = 'khubox-files'
132 -CDN_PATH = 'https://khubox-files.khunet.net'
133 -CLOUDFRONT_KEY_ID = 'APKAJ3FOBWI34OZJTXJQ'
134 -CLOUDFRONT_KEY_PRIVATE = '-----BEGIN RSA PRIVATE KEY-----\nMIIEowIBAAKCAQEA18VtzURs+fQev5L00LRwRbJaObQI4kfJCIsOE7eWSOqq4Akh\nA7fI6vs3z8orXBvgc+k6GgAHrIdNwckxoQuTsCxrTDm1104qy1T4JkVxkIBYHJgH\nGzKUloK5IqdmcYbOK7IQeHJ2gR9Mv/3oKUytJSsrbM9k4oLrsxGpyEuJeHIg28aP\nwhoVWmBGcPu48l4aYAZEVY7LZRJSOQ9y7Lf8FS1u7Xtw1P91gEaqrqVXqRWY02C8\nsixpJJuiAPnM3rpcpVNlAaPdDkWmaWYJoJDOlce7Dmx1a9Ckr24krM//vpEljurC\nGml0AsHpL8LE9msM5VA+miCxCz/K+wDgm2xvvQIDAQABAoIBAFmP3pLceyuJVBYK\n5smWjB+x91eKTkG2sFB2f8JZau0bUxApWeXULHa1DiaW8UaLX7BdN7vBFW5cvz7X\nx1zklEoFNghuz/btwD+kJlikbI4hZ/F+fTyh0yFiY3xp5dDrtrpWcBW+1UeleVMc\nDnjOFfSepajFsUeANlue0k2MZSRz34s2T2scV5ZkooqdXddYUF/wDhefYm6uvCgI\nPyvY/mbJTyhte/xagY/m6yzk5gxgad0qP2ZZrHhLLMlJ/GEZToWDxD2xUei61NQT\nFFc5ZutkAE6fVb3I4SJUBSX5fl0tTMz4Aak1GP2phMhjZyjYnQMq8kvL4BNFb7gp\nary8W/UCgYEA+eKkfjjlPsEx6yHMhD7pAwy/MpUqJmF5LMxIG+qfd+GZMQ2oucn5\npUAwBHP7BD6E9H7/7jdjnCiO+iPrzM9vNLfqsdCtPWzoFYJp/6Fv002uX8seNYvJ\nQyQqrM85LYIghhnkcmJMA8GR/Iu5ZEeE2BkAl9T2EKclzmB62d/ki6MCgYEA3Q0V\nz08IEwSJW+jEsOM+XGg2YkNqCVKGQD9n4CPx0TFVJxfqFl2nVwlN2hfrlJLUQ9+l\nfXnS5AW3tE88t9we+ea0saJZEqqlm/rGsfTV/twS9cWSgvG5fTzhUbu9/ElMU29L\nmydQfWTvCup7zCuQtgwM5ZRtPwuKsI8urUg6zR8CgYAt0coZvvMCI8i0dbkbkrGF\nNqQkcUeOTBc9CKQ8QjRFdh9x6DBFCOz2ySNE3cNsTs5wSo1BL/Ta4HD/GvEU2ABr\nKUImor3xYnPX5dbr4b0wgLD1rbf3V49q+Um98C1q086E6GCEPNP1aFwNc81lvtt0\nCHmcXZdVDGEZS4WbR7uPgwKBgBO/moY12lPQoPDsH75p3uVkjg9DVJLWo5XT1FTr\nASyeSqw+b7Rl05BsDV+BqZNRdtNFhMRsANJMTHg4aAVJDh9nZBdGmMyZIEiKI/w8\nEm49fRgl+YvnSpoMuViS/EswxTfjBo8q+P7q6IxCHKNF9Ry+gNx14TizsEVL1XC3\ntkEjAoGBAMyp7wdPobJMXcclRVq6rqHs9OMcnZAveVKyxNgDbZu4OB5X4xTxGEYT\nNZQ0MFf/HcwlnH7797gVQeqF9dlqUJYe+Fc8lc/Rcwta/4R5uMgri9t8RKN91YKF\nUUFBsDEkWlkoAmfPkcrrq9cLJlmSNt3ehQj4p5iAJwoVBXXa++PO\n-----END RSA PRIVATE KEY-----'
135 132
136 133
137 # Cors 134 # Cors
......
1 import boto3 1 import boto3
2 -import datetime
3 -from botocore.signers import CloudFrontSigner
4 -from cryptography.hazmat.backends import default_backend
5 -from cryptography.hazmat.primitives import hashes, serialization
6 -from cryptography.hazmat.primitives.asymmetric import padding
7 from django.conf import settings 2 from django.conf import settings
8 3
9 4
10 -def rsa_signer(message): 5 +def sign_download(file_id):
11 - private_key = serialization.load_pem_private_key( 6 + s3 = boto3.client('s3')
12 - settings.CLOUDFRONT_KEY_PRIVATE.encode('ascii'), 7 + signed_url = s3.generate_presigned_url(
13 - password=None, 8 + 'get_object',
14 - backend=default_backend() 9 + Params={'Bucket': settings.S3_BUCKET, 'Key': file_id},
10 + ExpiresIn=3600,
11 + HttpMethod='GET'
15 ) 12 )
16 - return private_key.sign(message, padding.PKCS1v15(), hashes.SHA1())
17 -
18 -
19 -def sign_download(url):
20 - expire_date = datetime.datetime.utcnow() + datetime.timedelta(hours=1)
21 - cloudfront_signer = CloudFrontSigner(settings.CLOUDFRONT_KEY_ID, rsa_signer)
22 - signed_url = cloudfront_signer.generate_presigned_url(url, date_less_than=expire_date)
23 return signed_url 13 return signed_url
24 14
25 15
...@@ -52,3 +42,13 @@ def s3_copy(file_id, new_file_id): ...@@ -52,3 +42,13 @@ def s3_copy(file_id, new_file_id):
52 } 42 }
53 obj = bucket.Object(str(new_file_id)) 43 obj = bucket.Object(str(new_file_id))
54 obj.copy(copy_source) 44 obj.copy(copy_source)
45 +
46 +
47 +def s3_update_and_return_size(file_id, name):
48 + s3 = boto3.resource('s3')
49 + s3_object = s3.Object(settings.S3_BUCKET, file_id)
50 + s3_object.copy_from(CopySource={'Bucket': settings.S3_BUCKET, 'Key': file_id},
51 + ContentType='application/octet-stream',
52 + ContentDisposition='attachment; filename=%s' % name,
53 + MetadataDirective='REPLACE')
54 + return s3_object.content_length
......
1 +import os
2 +import django
3 +from .aws import s3_delete, s3_update_and_return_size
4 +from .models import File
5 +
6 +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings")
7 +django.setup()
8 +
9 +
10 +def process_upload(event, context):
11 + file_id = event['Records'][0]['s3']['object']['key']
12 + file = File.objects.filter(id=file_id, deleted_at__isnull=True)
13 +
14 + # File Gone
15 + if len(file) == 0:
16 + s3_delete([file_id])
17 + return
18 +
19 + # Update
20 + file[0].size = s3_update_and_return_size(file_id, file[0].name)
21 + file[0].save()
1 import json 1 import json
2 import uuid 2 import uuid
3 -from django.conf import settings
4 from django.utils import timezone 3 from django.utils import timezone
5 -from ..aws import sign_upload, sign_download, s3_copy, s3_delete 4 +from pathvalidate import sanitize_filename
5 +from ..aws import sign_upload, sign_download, s3_copy, s3_delete, s3_update_and_return_size
6 from ..models import File, GroupUser 6 from ..models import File, GroupUser
7 7
8 8
...@@ -91,7 +91,7 @@ def create(request): ...@@ -91,7 +91,7 @@ def create(request):
91 owner_group_id=parent[0].owner_group_id, 91 owner_group_id=parent[0].owner_group_id,
92 uploader_id=request.user_id, 92 uploader_id=request.user_id,
93 type=received['type'], 93 type=received['type'],
94 - name=received['name'], 94 + name=sanitize_filename(received['name']),
95 size=0, 95 size=0,
96 created_at=timezone.now() 96 created_at=timezone.now()
97 ) 97 )
...@@ -178,8 +178,7 @@ def find_item(request, file_id): ...@@ -178,8 +178,7 @@ def find_item(request, file_id):
178 178
179 # Return File 179 # Return File
180 if file[0].type == 'file': 180 if file[0].type == 'file':
181 - download_url = '%s/%s' % (settings.CDN_PATH, file[0].id) 181 + download_url = sign_download(file[0].id)
182 - download_url = sign_download(download_url)
183 data = { 182 data = {
184 'id': file[0].id, 183 'id': file[0].id,
185 'parent_id': file[0].parent_id, 184 'parent_id': file[0].parent_id,
...@@ -273,7 +272,8 @@ def update_item(request, file_id): ...@@ -273,7 +272,8 @@ def update_item(request, file_id):
273 if 'name' in received: 272 if 'name' in received:
274 if received['name'] == '': 273 if received['name'] == '':
275 return {'result': False, 'error': '이름을 제대로 입력해주세요.'} 274 return {'result': False, 'error': '이름을 제대로 입력해주세요.'}
276 - file[0].name = received['name'] 275 + file[0].name = sanitize_filename(received['name'])
276 + s3_update_and_return_size(file_id, file[0].name)
277 if 'parent_id' in received: 277 if 'parent_id' in received:
278 file[0].parent_id = received['parent_id'] 278 file[0].parent_id = received['parent_id']
279 if 'is_public' in received: 279 if 'is_public' in received:
......
...@@ -17,6 +17,7 @@ idna==2.9 ...@@ -17,6 +17,7 @@ idna==2.9
17 importlib-metadata==1.6.0 17 importlib-metadata==1.6.0
18 jmespath==0.10.0 18 jmespath==0.10.0
19 -e git+http://khuhub.khu.ac.kr/2016104129/kappa.git@1b0e17bb6da7460d9d494828c682a5ef66736aa3#egg=kappa 19 -e git+http://khuhub.khu.ac.kr/2016104129/kappa.git@1b0e17bb6da7460d9d494828c682a5ef66736aa3#egg=kappa
20 +pathvalidate==2.3.0
20 pip-tools==5.1.2 21 pip-tools==5.1.2
21 placebo==0.9.0 22 placebo==0.9.0
22 pycparser==2.20 23 pycparser==2.20
......
...@@ -10,6 +10,17 @@ ...@@ -10,6 +10,17 @@
10 "vpc_config": { 10 "vpc_config": {
11 "SubnetIds": ["subnet-02d51e69"], 11 "SubnetIds": ["subnet-02d51e69"],
12 "SecurityGroupIds": ["sg-09c30d37d6a504f5c"] 12 "SecurityGroupIds": ["sg-09c30d37d6a504f5c"]
13 - } 13 + },
14 + "events": [
15 + {
16 + "function": "khubox.lambda.process_upload",
17 + "event_source": {
18 + "arn": "arn:aws:s3:::khubox-files",
19 + "events": [
20 + "s3:ObjectCreated:Put"
21 + ]
22 + }
23 + }
24 + ]
14 } 25 }
15 } 26 }
......