import datetime import logging import os import traceback from abc import * from zoneinfo import ZoneInfo import boto3 import gnupg # 環境変数 SECRET_KEY_FILE_BUCKET_NAME = os.environ["SECRET_KEY_FILE_BUCKET_NAME"] SECRET_KEY_FILE_PATH = os.environ["SECRET_KEY_FILE_PATH"] SAP_SUP_BUCKET_NAME = os.environ["SAP_SUP_BUCKET_NAME"] SAP_FIN_BUCKET_NAME = os.environ["SAP_FIN_BUCKET_NAME"] SAP_DATA_BACKUP_BUCKET_NAME = os.environ["SAP_DATA_BACKUP_BUCKET_NAME"] DATA_SOURCE_SAP_SUP = os.environ["DATA_SOURCE_SAP_SUP"] DATA_SOURCE_SAP_FIN = os.environ["DATA_SOURCE_SAP_FIN"] NDS_NOTICE_TOPIC = os.environ["NDS_NOTICE_TOPIC"] NDS_NOTICE_TITLE = os.environ["NDS_NOTICE_TITLE"] LOG_LEVEL = os.environ["LOG_LEVEL"] # 定数 DIRECTORY_RECV = '/recv/' DIRECTORY_RECV_ERROR = '/recv_error/' EXTENSION_ERROR = '.error' EXTENSION_DECRYPT_ERROR = '.decrypt_error' INDEX_EXTENSION_DELETE_NUM = -4 PATH_TEMP = '/tmp' PATH_TEMP_PRIVATE_KEY = '/tmp/private.key' PATH_TEMP_ENCRYPT_FILE = '/tmp/encrypt_file' PATH_TEMP_DECRYPT_FILE = '/tmp/decrypt_file' TARGET_BUCKET_BY_DATA_SOURCE = { DATA_SOURCE_SAP_SUP: SAP_SUP_BUCKET_NAME, DATA_SOURCE_SAP_FIN: SAP_FIN_BUCKET_NAME } # 変数 s3_client = boto3.client('s3') s3_resource = boto3.resource('s3') sns_client = boto3.client('sns') # logger設定 logger = logging.getLogger() def custome_time(*arg): return datetime.datetime.now(ZoneInfo("Asia/Tokyo")).timetuple() formatter = logging.Formatter( '[%(levelname)s]\t%(asctime)s\t%(message)s\n', '%Y-%m-%d %H:%M:%S' ) formatter.converter = custome_time for handler in logger.handlers: handler.setFormatter(formatter) level = logging.getLevelName(LOG_LEVEL) if not isinstance(level, int): level = logging.INFO logger.setLevel(level) def handler(event, context): try: # ① 処理開始ログを出力する logger.info('I-01-01 処理開始 SAPデータ復号処理') execute_date = datetime.date.today().strftime('%Y/%m/%d') logger.info(f'I-01-02 処理稼働日:{execute_date}') # ② 処理開始時に受け取ったイベント情報の以下内容をログに出力しメモリに保持する logger.info('I-02-01 イベント情報出力処理') s3_event = S3EventInfo(event["Records"][0]["s3"]) logger.info(f'I-02-02 バケット名:{s3_event.bucket_name}') logger.info(f'I-02-03 ファイルパス:{s3_event.file_path}') logger.info(f'I-02-04 データソース名:{s3_event.data_source_name}') # ③ S3からPGP暗号化ファイルを読み込む try: logger.info(f'I-03-01 PGP暗号化ファイル読込 読込元:{s3_event.bucket_name}/{s3_event.file_path}') s3_client.download_file(s3_event.bucket_name, s3_event.file_path, PATH_TEMP_ENCRYPT_FILE) logger.info('I-03-02 PGP暗号化ファイルを読み込みました') except Exception as e: logger.error(f'E-03-01 PGP暗号化ファイルの読み込みに失敗しました エラー内容:{e}') raise EncryptFileReadException('E-03-01', EXTENSION_ERROR, e) # ④ S3から秘密鍵ファイルを読み込む try: logger.info(f'I-04-01 秘密鍵ファイル読込 読込元:{SECRET_KEY_FILE_BUCKET_NAME}/{SECRET_KEY_FILE_PATH}') s3_client.download_file(SECRET_KEY_FILE_BUCKET_NAME, SECRET_KEY_FILE_PATH, PATH_TEMP_PRIVATE_KEY) logger.info('I-04-02 秘密鍵ファイルを読み込みました') except Exception as e: logger.error(f'E-04-01 秘密鍵ファイルの読み込みに失敗しました エラー内容:{e}') raise FileReadException('E-04-01', EXTENSION_ERROR, e) # ⑤ 「③」で読み込んだ秘密鍵ファイルをPGPライブラリにインポートを行う try: logger.info('I-05-01 秘密鍵ファイルインポート') gpg = gnupg.GPG(gnupghome=PATH_TEMP) with open(PATH_TEMP_PRIVATE_KEY) as key_file: gpg.import_keys(key_file.read()) logger.info('I-05-02 秘密鍵ファイルをインポートしました') except Exception as e: logger.error(f'E-05-01 秘密鍵ファイルのインポートに失敗しました エラー内容:{e}') raise PrivateKeyImportException('E-05-01', EXTENSION_ERROR, e) # ⑥ 「④」で読み込んだPGP暗号化ファイルを「⑤」でインポートした秘密鍵ファイルで復号する try: logger.info('I-06-01 PGP暗号化ファイルの復号化') with open(PATH_TEMP_ENCRYPT_FILE, 'rb') as temp_encrypr_file: gpg.decrypt_file(temp_encrypr_file, output=PATH_TEMP_DECRYPT_FILE) decrypt_file = open(PATH_TEMP_DECRYPT_FILE, 'rb') logger.info('I-06-02 PGP暗号化ファイルを復号しました') except Exception as e: logger.error(f'E-06-01 PGP暗号化ファイルの復号化に失敗しました エラー内容:{e}') raise DecryptException('E-06-01', EXTENSION_DECRYPT_ERROR, e) # ⑦ 各ファイルをS3に出力する try: logger.info('I-07-01 各ファイルをS3に出力する') # 「⑥」で復号化したファイルをSAPデータ保管用バケットに出力する decrypt_file_name = s3_event.file_name[:INDEX_EXTENSION_DELETE_NUM] decrypt_file_key = f'{execute_date}/{decrypt_file_name}' decrypt_bucket_name = TARGET_BUCKET_BY_DATA_SOURCE[s3_event.data_source_name] logger.info(f'I-07-02 復号化ファイル出力 ファイル名:{decrypt_file_name} 出力先:{decrypt_bucket_name}/{decrypt_file_key}') decrypt_file_obj = s3_resource.Object(decrypt_bucket_name, decrypt_file_key) decrypt_file_obj.put(Body=decrypt_file) decrypt_file.close logger.info('I-07-03 復号化ファイルをS3に出力しました') except Exception as e: logger.error(f'E-07-01 復号化ファイルのS3出力に失敗しました エラー内容:{e}') raise FileOutputException('E-07-01', EXTENSION_ERROR, e) # 「④」で読み込んだPGP暗号化ファイルを以下に移動(コピー削除)する try: copy_source = { 'Bucket': s3_event.bucket_name, 'Key': s3_event.file_path } backup_file_key = f'{s3_event.data_source_name}/{execute_date}/{s3_event.file_name}' logger.info( f'I-07-04 PGP暗号化ファイル移動 移動元:{s3_event.bucket_name}/{s3_event.file_path} 移動先:{SAP_DATA_BACKUP_BUCKET_NAME}/{backup_file_key}') backup_file_obj = s3_resource.Object(SAP_DATA_BACKUP_BUCKET_NAME, backup_file_key) backup_file_obj.copy(copy_source) s3_client.delete_object(Bucket=s3_event.bucket_name, Key=s3_event.file_path) logger.info('I-07-05 PGP暗号化ファイルをバックアップ用バケットに移動しました') except Exception as e: logger.error(f'E-07-02 PGP暗号化ファイルのS3出力に失敗しました エラー内容:{e}') raise FileOutputException('E-07-02', EXTENSION_ERROR, e) # ⑧ 処理終了ログを出力する logger.info('I-08-01 処理終了 SAPデータ復号処理') except EncryptFileReadException as e: traceback.print_exc() create_status_file(s3_event, e.extension) error_notice(e.id, e.arg) except CustomException as e: traceback.print_exc() create_status_file(s3_event, e.extension) move_encrypt_file(s3_event) error_notice(e.id, e.arg) except Exception as e: logger.error(f'E-99 想定外のエラーが発生しました エラー内容:{e}') traceback.print_exc() create_status_file(s3_event, EXTENSION_ERROR) move_encrypt_file(s3_event) error_notice('E-99', e) return # エラーステータスファイル作成 def create_status_file(s3_event, extension) -> None: try: result_error_file_name = s3_event.file_name + extension result_error_key = s3_event.data_source_name + DIRECTORY_RECV + result_error_file_name result_error_obj = s3_resource.Object(s3_event.bucket_name, result_error_key) result_error_obj.put(Body='') logger.error( f'E-ERR-01 recvディレクトリにエラーファイルを作成しました ファイル名:{result_error_file_name} 出力先:{s3_event.bucket_name}/{result_error_key}') except Exception as e: logger.error(f'E-96 エラーステータスファイルの作成に失敗しました エラー内容:{e}') traceback.print_exc() return # PGP暗号化ファイル移動 def move_encrypt_file(s3_event) -> None: try: copy_source = { 'Bucket': s3_event.bucket_name, 'Key': s3_event.file_path } error_file_name = f'{datetime.datetime.now():%Y%m%d%H%M%S}_{s3_event.file_name}' error_key = s3_event.data_source_name + DIRECTORY_RECV_ERROR + error_file_name error_obj = s3_resource.Object(s3_event.bucket_name, error_key) error_obj.copy(copy_source) s3_client.delete_object(Bucket=s3_event.bucket_name, Key=s3_event.file_path) logger.error( f'E-ERR-02 recv_errorディレクトリにファイルを移動しました 移動元:{s3_event.bucket_name}/{s3_event.file_path} 移動先:{s3_event.bucket_name}/{error_key}') except Exception as e: logger.error(f'E-97 PGP暗号化ファイルの移動に失敗しました エラー内容:{e}') traceback.print_exc() return # 保守要員チーム通知 def error_notice(error_log_id, exception) -> None: try: error_msg = f'{error_log_id} のエラーが発生しました。ご確認ください\n詳細:{exception}' params = { 'TopicArn': NDS_NOTICE_TOPIC, 'Subject': NDS_NOTICE_TITLE, 'Message': error_msg } sns_client.publish(**params) logger.error(f'E-ERR-03 処理異常通知の送信指示をしました 通知先トピック:{NDS_NOTICE_TOPIC}') except Exception as e: logger.error(f'E-98 処理異常通知の送信指示に失敗しました エラー内容:{e}') traceback.print_exc() return # S3イベント情報を保持するクラス class S3EventInfo: def __init__(self, s3_event) -> None: self._bucket_name = s3_event["bucket"]["name"] self._file_path = s3_event["object"]["key"] self._file_name = os.path.basename(s3_event["object"]["key"]) self._data_source_name = os.path.dirname(s3_event["object"]["key"]).split('/')[0] @property def bucket_name(self): return self._bucket_name @property def file_path(self): return self._file_path @property def file_name(self): return self._file_name @property def data_source_name(self): return self._data_source_name # カスタムExceptionクラス class CustomException(Exception, metaclass=ABCMeta): def __init__(self, id, extension, arg): self.id = id self.extension = extension self.arg = arg class FileReadException(CustomException): pass class EncryptFileReadException(CustomException): pass class FileOutputException(CustomException): pass class PrivateKeyImportException(CustomException): pass class DecryptException(CustomException): pass