S3 上の画像を boto3 で圧縮後、Webページで Access Denied された

実現したいこと

AWS初心者です。

S3 をオリジンとした CloudFront を経由して、Webサイトで画像を配信しています。

このWebサイトは Java などで書かれていますが、
ストレージ軽量化のため、Python の boto3 + Pillow で S3 バケット上の画像を圧縮するスクリプトを作成しました。

この圧縮した画像をWebページにも反映させたいです。

なお、元の画像は残さず、置換することを希望しています。

発生している問題・分からないこと

Python の boto3 + Pillow で S3 上の画像を圧縮しました。

その後、Webページにアクセスすると、圧縮した画像のみ表示されなくなりました。
下記画像のように、画像アイコンと代替テキストのみ表示されました。

イメージ説明

この画像に対し、「新しいタブで画像を開く」と、以下のエラーメッセージが発生しました。

エラーメッセージ

error

1<Error> 2<Code>AccessDenied</Code> 3<Message>Access Denied</Message> 4<RequestId>requestid</RequestId> 5<HostId>hostid</HostId> 6</Error>

該当のソースコード

Python

1from io import BytesIO 2import json 3import time 4import os 5import urllib.parse 6from PIL import Image 7import boto3 8from datetime import datetime, timedelta, timezone 9 10REGION_NAME = 'ap-northeast-1' # tokyo11BUCKET_NAME = 'test'12COMPRESSION_RATE = 50 # 1 ~ 9513PREFIX = ''14 15def load_aws_credentials(file_path):16 # load AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY from json17 with open(file_path, 'r') as file:18 credentials = json.load(file)19 return credentials 20 21def access_s3_bucket(aws_access_key_id, aws_secret_access_key):22 # access to S3 with boto323 s3 = boto3.resource('s3',24 aws_access_key_id=aws_access_key_id,25 aws_secret_access_key=aws_secret_access_key,26 region_name=REGION_NAME 27 )28 s3_bucket = BUCKET_NAME 29 bucket = s3.Bucket(s3_bucket)30 return bucket 31 32def get_objects(bucket):33 objs = bucket.meta.client.list_objects_v2(34 Bucket=bucket.name,35 Prefix=PREFIX 36 )37 return objs 38 39def select_recent_files(objs):40 # get file keys modified last week41 one_week_ago = datetime.now(timezone.utc) - timedelta(days=7)42 file_keys = []43 for obj in objs.get('Contents'):44 # 画像ファイルのうち、直近1週間にアップロードされたものを絞り込む45 file_key = obj.get('Key')46 last_modified_time = obj.get('LastModified')47 last_modified_time_utc = last_modified_time.replace(tzinfo=timezone.utc)48 if file_key.lower().endswith(('.png', '.jpg', '.jpeg')):49 if one_week_ago <= last_modified_time_utc:50 file_keys.append(file_key)51 return file_keys 52 53def get_file_extension(file_key):54 # get ファイル拡張子55 file_paths = os.path.splitext(file_key)56 file_extension = file_paths[1][1:]57 if file_extension.lower() == "jpg":58 # jpg -> jpeg59 file_extension = "jpeg"60 return file_extension 61 62def extract_directory_from_file_path(file_path):63 return os.path.dirname(file_path)64 65def create_directory_if_not_exists(directory):66 if not os.path.exists(directory):67 os.makedirs(directory)68 69def compress_image(file_extension, obj, file_key):70 try:71 # 画像圧縮72 if file_key.lower().endswith("jpg"):73 # jpg -> jpeg74 file_key = file_key[:-3] + "jpeg"75 obj_body = BytesIO(obj['Body'].read())76 img = Image.open(obj_body)77 if file_extension == 'png':78 img = img.convert('RGB') # png -> jpeg79 bucket_folder_path = extract_directory_from_file_path(file_key)80 save_directory = f'images/{bucket_folder_path}'81 create_directory_if_not_exists(save_directory)82 save_path = f'images/{file_key}'83 img.save(save_path, quality=COMPRESSION_RATE)84 return save_path 85 except Exception as e:86 print(e)87 with open('error.txt', 'a') as f:88 f.write(str(e))89 raise e 90 91def empty_folder(directory):92 # 指定されたディレクトリ内のフォルダを走査93 for root, dirs, files in os.walk(directory):94 # フォルダ内のファイルを削除95 for file in files:96 file_path = os.path.join(root, file)97 os.remove(file_path)98 99def main():100 101 aws_credentials = load_aws_credentials('credentials.json')102 aws_access_key_id = aws_credentials.get('AWS_ACCESS_KEY_ID') # アクセスキー ID103 aws_secret_access_key = aws_credentials.get('AWS_SECRET_ACCESS_KEY') # シークレットアクセスキー104 105 bucket = access_s3_bucket(aws_access_key_id, aws_secret_access_key) # 画像フォルダ106 107 objs = get_objects(bucket) # 画像ファイル All108 109 file_keys = select_recent_files(objs) # 画像ファイル Selceted110 111 for file_key in file_keys:112 113 obj = bucket.Object(file_key).get()114 115 file_extension = get_file_extension(file_key)116 117 save_path = compress_image(file_extension, obj, file_key)118 119 try:120 bucket.upload_file(save_path, file_key)121 122 # upload successed123 print(f'compressed {file_key}\n')124 with open("compressed_files.txt", "a") as f:125 f.write(f'{file_key}\n')126 127 empty_folder('images')128 129 time.sleep(1)130 131 except Exception as e:132 # upload failed133 print(e)134 with open('error.txt', 'a') as f:135 f.write(str(e))136 raise e 137 138if __name__ == "__main__":139 main()

試したこと・調べたこと

上記の詳細・結果
  • Python は正常に動作し、S3 マネジメントコンソール上でも画像が圧縮されていることを確認しました。すなわち、アクセスキーは正しいと判断できました。
  • Python で操作していない画像は問題なく表示できます。
  • boto3.resource()boto3.client() に書き換え: 変化はありませんでした。
  • ブロックパブリックアクセス: 有効です。以前よりこの設定で運用しているため、変更する必要はないと判断しました。
  • バケットポリシー: 設定していません。
  • アクセスコントロールリスト (ACL): バケット所有者 (AWS アカウント)に読み取り、書き込み権限
  • KMS によるデフォルトの暗号化: 該当する KMS はありません。
  • CloudFront のオリジンアクセス情報: Public
  • CloudFront キャッシュ削除: 変化はありませんでした。
  • CloudTrail 内のAssumeRole イベント: 該当するイベントはありません。

主に以下を参考にしました。

S3の403 Access Deniedを解決する時の覚書
CloudFront×S3で403 Access Deniedが出るときに確認すべきこと
Python boto3 s3 get_bucket_tagging : Access Denied when used in a loop
【手順】CloudFrontのキャッシュをクリアしよう
Amazon S3 からの 403 Access Denied エラーをトラブルシューティングするにはどうすればよいですか?
CloudFront ディストリビューションのオリジンとして S3 ウェブサイトのエンドポイントを使用しています。「403 Access Denied」(403 アクセス拒否) エラーが発生するのはなぜですか?

自身も調査を継続し、進捗があれば報告いたします。
何卒よろしくお願いいたします。

補足

OS: Windows 11 Home
言語: Python 3.12.2

コメントを投稿

0 コメント