Il existe une coutume selon laquelle un zip avec un mot de passe est joint à un e-mail et "le mot de passe sera envoyé séparément". Je ne le fais pas moi-même, mais c'est ennuyeux parce que je dois le faire selon l'autre partie.
Ici, les avantages et les inconvénients de cette méthode n'ont pas d'importance. Peu importe combien je prêche, la situation d'avoir cette coutume ne change pas.
Et je ne pense pas à briser cette pratique. Je vais laisser cela à quelque chose avec un pouvoir énorme.
Dit le vieil idiot. "Enroulez-le autour d'un long." Cependant, je pense qu'il vaut mieux réfléchir à la façon de l'enrouler.
Il n'y a qu'une seule chose que je veux résoudre quand elle est enroulée. Ne sois pas ennuyé. Si vous créez un système Web à cet effet et ouvrez le navigateur pour faire quelque chose comme ça, ce sera écrasant. Je veux le réaliser aussi près que possible de la transmission de courrier normale.
Donc, après y avoir réfléchi, j'ai essayé de le résoudre avec un sentiment de sans serveur en utilisant Amazon SES tout en autorisant certaines restrictions.
Cependant, il existe les restrictions suivantes. Personnellement, c'est acceptable.
Cc
(je suis Bcc
)Lambda C'est la première fois que j'écris sérieusement python, mais est-ce que ça va comme ça? Il s'agit d'une bataille entre les e-mails, les codes de caractères et les fichiers.
# -*- coding: utf-8 -*-
import os
import sys
import string
import random
import json
import urllib.parse
import boto3
import re
import smtplib
import email
import base64
from email import encoders
from email.header import decode_header
from email.mime.base import MIMEBase
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.mime.image import MIMEImage
from datetime import datetime
sys.path.append(os.path.join(os.path.dirname(os.path.realpath(__file__)), 'vendored'))
import pyminizip
s3 = boto3.client('s3')
class MailParser(object):
"""
Classe d'analyse du courrier
(référence) http://qiita.com/sayamada/items/a42d344fa343cd80cf86
"""
def __init__(self, email_string):
"""
Initialisation
"""
self.email_message = email.message_from_string(email_string)
self.subject = None
self.from_address = None
self.reply_to_address = None
self.body = ""
self.attach_file_list = []
#Interprétation de eml
self._parse()
def get_attr_data(self):
"""
Obtenir des données de messagerie
"""
attr = {
"from": self.from_address,
"reply_to": self.reply_to_address,
"subject": self.subject,
"body": self.body,
"attach_files": self.attach_file_list
}
return attr
def _parse(self):
"""
Analyse des fichiers courrier
"""
#Analyse de la partie en-tête du message
self.subject = self._get_decoded_header("Subject")
self.from_address = self._get_decoded_header("From")
self.reply_to_address = self._get_decoded_header("Reply-To")
#Extraire uniquement la chaîne de caractères de l'adresse e-mail
from_list = re.findall(r"<(.*@.*)>", self.from_address)
if from_list:
self.from_address = from_list[0]
reply_to_list = re.findall(r"<(.*@.*)>", self.reply_to_address)
if reply_to_list:
self.reply_to_address = ','.join(reply_to_list)
#Analyse de la partie du corps du message
for part in self.email_message.walk():
#Si le ContentType est en plusieurs parties, le contenu réel est encore plus
#Puisqu'il est dans la partie intérieure, sautez-le
if part.get_content_maintype() == 'multipart':
continue
#Obtenir le nom du fichier
attach_fname = part.get_filename()
#Doit être le corps s'il n'y a pas de nom de fichier
if not attach_fname:
charset = str(part.get_content_charset())
if charset != None:
if charset == 'utf-8':
self.body += part.get_payload()
else:
self.body += part.get_payload(decode=True).decode(charset, errors="replace")
else:
self.body += part.get_payload(decode=True)
else:
#S'il y a un nom de fichier, c'est un fichier joint
#Obtenez des données
self.attach_file_list.append({
"name": attach_fname,
"data": part.get_payload(decode=True)
})
def _get_decoded_header(self, key_name):
"""
Obtenez le résultat décodé à partir de l'objet d'en-tête
"""
ret = ""
#Les clés qui n'ont pas l'élément correspondant renvoient des caractères vides
raw_obj = self.email_message.get(key_name)
if raw_obj is None:
return ""
#Rendre le résultat décodé unicode
for fragment, encoding in decode_header(raw_obj):
if not hasattr(fragment, "decode"):
ret += fragment
continue
#S'il n'y a pas d'encodage, UTF pour le moment-Décoder avec 8
if encoding:
ret += fragment.decode(encoding)
else:
ret += fragment.decode("UTF-8")
return ret
class MailForwarder(object):
def __init__(self, email_attr):
"""
Initialisation
"""
self.email_attr = email_attr
self.encode = 'utf-8'
def send(self):
"""
Compressez le fichier joint avec un mot de passe, transférez-le et envoyez un e-mail de notification de mot de passe
"""
#Génération de mot de passe
password = self._generate_password()
#génération de données zip
zip_name = datetime.now().strftime('%Y%m%d%H%M%S')
zip_data = self._generate_zip(zip_name, password)
#Envoyer des données zip
self._forward_with_zip(zip_name, zip_data)
#Envoyer le mot de passe
self._send_password(zip_name, password)
def _generate_password(self):
"""
Génération de mot de passe
Mélangez en prenant 4 lettres chacun parmi les symboles, les lettres et les chiffres
"""
password_chars = ''.join(random.sample(string.punctuation, 4)) + \
''.join(random.sample(string.ascii_letters, 4)) + \
''.join(random.sample(string.digits, 4))
return ''.join(random.sample(password_chars, len(password_chars)))
def _generate_zip(self, zip_name, password):
"""
Générer des données pour le fichier Zip avec mot de passe
"""
tmp_dir = "/tmp/" + zip_name
os.mkdir(tmp_dir)
#Enregistrez le fichier localement
for attach_file in self.email_attr['attach_files']:
f = open(tmp_dir + "/" + attach_file['name'], 'wb')
f.write(attach_file['data'])
f.flush()
f.close()
#Pour compresser avec mot de passe
dst_file_path = "/tmp/%s.zip" % zip_name
src_file_names = ["%s/%s" % (tmp_dir, name) for name in os.listdir(tmp_dir)]
pyminizip.compress_multiple(src_file_names, dst_file_path, password, 4)
# #Lire le fichier zip généré
r = open(dst_file_path, 'rb')
zip_data = r.read()
r.close()
return zip_data
def _forward_with_zip(self, zip_name, zip_data):
"""
Générer des données pour le fichier Zip avec mot de passe
"""
self._send_message(
self.email_attr['subject'],
self.email_attr["body"].encode(self.encode),
zip_name,
zip_data
)
return
def _send_password(self, zip_name, password):
"""
Envoyer le mot de passe du fichier zip
"""
subject = self.email_attr['subject']
message = """
Il s'agit du mot de passe du fichier que vous avez envoyé précédemment.
[matière] {}
[nom de fichier] {}.zip
[mot de passe] {}
""".format(subject, zip_name, password)
self._send_message(
'[password]%s' % subject,
message,
None,
None
)
return
def _send_message(self, subject, message, attach_name, attach_data):
"""
envoyer un e-mail
"""
msg = MIMEMultipart()
#entête
msg['Subject'] = subject
msg['From'] = self.email_attr['from']
msg['To'] = self.email_attr['reply_to']
msg['Bcc'] = self.email_attr['from']
#Texte
body = MIMEText(message, 'plain', self.encode)
msg.attach(body)
#Pièce jointe
if attach_data:
file_name = "%s.zip" % attach_name
attachment = MIMEBase('application', 'zip')
attachment.set_param('name', file_name)
attachment.set_payload(attach_data)
encoders.encode_base64(attachment)
attachment.add_header("Content-Dispositon", "attachment", filename=file_name)
msg.attach(attachment)
#Envoyer
smtp_server = self._get_decrypted_environ("SMTP_SERVER")
smtp_port = self._get_decrypted_environ("SMTP_PORT")
smtp_user = self._get_decrypted_environ("SMTP_USER")
smtp_password = self._get_decrypted_environ("SMTP_PASSWORD")
smtp = smtplib.SMTP(smtp_server, smtp_port)
smtp.ehlo()
smtp.starttls()
smtp.ehlo()
smtp.login(smtp_user, smtp_password)
smtp.send_message(msg)
smtp.quit()
print("Successfully sent email")
return
def _get_decrypted_environ(self, key):
"""
Décrypter les variables d'environnement chiffrées
"""
client = boto3.client('kms')
encrypted_data = os.environ[key]
return client.decrypt(CiphertextBlob=base64.b64decode(encrypted_data))['Plaintext'].decode('utf-8')
def lambda_handler(event, context):
#Obtenir le nom du bucket et le nom de la clé de l'événement
bucket = event['Records'][0]['s3']['bucket']['name']
key = urllib.parse.unquote_plus(event['Records'][0]['s3']['object']['key'])
try:
#Lire le contenu du fichier depuis S3
s3_object = s3.get_object(Bucket=bucket, Key=key)
email_string = s3_object['Body'].read().decode('utf-8')
#Analyser les e-mails
parser = MailParser(email_string)
#Réexpédition du courrier
forwarder = MailForwarder(parser.get_attr_data())
forwarder.send()
return
except Exception as e:
print(e)
raise e
pyminizip Il semble que le zip protégé par mot de passe ne puisse pas être fait avec une bibliothèque standard. Donc, je me suis appuyé sur une bibliothèque externe appelée pyminizip uniquement ici. Cependant, il s'agissait d'une bibliothèque qui a été créée au moment de l'installation pour créer un binaire, j'ai donc configuré un conteneur Docker pour Amazon Linux localement pour l'exécuter sur Lambda et créé un binaire. Y a-t-il un autre bon moyen? ..
AWS SAM En passant, j'ai testé cela localement en utilisant AWS SAM. C'était bien jusqu'à ce que j'essaye d'écrire directement les informations du serveur SMTP, mais quand je les ai déplacées vers la variable d'environnement, cela ne fonctionnait pas bien et j'étais frustré. Il semble qu'il a été corrigé mais pas publié.
Je vais le publier parce que c'est un gros problème. Nom de code zaru
.
Veuillez me pardonner si la méthode de réglage reste floue. ..
https://github.com/Kta-M/zaru
Je ne l'ai essayé que dans mon environnement (Mac, Thunderbird), donc cela peut ne pas fonctionner en fonction du mailer et d'autres environnements. Veuillez prendre la responsabilité de vos actions.
SES SES n'est pas encore disponible dans la région de Tokyo, nous allons donc le construire dans la région de l'Oregon (us-west-2).
Tout d'abord, nous vérifierons le domaine afin que vous puissiez envoyer des e-mails à SES. Il existe différentes méthodes, je vais donc omettre ce domaine. Par exemple, cela peut être utile-> Envoyer le courrier de domaine à l'aide d'Amazon SES / Route53 with Rails
Après avoir vérifié le domaine, créez une règle.
Dans Ensembles de règles
sur le côté droit du menu, cliquez sur Afficher le jeu de règles actif
.
Cliquez sur "Créer une règle".
Enregistrez l'adresse e-mail pour recevoir. Saisissez l'adresse e-mail du domaine vérifié et cliquez sur «Ajouter un destinataire».
Enregistrez l'action lors de la réception d'un e-mail.
Sélectionnez S3
comme type d'action et spécifiez le compartiment pour stocker les données de courrier reçues. À ce stade, si vous créez un compartiment avec Créer un compartiment S3
, la stratégie de compartiment requise sera enregistrée automatiquement, ce qui est pratique.
Une stratégie est définie qui autorise les téléchargements de fichiers de SES vers le compartiment.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowSESPuts-XXXXXXXXXXXX",
"Effect": "Allow",
"Principal": {
"Service": "ses.amazonaws.com"
},
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::<ses-bucket-name>/*",
"Condition": {
"StringEquals": {
"aws:Referer": "XXXXXXXXXXXX"
}
}
}
]
}
En outre, les données de messagerie enregistrées dans le compartiment peuvent être stockées, il peut donc être préférable de définir un cycle de vie afin qu'elles soient supprimées après un certain temps.
Donnez un nom à la règle. Le reste est par défaut.
Vérifiez les détails d'inscription et inscrivez-vous!
Lambda
Déployez dans la région de l'Oregon ainsi que dans SES. Étant donné que CloudFormation sera utilisé, veuillez créer un compartiment S3 pour télécharger des données.
# git clone [email protected]:Kta-M/zaru.git
# cd zaru
# aws cloudformation package --template-file template.yaml --s3-bucket <cfn-bucket-name> --output-template-file packaged.yaml
# aws cloudformation deploy --template-file packaged.yaml --stack-name zaru-stack --capabilities CAPABILITY_IAM --region us-west-2
Si vous accédez à la console Lambda, la fonction est créée. Il crée également les rôles IAM nécessaires pour exécuter cette fonction.
Configurez Lambda pour qu'il fonctionne en déclenchant l'entrée des données de messagerie dans le compartiment.
Accédez à l'onglet Déclenchement sur l'écran des détails de la fonction.
Cliquez sur «Ajouter un déclencheur» pour créer un événement S3. Le compartiment dont les données proviennent de SES, le type d'événement est Put. A part cela, c'est la valeur par défaut. Le seau est
Dans cette fonction Lambda, les informations relatives à SMTP sont obtenues à partir de la variable d'environnement chiffrée. Créez une clé à utiliser pour ce cryptage.
Depuis la console IAM, cliquez sur la «clé de chiffrement» en bas à gauche. Changez la région en Oregon et créez la clé.
Tout ce que vous avez à faire est de définir un alias de votre choix, et le reste est OK par défaut.
Revenez à Lambda et définissez les variables d'environnement utilisées dans la fonction. Au bas de l'onglet Code se trouve un formulaire pour définir les variables d'environnement. Cochez «Activer l'assistant de chiffrement» et spécifiez la clé de chiffrement que vous avez créée précédemment. Pour les variables d'environnement, entrez le nom et la valeur de la variable (texte brut) et appuyez sur le bouton «cryptage». Ensuite, il sera chiffré avec la clé de chiffrement spécifiée. Les quatre variables d'environnement suivantes sont définies.
Nom de variable | La description | Exemple |
---|---|---|
SMTP_SERVER | serveur smtp | smtp.example.com |
SMTP_PORT | port smtp | 587 |
SMTP_USER | Nom d'utilisateur pour se connecter au serveur smtp | [email protected] |
SMTP_PASSWORD | SMTP_Mot de passe de l'utilisateur |
Enfin, accordez au rôle qui exécute cette fonction Lambda les autorisations requises.
Tout d'abord, accédez à la stratégie
de la console IAM et créez les deux stratégies suivantes avec Créer une stratégie
-> Créer votre propre stratégie
.
** Politique: s3-get-object-zaru **
Pour «
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "Stmt1505586008000",
"Effect": "Allow",
"Action": [
"s3:GetObject"
],
"Resource": [
"arn:aws:s3:::<ses-bucket-name>/*"
]
}
]
}
** Politique; kms-decrypt-zaru **
Pour «
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "Stmt1448696327000",
"Effect": "Allow",
"Action": [
"kms:Decrypt"
],
"Resource": [
"<kms-arn>"
]
}
]
}
Enfin, attachez ces deux stratégies au rôle d'exécution de la fonction Lambda. Tout d'abord, allez dans «Rôle» dans la console IAM, sélectionnez un rôle et attachez-le à partir de «Attach Policy».
Cela devrait maintenant fonctionner. Veuillez définir l'adresse e-mail définie pour SES dans «À» et l'adresse e-mail de l'autre partie dans «Répondre à», et envoyez-la avec un fichier approprié en pièce jointe. Comment c'est?
Zip Dontokoi attaché!
Recommended Posts