278 lines
12 KiB
Python
278 lines
12 KiB
Python
import datetime
|
||
import logging
|
||
import os
|
||
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/'
|
||
DIRECTORY_TARGET = '/target/'
|
||
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')
|
||
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.exception(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.exception(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, gpgbinary="/usr/bin/gpg", verbose=True)
|
||
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.exception(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.exception(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}')
|
||
s3_client.put_object(Bucket=decrypt_bucket_name, Key=decrypt_file_key, Body=decrypt_file)
|
||
decrypt_file.close()
|
||
logger.info('I-07-03 復号化ファイルをS3に出力しました')
|
||
except Exception as e:
|
||
logger.exception(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}')
|
||
s3_client.copy_object(Bucket=SAP_DATA_BACKUP_BUCKET_NAME, Key=backup_file_key, CopySource=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.exception(f'E-07-02 PGP暗号化ファイルのS3出力に失敗しました エラー内容:{e}')
|
||
raise FileOutputException('E-07-02', EXTENSION_ERROR, e)
|
||
# 「⑥」で復号化したファイルをデータ取込用バケットに出力する
|
||
try:
|
||
copy_source = {
|
||
'Bucket': decrypt_bucket_name,
|
||
'Key': decrypt_file_key
|
||
}
|
||
import_file_folder = f'{s3_event.data_source_name}{DIRECTORY_TARGET}'
|
||
import_file_key = f'{import_file_folder}{decrypt_file_name}'
|
||
logger.info(f'I-07-06 復号化ファイル出力 ファイル名:{decrypt_file_name} 出力先:{s3_event.bucket_name}/{import_file_folder}')
|
||
s3_client.copy_object(Bucket=s3_event.bucket_name, Key=import_file_key, CopySource=copy_source)
|
||
logger.info(f'I-07-07 復号化ファイルをS3に出力しました')
|
||
except Exception as e:
|
||
logger.exception(f'E-07-03 復号化ファイルのS3出力に失敗しました エラー内容:{e}')
|
||
raise FileOutputException('E-07-02', EXTENSION_ERROR, e)
|
||
# ⑧ 処理終了ログを出力する
|
||
logger.info('I-08-01 処理終了 SAPデータ復号処理')
|
||
except EncryptFileReadException as e:
|
||
create_status_file(s3_event, e.extension)
|
||
error_notice(e.id, e.arg)
|
||
except CustomException as e:
|
||
create_status_file(s3_event, e.extension)
|
||
move_encrypt_file(s3_event)
|
||
error_notice(e.id, e.arg)
|
||
except Exception as e:
|
||
logger.exception(f'E-99 想定外のエラーが発生しました エラー内容:{e}')
|
||
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
|
||
s3_client.put_object(Bucket=s3_event.bucket_name, Key=result_error_key, Body='')
|
||
logger.error(
|
||
f'E-ERR-01 recvディレクトリにエラーファイルを作成しました ファイル名:{result_error_file_name} 出力先:{s3_event.bucket_name}/{result_error_key}')
|
||
except Exception as e:
|
||
logger.exception(f'E-96 エラーステータスファイルの作成に失敗しました エラー内容:{e}')
|
||
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
|
||
s3_client.copy_object(Bucket=s3_event.bucket_name, Key=error_key, CopySource=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.exception(f'E-97 PGP暗号化ファイルの移動に失敗しました エラー内容:{e}')
|
||
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.exception(f'E-98 処理異常通知の送信指示に失敗しました エラー内容:{e}')
|
||
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
|