Quay lại

Upload Files Vào S3 Từ EC2 Instance Với IAM Role Cho Laravel App Production Chuyên mục PHP và Laravel    2024-05-05    9 Lượt xem    7 Lượt thích    comment-3 Created with Sketch Beta. 0 Bình luận

Upload Files Vào S3 Từ EC2 Instance Với IAM Role Cho Laravel App Production
   

Xin chào tất cả mọi người hôm nay mình sẽ hướng dẫn các bạn sử dụng s3 để lưu trữ files, với bài viết này chúng ta sẽ sử dụng 2 cách để xử lý lưu trữ.

Cách thứ nhất là sử dụng Multipart upload để upload những files lớn lên s3 và cách thứ 2 là sử dụng File upload để xử lý lưu trữ files với kích thước vừa và nhỏ lên s3.

Trên thực tế chúng ta sẽ có 2 cách thông dụng để có thể truyền files lên s3 đó là sử dụng một IAM User và lấy secret key và access key của nó để có thể  để quản lý và truy cập vào s3, cách còn lại là sử dụng IAM Roles để phục vụ cho môi trường production khi bạn chạy ứng dụng trên một môi trường EC2 hoặc Lambda, ở bài viết này mình sẽ hướng dẫn sử dụng cách IAM Roles để phục vụ cho môi trường production nhé !

Để thực hiện được với s3 mình có một số yêu cầu sau:

  • Có tài khoản AWS.
  • Có hiểu biết cơ bản về IAM và policies.

Bước 1: Tạo Bucket

  1. Đăng nhập vào AWS Console: Đăng nhập vào AWS Console bằng tài khoản có đủ quyền để tạo và quản lý bucket trên Amazon S3.

  2. Điều hướng đến trang S3: Trong AWS Console, chọn dịch vụ "S3" từ menu tìm kiếm hoặc menu dịch vụ.

  3. Tạo một Bucket mới:

    • Nhấp vào nút "Create bucket".
    • Nhập "bucket name" cho dự án của bạn. Lưu ý rằng tên bucket phải là duy nhất trên toàn hệ thống S3 và không được thay đổi sau khi đã tạo.
    • Chọn khu vực lưu trữ (region) cho bucket. Chọn khu vực gần với người dùng của bạn để giảm độ trễ khi truy cập.
    • Ở bước Block public access (bucket settings) => Bỏ tích Block all public access
    • Đảm bảo các tùy chọn bổ sung như logging và versioning được cấu hình theo nhu cầu của bạn (optinal). Nếu bạn bật versioning thì tên file phải đảm bảo giống nhau, không cần phải thêm timestamp hoặc bất kỳ phần tử duy nhất nào khác vào tên file khi upload lên S3 khi bạn sử dụng versioning.

  4. Thiết lập quyền truy cập cho Bucket: Bucket policy được sử dụng để cung cấp quyền truy cập các tài nguyền của s3 cho các users như (IAM user, nhóm người dùng, hoặc end users) có quyền truy cập và thực hiện các hành động đọc, ghi, xóa hoặc thay đổi các metadata trên các object đó, chính vì vậy để những files như images, video của chúng ta có thể truy cập ngoài internet thì chúng ta phải cấu hình policy.

    Bucket policy có thể áp dụng cho một số đối tượng khác nhau:

    • Người dùng IAM (Identity and Access Management): Bạn có thể sử dụng bucket policy để điều chỉnh quyền truy cập vào các đối tượng trong bucket cho các người dùng IAM trong tài khoản AWS của bạn.

    • Các tài khoản AWS khác: Bạn cũng có thể cho phép các tài khoản AWS khác (không phải là tài khoản của bạn) truy cập vào các đối tượng trong bucket của bạn thông qua bucket policy.

    • Public Access: Bạn có thể cấu hình bucket policy để cho phép mọi người, bao gồm cả người dùng không đăng nhập (anonymous users), truy cập vào các đối tượng trong bucket của bạn.

    • Các bước thực hiện như sau:

    1. Vào trong bucket của bạn vừa tạo sau đó chọn tab "Permissions" kéo xuống và tìm đến "Bucket policy" sau đó thì chỉnh sửa policy theo đoạn policy dưới đây, nhớ là thay tên bucket của bạn vào phần Resource. 
      Note: Nếu trong ứng dụng của bạn không có tính năng cho phép người dùng upload ảnh thì không cần phải thêm action "s3:GetObject"
    2. {
          "Version": "2012-10-17",
          "Id": "Policy1710384852296",
          "Statement": [
              {
                  "Sid": "Stmt1710384850357",
                  "Effect": "Allow",
                  "Principal": "*",
                  "Action": [
                      "s3:GetObject",
                      "s3:PutObject"
                  ],
                  "Resource": "arn:aws:s3:::your-bucket-name/*"
              }
          ]
      }

Bước 2: Thiết Lập IAM Roles

  1. Đăng nhập vào AWS Console: Đăng nhập vào AWS Console bằng tài khoản có đủ quyền để thực hiện các thay đổi về IAM role và S3.

  2. Tạo một Policy cho IAM Role:

    • Chúng ta nên custom một policy mới cho user này để chúng ta dễ kiểm soát những actions của user trên tài nguyên s3 thay vì chọn một cái "AmazonS3FullAccess" nha:
      • Click vào "Policies".
      • Sau đó chọn "Create policy".
      • Sau click vào tab "JSON" để tạo policy từ một định dạng JSON.
      • Copy IAM policy dưới đây và paste vào "Policy editor":
        {
            "Version": "2012-10-17",
            "Statement": [
                {
                    "Sid": "VisualEditor0",
                    "Effect": "Allow",
                    "Action": "s3:ListBucket",
                    "Resource": "arn:aws:s3:::your_bucket"
                },
                {
                    "Sid": "VisualEditor1",
                    "Effect": "Allow",
                    "Action": "s3:ListAllMyBuckets",
                    "Resource": "*"
                },
                {
                    "Effect": "Allow",
                    "Action": [
                        "s3:GetObject",
                        "s3:DeleteObject",
                        "s3:GetObjectAcl",
                        "s3:PutObjectAcl",
                        "s3:PutObject"
                    ],
                    "Resource": [
                        "arn:aws:s3:::your_bucket/*"
                    ]
                }
            ]
        }
      • Điều chỉnh các quyền truy cập (actions) và tài nguyên (resources) theo nhu cầu của bạn sau đó thì chọn "Next".
      • Bước cuối cùng nhập "Policy name" và gắn policy này cho IAM role.
  3. Tạo một IAM Role mới:

    • Trong AWS Console, điều hướng đến trang IAM.
    • Chọn "Roles" từ menu bên trái và nhấn vào nút "Create Role".
    • Chọn service là "AWS service" và "Use case" là EC2.
    • Bước tiếp theo ở trong thanh search hãy tìm đến IAM Policy mà bạn vừa tạo.
    • Sau đó thì đặt tên cho IAM role của bạn, và click vào create Role

Bước 3 Tạo Instance Và Gắn IAM Role

  1. Tạo Instance EC2:

    • Vì đây là chạy cho production nên chúng ta đã available Instance rồi nên mình không hướng dẫn chi tiết nữa, các bạn chỉ cần làm theo các bước, trong trường hợp dự án của bạn đã tích hợp rồi thì bỏ qua nhé!
  2. Gán IAM Role:

    • Sau khi bạn đã tạo instance, trong EC2 console, chọn instance mà bạn vừa tạo.
    • Trong tab "Actions" ở phía trên, chọn "Security" và sau đó chọn "Modify IAM Role".
    • Chọn IAM Role mà bạn muốn gán cho instance và nhấp vào nút "Update IAM Role".
  3. Kiểm tra EC2 đã kết nối đến S3 hay chưa:
    1. Cài đặt aws package để kiểm tra: Link
    2. Kiểm tra bằng lệnh dưới đây:
      1. aws s3 ls s3://BUCKET-NAME
    3. Lưu ý: Chúng ta không cần phải config AWS CLI trên EC2 instance nhé! nó sẽ tự động kết nối rồi, dưới local chúng ta mới cần phải config AWS CLI.

Bước 4: Sử Dụng Trong Laravel

  1. Cài đặt AWS SDK cho PHP: Sử dụng Composer để cài đặt AWS SDK cho PHP vào dự án Laravel của bạn. Bạn có thể thực hiện điều này bằng cách chạy lệnh sau trong terminal: 

    1. aws/aws-sdk-php-laravel

      composer require aws/aws-sdk-php-laravel
    2. league/flysystem-aws-s3-v3

      composer require league/flysystem-aws-s3-v3
  2. Thiết lập biến môi trường: Đảm bảo bạn đã thiết lập các biến môi trường trong tập tin .env của Laravel:

    1. Vì chúng ta đã kết nối EC2 và S3 thông qua IAM Role rồi nên Access Key và Secret Access Key chúng ta KHÔNG cần nữa và để trống những vẫn phải nhập Region và Bucket Name.

      AWS_ACCESS_KEY_ID=""
      AWS_SECRET_ACCESS_KEY=""
      AWS_DEFAULT_REGION=your_bucket_s3_region
      AWS_BUCKET=your_bucket_s3_name
      AWS_USE_PATH_STYLE_ENDPOINT=false
  3. Tạo Route và Controller: Tạo một route và một controller để xử lý việc upload file từ form và lưu trữ nó trên Amazon S3.

  4. Viết code xử lý: Mình tạo ra một Trait riêng để xử lý việc upload files lên s3 để mình tái sử dụng nó, và mình sử dụng nó trong services, các bạn có thể copy functions của mình dưới đây sau đó thì sử dụng trong file helpers cũng được sau đó thì tùy biến cho phù hợp nếu thực sự cần thiết, cái cốt lỗi ở đây là cách xử lý, còn tùy vào các dự án mà các bạn dùng cho phù hợp, mình đã có mô tả chi tiết về từng functions bên dưới, hoặc các bạn vào github của mình để xem cách xử lý nhé!

    <?php
    
    namespace App\Traits;
    
    use Illuminate\Support\Facades\Storage;
    use Symfony\Component\HttpFoundation\File\UploadedFile;
    use Aws\S3\MultipartUploader;
    use Aws\Exception\MultipartUploadException;
    use Aws\Exception\AwsException;
    use Aws\S3\S3Client;
    
    trait FileUploadTrait
    {
        /**
         * upLoadObjectToS3
         *
         * @param  UploadedFile $objectFile
         * @param  string $directoryName
         * @param  string $folder
         * @return array
         */
        public function upLoadObjectToS3(string $folder, UploadedFile $objectFile, string $directoryName): array
        {
            $initial = [
                'status' => false,
                'fileName' => '',
                'filePath' => ''
            ];
            if ($objectFile) {
                $fileName = $this->getFileName($objectFile);
                $path = $folder . '/' . $directoryName . '/' . $fileName;
                Storage::disk('s3')->put($path, file_get_contents($objectFile), 's3');
                $initial = [
                    'status' => true,
                    'fileName' => $fileName,
                    'filePath' => $this->getObjectUrlFromS3($path),
                ];
            }
            return $initial;
        }
    
        /**
         * multipartUploaderToS3
         * 
         * use this function if uploading file size 100MB to 5GB
         * @param  UploadedFile $objectFile
         * @param  string $directoryName
         * @return array
         */
        public function multipartUploaderToS3(string $folder, UploadedFile $objectFile, string $directoryName): array
        {
            $initial = [
                'status' => false,
                'fileName' => '',
                'filePath' => ''
            ];
            if ($objectFile) {
                $fileName = $this->getFileName($objectFile);
                $path = $folder . '/' . $directoryName . '/' . $fileName;
                $contents = fopen($objectFile, 'rb');
    
                $s3 = new S3Client([
                    'version' => 'latest',
                    'region'  => env('AWS_DEFAULT_REGION')
                ]);
    
                // Use MultipartUploader to upload files to S3
                $uploader = new MultipartUploader($s3, $contents, [
                    'bucket' => env('AWS_BUCKET'),
                    'key'    => $path,
                ]);
    
                try {
                    $result = $uploader->upload();
                    $initial = [
                        'status' => true,
                        'fileName' => $fileName,
                        'filePath' => $result['ObjectURL'],
                    ];
                    return $initial;
                } catch (MultipartUploadException $e) {
                    $initial['message'] = 'Failed to upload file';
                } catch (AwsException $e) {
                    $initial['message'] = 'AWS error: ' . $e->getMessage();
                }
            }
    
            return $initial;
        }
    
        /**
         * getUrlFromS3
         *
         * @param string|array $pathFile
         * @return void
         */
        public function getObjectUrlFromS3(string|array $pathFile): string|array
        {
            $initial = [
                'status' => true,
                'filePath' => null
            ];
            if (empty($pathFile)) {
                return '';
            }
            if (is_array($pathFile)) {
                $arrPathExist = [];
                foreach ($pathFile as $item) {
                    if (Storage::disk('s3')->exists($item)) {
                        $arrPathExist[] = Storage::disk('s3')->url($item);
                    }
                }
    
                if(!empty($arrPathExist)) {
                    $initial['filePath'] = $arrPathExist;
                    return $initial;
                }
            }
            if (is_string($pathFile) && Storage::disk('s3')->exists($pathFile)) {
                $url = Storage::disk('s3')->url($pathFile);
                $initial['filePath'] = $url;
                return $initial;
            }
        }
    
        /**
         * deleteObjectS3
         *
         * @param string|array $pathFile
         * @return void
         */
        public function deleteObjectS3(string|array $pathFile): array
        {
            $initial = [
                'status' => false,
            ];
            if (empty($pathFile)) {
                return $initial;
            }
            if (is_array($pathFile)) {
                $arrPathExist = [];
                foreach ($pathFile as $item) {
                    if (Storage::disk('s3')->exists($item)) {
                        $arrPathExist[] = $item;
                    }
                }
                if (!empty($arrPathExist)) {
                    $data = Storage::disk('s3')->delete($arrPathExist);
                    $initial['status'] = $data;
                }
            }
            if (is_string($pathFile) && (Storage::disk('s3')->exists($pathFile))) {
                $data = Storage::disk('s3')->delete($pathFile);
                $initial['status'] = $data;
            }
    
            return $initial;
        }
    
        /**
         * getFileName
         *
         * @param object $objectFile
         * @return string
         */
        private function getFileName(UploadedFile $objectFile): string
        {
            $originName = $objectFile->getClientOriginalName();
            $fileName = pathinfo($originName, PATHINFO_FILENAME);
            $extension = $objectFile->getClientOriginalExtension();
            $fileName = $fileName . '_' . time() . '.' . $extension;
    
            return $fileName;
        }
    }
    

Như vậy là mình đã hướng dẫn xong rồi mong rằng nó giúp được một phần nào đó trong dự án của các bạn.


Có thể bạn chưa biết?

IAM policy và bucket policy là hai loại chính sách trong Amazon S3 được sử dụng để quản lý quyền truy cập vào các tài nguyên S3, nhưng chúng có một số điểm khác nhau. Dưới đây là sự giống và khác nhau giữa chúng:

Giống nhau:

  1. Quản lý quyền truy cập: Cả hai loại policy đều được sử dụng để quản lý quyền truy cập vào các tài nguyên S3, bao gồm các bucket và các đối tượng trong bucket.

  2. Sử dụng cú pháp JSON: Cả hai loại policy đều được viết bằng cú pháp JSON để xác định các quyền hạn và tài nguyên mà người dùng được phép truy cập.

Khác biệt:

  1. Phạm vi áp dụng:

    • IAM policy được gắn với người dùng, nhóm người dùng hoặc vai trò trong AWS IAM, và xác định quyền hạn của họ trên tất cả các tài nguyên trong tài khoản AWS.
    • Bucket policy được gắn với một bucket cụ thể trong Amazon S3 và xác định quyền truy cập vào các đối tượng trong bucket đó.
  2. Người được ảnh hưởng:

    • IAM policy ảnh hưởng đến người dùng, nhóm người dùng hoặc vai trò mà nó được gắn vào.
    • Bucket policy ảnh hưởng đến tất cả các người dùng và tài khoản AWS khác nhau mà cố gắng truy cập vào các đối tượng trong bucket.
  3. Quyền hạn chi tiết:

    • IAM policy thường cung cấp các quyền hạn chi tiết cho từng người dùng hoặc vai trò cụ thể, cho phép bạn tùy chỉnh quyền truy cập một cách linh hoạt.
    • Bucket policy thường được sử dụng để cấu hình quyền truy cập "toàn bộ hoặc không", điều chỉnh quyền truy cập cho tất cả các người dùng hoặc tài khoản AWS được liên kết với bucket.

Khi nào sử dụng:

  • IAM policy: Sử dụng IAM policy khi bạn muốn điều chỉnh quyền truy cập của người dùng cụ thể, nhóm người dùng hoặc vai trò trong tài khoản AWS của bạn.

  • Bucket policy: Sử dụng bucket policy khi bạn muốn quản lý quyền truy cập vào các đối tượng trong một bucket cụ thể và áp dụng chúng cho tất cả các người dùng hoặc tài khoản AWS được liên kết với bucket đó.


Ví dụ trong trường hợp chúng ta đã cấu hình một IAM policy cho user cho phép hành động "s3:DeleteObject", nhưng không cấp quyền cho hành động này trong Bucket policy, thì user đó vẫn có thể thực hiện hành động xoá đối tượng (delete object).

Nguyên lý hoạt động là khi bạn cung cấp quyền truy cập cho một người dùng thông qua IAM policy, thì quyền truy cập đó sẽ được ưu tiên hơn so với quyền truy cập được cấu hình trong bucket policy. Bucket policy chỉ được áp dụng khi không có IAM policy nào ứng với người dùng đó, hoặc nếu IAM policy không cung cấp quyền truy cập cho một hành động cụ thể.

Vì vậy, trong trường hợp của bạn, nếu user được cấp quyền s3:DeleteObject thông qua IAM policy, thì user đó vẫn có thể xoá các đối tượng trong bucket, bất kể bucket policy có cấm hành động này hay không.

Bình luận (0)

Michael Gough
Michael Gough
Michael Gough
Michael Gough
Michael Gough
Michael Gough
Michael Gough
Michael Gough
Michael Gough
Michael Gough
Michael Gough
Michael Gough
Michael Gough
Michael Gough
Michael Gough
Michael Gough

Bài viết liên quan

Learning English Everyday