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