[PYTHON] Ich habe es geschafft, weil der Brauch, eine Zip mit einem Passwort an eine E-Mail anzuhängen und "Ich werde Ihnen das Passwort separat senden" zu sagen, mühsam ist.

Dieser Brauch

Es ist üblich, eine Zip mit einem Passwort an eine E-Mail anzuhängen und zu sagen: "Das Passwort wird separat gesendet." Ich mache es nicht selbst, aber es ist ärgerlich, weil ich es laut der anderen Partei tun muss.

Hier spielen die Vor- und Nachteile dieser Methode keine Rolle. Egal wie viel ich predige, die Situation, diesen Brauch zu haben, ändert sich nicht.

Und ich denke nicht daran, diese Praxis zu brechen. Ich überlasse das etwas mit enormer Kraft.

Der alte Idiot sagte. "Wickeln Sie es um einen langen." Ich denke jedoch, dass es besser ist, darüber nachzudenken, wie man es aufwickelt.

Überlegen Sie, wie Sie intelligent aufwickeln können

Es gibt nur eine Sache, die ich lösen möchte, wenn es aufgerollt ist. Sei nicht verärgert. Wenn Sie zu diesem Zweck ein Websystem erstellen und den Browser öffnen, um so etwas zu tun, ist dies überwältigend. Ich möchte es so nah wie möglich an der normalen Postübertragung realisieren.

Nachdem ich darüber nachgedacht hatte, versuchte ich, es mit einem serverlosen Gefühl unter Verwendung von Amazon SES zu lösen, während ich einige Einschränkungen zuließ.

Spezifikation

  1. Schreiben Sie normal eine E-Mail (neu, antworten, weiterleiten)
  2. Werfen Sie die Datei so wie sie ist, ohne sie zu komprimieren
  3. Stellen Sie die E-Mail-Adresse für SES auf "An" und die Person ein, an die Sie die Datei tatsächlich an "Antworten an" senden möchten.
  4. Glauben Sie an das System und drücken Sie die Senden-Taste
  5. Sie und die andere Partei erhalten eine E-Mail mit einer an ein Passwort angehängten Postleitzahl und einer E-Mail mit einer Passwortbenachrichtigung.

Es gibt jedoch die folgenden Einschränkungen. Persönlich ist es akzeptabel.

Systemkonfiguration

flow_01.png

  1. Senden Sie eine E-Mail an SES
  2. Mail-Daten werden in S3 gespeichert
  3. Lambda beginnt damit als Auslöser
  4. Lambda analysiert den Inhalt der E-Mail und generiert ein Passwort und eine Zip-Datei
  5. Senden Sie eine nette E-Mail (senden Sie sie für alle Fälle mit Bcc an sich selbst)

Implementierung

Lambda Es ist das erste Mal, dass ich Python ernsthaft schreibe, aber ist es so in Ordnung? Es geht um einen Kampf zwischen E-Mail, Zeichencodes und Dateien.

# -*- 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):
    """
Mail-Analyseklasse
    (Referenz) http://qiita.com/sayamada/items/a42d344fa343cd80cf86
    """

    def __init__(self, email_string):
        """
Initialisieren
        """
        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 = []

        #Interpretation von eml
        self._parse()

    def get_attr_data(self):
        """
Mail-Daten abrufen
        """
        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):
        """
Mail-Dateien analysieren
        """

        #Analyse des Nachrichtenkopfteils
        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")

        #Extrahieren Sie nur die Zeichenfolge der E-Mail-Adresse
        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 des Nachrichtentextteils
        for part in self.email_message.walk():
            #Wenn der ContentType mehrteilig ist, ist der tatsächliche Inhalt noch größer
            #Da es sich im Innenteil befindet, überspringen Sie es
            if part.get_content_maintype() == 'multipart':
                continue
            #Dateinamen abrufen
            attach_fname = part.get_filename()
            #Sollte der Body sein, wenn es keinen Dateinamen gibt
            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:
                #Wenn es einen Dateinamen gibt, handelt es sich um eine angehängte Datei
                #Daten bekommen
                self.attach_file_list.append({
                    "name": attach_fname,
                    "data": part.get_payload(decode=True)
                })

    def _get_decoded_header(self, key_name):
        """
Ruft das dekodierte Ergebnis aus dem Header-Objekt ab
        """
        ret = ""

        #Schlüssel ohne das entsprechende Element geben leere Zeichen zurück
        raw_obj = self.email_message.get(key_name)
        if raw_obj is None:
            return ""
        #Machen Sie das dekodierte Ergebnis unicode
        for fragment, encoding in decode_header(raw_obj):
            if not hasattr(fragment, "decode"):
                ret += fragment
                continue
            #Wenn es keine Codierung gibt, UTF vorerst-Mit 8 dekodieren
            if encoding:
                ret += fragment.decode(encoding)
            else:
                ret += fragment.decode("UTF-8")
        return ret

class MailForwarder(object):

    def __init__(self, email_attr):
        """
Initialisieren
        """
        self.email_attr = email_attr
        self.encode     = 'utf-8'

    def send(self):
        """
Komprimieren Sie die angehängte Datei mit einem Kennwort, übertragen Sie sie und senden Sie eine Kennwortbenachrichtigungs-E-Mail
        """

        #Passwortgenerierung
        password = self._generate_password()

        #Generierung von Zip-Daten
        zip_name = datetime.now().strftime('%Y%m%d%H%M%S')
        zip_data = self._generate_zip(zip_name, password)

        #Senden Sie Zip-Daten
        self._forward_with_zip(zip_name, zip_data)

        #Passwort senden
        self._send_password(zip_name, password)

    def _generate_password(self):
        """
Passwortgenerierung
Mische, indem du jeweils 4 Buchstaben aus Symbolen, Buchstaben und Zahlen nimmst
        """
        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):
        """
Generieren Sie Daten für die Zip-Datei mit dem Passwort
        """
        tmp_dir  = "/tmp/" + zip_name
        os.mkdir(tmp_dir)

        #Speichern Sie die Datei lokal
        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()

        #Mit Passwort komprimieren
        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)

        # #Lesen Sie die generierte Zip-Datei
        r = open(dst_file_path, 'rb')
        zip_data = r.read()
        r.close()

        return zip_data

    def _forward_with_zip(self, zip_name, zip_data):
        """
Generieren Sie Daten für die Zip-Datei mit dem Passwort
        """
        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):
        """
Passwort für die Zip-Datei senden
        """

        subject = self.email_attr['subject']

        message = """
Dies ist das Kennwort für die Datei, die Sie zuvor gesendet haben.

[Gegenstand] {}
[Dateiname] {}.zip
[Passwort] {}
        """.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):
        """
E-Mail senden
        """

        msg = MIMEMultipart()

        #Header
        msg['Subject'] = subject
        msg['From']    = self.email_attr['from']
        msg['To']      = self.email_attr['reply_to']
        msg['Bcc']     = self.email_attr['from']

        #Text
        body = MIMEText(message, 'plain', self.encode)
        msg.attach(body)

        #Angehängte Datei
        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)

        #Senden
        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):
        """
Verschlüsselte Umgebungsvariablen entschlüsseln
        """

        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):

    #Abrufen des Bucket-Namens und des Schlüsselnamens vom Ereignis
    bucket = event['Records'][0]['s3']['bucket']['name']
    key = urllib.parse.unquote_plus(event['Records'][0]['s3']['object']['key'])

    try:
        #Lesen Sie den Inhalt der Datei aus S3
        s3_object = s3.get_object(Bucket=bucket, Key=key)
        email_string = s3_object['Body'].read().decode('utf-8')

        #E-Mail analysieren
        parser = MailParser(email_string)

        #Mail-Weiterleitung
        forwarder = MailForwarder(parser.get_attr_data())
        forwarder.send()
        return

    except Exception as e:
        print(e)
        raise e

pyminizip Es scheint, dass passwortgeschütztes Zip nicht mit einer Standardbibliothek durchgeführt werden kann. Daher habe ich mich nur hier auf eine externe Bibliothek namens pyminizip verlassen. Dies war jedoch eine Bibliothek, die zum Zeitpunkt der Installation erstellt wurde, um eine Binärdatei zu erstellen. Daher habe ich einen Docker-Container für Amazon Linux lokal eingerichtet, um ihn auf Lambda auszuführen, und eine Binärdatei erstellt. Gibt es einen anderen guten Weg? ..

AWS SAM Übrigens habe ich dies lokal mit AWS SAM getestet. Es war gut, bis ich versuchte, die Informationen des SMTP-Servers direkt zu schreiben, aber als ich sie in die Umgebungsvariable verschob, funktionierte sie nicht gut und ich war frustriert. Es sieht so aus, als ob es behoben, aber nicht veröffentlicht wurde.

Einführungsmethode

Ich werde es veröffentlichen, weil es eine große Sache ist. Codename zaru. Bitte verzeihen Sie mir, obwohl die Einstellmethode schlammig bleibt. .. https://github.com/Kta-M/zaru

Ich habe es nur in meiner Umgebung (Mac, Thunderbird) ausprobiert, daher funktioniert es je nach Mailer und anderen Umgebungen möglicherweise nicht. Bitte übernehmen Sie die Verantwortung für Ihre Handlungen.

SES SES ist in der Region Tokio noch nicht verfügbar, daher werden wir es in der Region Oregon (us-west-2) bauen.

Domainüberprüfung

Zunächst überprüfen wir die Domain, damit Sie E-Mails an SES senden können. Es gibt verschiedene Methoden, daher werde ich diesen Bereich weglassen. Dies kann beispielsweise hilfreich sein-> Domain-Mail mit Amazon SES / Route53 mit Rails senden

Regelerstellung

Erstellen Sie nach Überprüfung der Domäne eine Regel.

Klicken Sie unter "Regelsätze" auf der rechten Seite des Menüs auf "Aktiven Regelsatz anzeigen". ses_rule_01.png

Klicken Sie auf "Regel erstellen". ses_rule_02.png

Registrieren Sie die zu empfangende E-Mail-Adresse. Geben Sie die E-Mail-Adresse der verifizierten Domain ein und klicken Sie auf "Empfänger hinzufügen". ses_rule_03.png

Registrieren Sie die Aktion, wenn Sie eine E-Mail erhalten. Wählen Sie als Aktionstyp "S3" und geben Sie den Bucket zum Speichern der empfangenen E-Mail-Daten an. Wenn Sie zu diesem Zeitpunkt einen Bucket mit "S3-Bucket erstellen" erstellen, wird die erforderliche Bucket-Richtlinie automatisch registriert. Dies ist praktisch. Es wird eine Richtlinie festgelegt, die das Hochladen von Dateien von SES in den Bucket ermöglicht.

{
    "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"
                }
            }
        }
    ]
}

Außerdem können die im Bucket gespeicherten E-Mail-Daten gespeichert werden. Daher ist es möglicherweise besser, einen Lebenszyklus festzulegen, damit sie nach einer bestimmten Zeit gelöscht werden. ses_rule_04.png

Geben Sie der Regel einen Namen. Der Rest ist standardmäßig. ses_rule_05.png

Überprüfen Sie die Registrierungsdaten und registrieren Sie sich! ses_rule_06.png

Lambda

Bereitstellen

Bereitstellen in der Region Oregon sowie in SES. Da CloudFormation verwendet wird, erstellen Sie bitte einen S3-Bucket, um Daten hochzuladen.

# 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

Wenn Sie zur Lambda-Konsole gehen, wird die Funktion erstellt. Außerdem werden die IAM-Rollen erstellt, die zum Ausführen dieser Funktion erforderlich sind. lambda_01.png

Triggereinstellung

Stellen Sie Lambda auf Arbeit ein, indem Sie Mail-Daten auslösen, um in den Bucket zu gelangen.

Wechseln Sie im Bildschirm mit den Funktionsdetails zur Registerkarte Trigger. lambda_02.png

Klicken Sie auf "Trigger hinzufügen", um ein S3-Ereignis zu erstellen. Der Bucket, in dem die Daten von SES stammen, ist der Ereignistyp Put. Davon abgesehen ist dies die Standardeinstellung. Bucket isLamb_03.png

Verschlüsselungsschlüssel erstellen

In dieser Lambda-Funktion werden SMTP-bezogene Informationen aus der verschlüsselten Umgebungsvariablen abgerufen. Erstellen Sie einen Schlüssel für diese Verschlüsselung.

Klicken Sie in der IAM-Konsole unten links auf den Verschlüsselungsschlüssel. Ändern Sie die Region in Oregon und erstellen Sie einen Schlüssel. lambda_04.png

Sie müssen lediglich einen Alias Ihrer Wahl festlegen, der Rest ist standardmäßig in Ordnung. lambda_05.png

Festlegen der Anzahl der Umgebungsvariablen

Gehen Sie zurück zu Lambda und legen Sie die in der Funktion verwendeten Umgebungsvariablen fest. Am unteren Rand der Registerkarte Code befindet sich ein Formular zum Festlegen von Umgebungsvariablen. Aktivieren Sie "Verschlüsselungshilfe aktivieren" und geben Sie den zuvor erstellten Verschlüsselungsschlüssel an. Geben Sie für Umgebungsvariablen den Variablennamen und den Wert (Klartext) ein und klicken Sie auf die Schaltfläche "Verschlüsselung". Anschließend wird es mit dem angegebenen Verschlüsselungsschlüssel verschlüsselt. Die folgenden vier Umgebungsvariablen werden festgelegt.

Variablennamen Erläuterung Beispiel
SMTP_SERVER SMTP-Server smtp.example.com
SMTP_PORT SMTP-Port 587
SMTP_USER Benutzername, um sich beim SMTP-Server anzumelden [email protected]
SMTP_PASSWORD SMTP_Benutzer-Passwort

lambda_06.png

Rolleneinstellungen

Geben Sie der Rolle, die diese Lambda-Funktion ausführt, die erforderlichen Berechtigungen.

Gehen Sie zunächst zur "Richtlinie" der IAM-Konsole und erstellen Sie die folgenden beiden Richtlinien mit "Richtlinie erstellen" -> "Eigene Richtlinie erstellen". lambda_07.png

** Richtlinie: s3-get-object-zaru ** Geben Sie für den Bucket-Namen an, um E-Mail-Daten von SES zu empfangen.

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "Stmt1505586008000",
            "Effect": "Allow",
            "Action": [
                "s3:GetObject"
            ],
            "Resource": [
                "arn:aws:s3:::<ses-bucket-name>/*"
            ]
        }
    ]
}

** Richtlinie; kms-decrypt-zaru ** Geben Sie für "" die ARN des Verschlüsselungsschlüssels an.

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "Stmt1448696327000",
            "Effect": "Allow",
            "Action": [
                "kms:Decrypt"
            ],
            "Resource": [
                "<kms-arn>"
            ]
        }
    ]
}

Fügen Sie diese beiden Richtlinien schließlich der Lambda-Funktionsausführungsrolle hinzu. Gehen Sie zunächst in der IAM-Konsole zu "Rolle", wählen Sie eine Rolle aus und hängen Sie sie an "Richtlinie anhängen" an. lambda_08.png

Funktionsprüfung

Es sollte jetzt funktionieren. Bitte stellen Sie die für SES festgelegte E-Mail-Adresse in "An" und die E-Mail-Adresse der anderen Partei in "Antwort an" ein und senden Sie sie mit einer entsprechenden angehängten Datei. Wie ist das?

Zusammenfassung

Dontokoi Reißverschluss angebracht!

Recommended Posts

Ich habe es geschafft, weil der Brauch, eine Zip mit einem Passwort an eine E-Mail anzuhängen und "Ich werde Ihnen das Passwort separat senden" zu sagen, mühsam ist.
Wenn ihr in der Scope-Küche das mit einem Rand machen könnt ~ ♪
Machst du so etwas wie eine Rakete?
Wenn Sie einen Test mit DB mit django schreiben, ist es möglicherweise schneller, `setUpTestData ()` zu verwenden
Ich habe es geschafft, weil der Brauch, eine Zip mit einem Passwort an eine E-Mail anzuhängen und "Ich werde Ihnen das Passwort separat senden" zu sagen, mühsam ist.
Es ist eine Huckepack-Geschichte über den Dienst, der "Nyan" zurückgibt, wenn Sie Ping drücken
Mit der Docker-Version der Nginx-Einheit war es ein wenig schwierig, eine Flasche zu machen
Bis Sie Blender installieren und vorerst mit Python ausführen können
Das Geräusch von Mr. Tick am Arbeitsplatz ist ... Ich habe es mit dem Code geschafft
Es ist überraschend mühsam, eine Liste mit dem Datum und der Uhrzeit der letzten Anmeldung von Arbeitsbereichen abzurufen
Geben Sie den Bericht mit Python aus DB in PDF aus, hängen Sie ihn automatisch an eine E-Mail an und senden Sie ihn
Was tun, wenn Sie mit Python keine E-Mail an Yahoo senden konnten?
Ich möchte Google Mail mit Python senden, kann dies jedoch aufgrund eines Fehlers nicht
Ich möchte ein Element mit numpy in eine Datei schreiben und es überprüfen.
Mit der Docker-Version der Nginx-Einheit war es ein wenig schwierig, eine Flasche zu machen
Ich mag es nicht, mit der Veröffentlichung von Pokemon Go frustriert zu sein, deshalb habe ich ein Skript erstellt, um die Veröffentlichung zu erkennen und zu twittern
Ein Skript, das den registrierten Server anpingt und eine bestimmte Anzahl von E-Mails mit Google Mail sendet, wenn dies fehlschlägt
Ich dachte ein wenig nach, weil Trace Plot von Stan's Parameter schwer zu sehen ist
Notieren Sie sich, was Sie in Zukunft mit Razpai machen möchten
Was tun, wenn Sie eine Binärdatei katzen oder verfolgen und das Terminal verstümmelt ist?
[Python] Was ist ein Slice? Eine leicht verständliche Erklärung zur Verwendung anhand eines konkreten Beispiels
Ich habe versucht, das automatische Senden einer E-Mail durch Doppelklicken auf das Symbol [Python] zu ermöglichen
Was tun, wenn ein Teil des Hintergrundbilds transparent wird, wenn Sie transparente Bilder mit Pillow kombinieren?
Es ist mühsam, die Einstellungen zwischen Intranet und Geschäftsreise / zu Hause zu ändern. Daher war ich ein wenig glücklich, als ich mit Apache2 lokal einen Forward-Proxy eingerichtet habe.
Wenn es schwierig ist, das zu kopieren, was Sie mit vue erstellt haben
Ich habe ein POST-Skript erstellt, um ein Problem in Github zu erstellen und es im Projekt zu registrieren
Ist es möglich, ein Pre-Listing-Unternehmen zu gründen und mit Aktienoptionen ein Vermögen zu machen?
[AWS lambda] Stellen Sie mit lambda verschiedene Bibliotheken bereit (generieren Sie eine Zip-Datei mit einem Kennwort und laden Sie sie auf s3 hoch) @ Python
Da es der 20. Jahrestag der Gründung ist, habe ich versucht, die Texte von Parfüm mit Word Cloud zu visualisieren
Ich habe versucht, das automatische Senden einer E-Mail durch Doppelklicken auf das Symbol [GAS / Python] zu ermöglichen
Die Geschichte, ein Tool zum Laden von Bildern mit Python zu erstellen ⇒ Speichern unter
Ich habe ein npm-Paket erstellt, um die ID der IC-Karte mit Raspberry Pi und PaSoRi zu erhalten
Lesen Sie die Daten des NFC-Lesegeräts, das mit Python an Raspberry Pi 3 angeschlossen ist, und senden Sie sie mit OSC an openFrameworks
Laden Sie Daten mit einem Befehl und einer Aktualisierung auf s3 von aws hoch und löschen Sie die verwendeten Daten (unterwegs).