[PYTHON] Geben Sie eine signierte URL mit AWS SQS aus

AWS S3 hat eine "signierte URL". SQS kann etwas Ähnliches wie diese Funktion tun.

Wann benutzt du es?

Angenommen, Sie möchten ein solches System erstellen.

device_image.png

Betrachten Sie die Konfiguration in AWS.

** <Konfiguration A: API-Gateway + Lambda> **

Lambda + API Gateway hat den Ruf, billig zu sein, kann jedoch bei der Bearbeitung einer großen Anzahl von Anfragen recht teuer sein.

talk_image_gateway.png

Es ist eine grobe Berechnung, aber wenn es 172,8 Millionen Anfragen pro Tag gibt, ist die Gebühr für das Sammeln von Daten wie folgt.

Bedienung Preis pro Million Anfragen Tägliche Gebühr Bedingungen
Lambda Kommt auf die Bedingungen an 387 USD Durchschnittliche 1-Sekunden-Verarbeitung, Speicherzuweisung 128 MB
Api Gateway 4.25 USD 731 USD REST API
SQS 0.4 USD 69 USD

Gesamt: 1187 USD pro Tag

Mit Lambda-Verzögerungen, WAF, CloudFront, Authentifizierung und mehr geht es sogar noch höher.

** <Konfiguration B: AWS IoT + Rule Engine + SQS> **

Der Preis ist niedriger, wenn die Konfiguration mit der Regelengine von AWS IoT auf SQS übertragen werden soll. Aber dieses mal ...

Bedienung Preis pro Million Anfragen Tägliche Gebühr Bedingungen
AWS IoT Senden: 1.2 USD
Regel: 0.18 USD
Aktion: 0.18 USD
268 USD Regel+Handeln Sie
SQS 0.4 USD 69 USD

Gesamt: 337 USD pro Tag

talk_image_iot.png

Diesmal ist MQTT aufgrund des Ports nicht möglich, und WebSocket ist auch aufgrund der Terminalseite nicht möglich.

** **

Übrigens, wenn Sie direkt vom Terminal an SQS senden können, kostet es ungefähr 1/20, 69 Dollar pro Tag. Es liegt ein Sicherheitsproblem vor, da das AWS-SDK nicht verwendet werden kann. Etwas mehr als das ...

talk_image_sqs.png

Es ist wahrscheinlich Sorya.

** <Konfiguration D: SQS + signierte URL> **

Wenn Sie sorgfältig darüber nachdenken, können Sie es anscheinend gut machen, wenn Sie eine Authentifizierung ohne SDK durchführen und diese direkt an SQS senden können.

Um einen solchen Mechanismus zu erstellen, werden wir eine mit SQS + signierte URL realisieren.

Abschlusszeichnung

draw.png

** Die auszuführende Verarbeitung und der Ausführungszeitpunkt von jedem sind wie folgt **

wird bearbeitet Ziel zeitliche Koordinierung
SQS-signierte URL abrufen Terminal → API-Gateway Einmal alle 15 Minuten ausführen
Terminalstatus an SQS senden Terminal → SQS Alle 5 Sekunden einmal ausführen

** Die von SQS ausgelösten Daten werden diesmal wie folgt angenommen **

Zu sendende Daten Dateninhalt
Name der SQS-Warteschlange sqs-send-request-test-0424
Muster der zu übertragenden Daten Open/Offen (Terminal an, menschliches Gefühl an)
Close/Offen (Terminal aus, menschliches Gefühl an)
Open/Schließen (Terminal ein, menschliches Gefühl aus)
Close/Schließen (Terminal aus, menschliches Gefühl aus)
* Senden Sie eine von 4 Möglichkeiten

SQS-signierte URL abrufen (einmal alle 15 Minuten ausführen)

Fordern Sie eine signierte URL vom API-Gateway an [^ 1]

[^ 1]: * Die API-Gateway-Authentifizierung wird dieses Mal weggelassen. Wenn Sie sich authentifizieren möchten, geben Sie die Authentifizierungsinformationen in den Header der übertragenen Daten ein.

Terminalseitige Anfrage


curl "https://xxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/url" -s -X POST \
-d '{"que_name":"sqs-send-request-test-0424", "patterns":["Open/Open","Close/Open","Open/Close","Close/Close"]}'

Wenn die Anforderung erfolgreich ist, erhalten Sie die gleiche Anzahl signierter URLs wie das Datenmuster.

Terminalseite empfangene Daten


{
    "url": {
        "Open/Open": "https://sqs.ap-northeast-1.amazonaws.com/xxxxxx/sqs-send-request-test-0424?AWSAccessKeyId=xxxxx&Action=SendMessage&MessageBody=Open%2FOpen&SignatureMethod=HmacSHA256&SignatureVersion=2&Timestamp=2020-04-30T10%3A42%3A54&Version=2012-11-05&Signature=xxxxxxxxxxxxxxxxxxxxx", 
        "Close/Open": "https://sqs.ap-northeast-1.amazonaws.com/xxxxxx/sqs-send-request-test-0424?AWSAccessKeyId=xxxxx&Action=SendMessage&MessageBody=Close%2FOpen&SignatureMethod=HmacSHA256&SignatureVersion=2&Timestamp=2020-04-30T10%3A42%3A54&Version=2012-11-05&Signature=xxxxxxxxxxxxxxxxxxxxx",
        "Open/Close": "https://sqs.ap-northeast-1.amazonaws.com/xxxxxx/sqs-send-request-test-0424?AWSAccessKeyId=xxxxxx&Action=SendMessage&MessageBody=Open%2FClose&SignatureMethod=HmacSHA256&SignatureVersion=2&Timestamp=2020-04-30T10%3A42%3A54&Version=2012-11-05&Signature=xxxxxxxxxxxxxxxxxxxxx", 
        "Close/Close": "https://sqs.ap-northeast-1.amazonaws.com/xxxxxx/sqs-send-request-test-0424?AWSAccessKeyId=xxxxxx&Action=SendMessage&MessageBody=Close%2FClose&SignatureMethod=HmacSHA256&SignatureVersion=2&Timestamp=2020-04-30T10%3A42%3A54&Version=2012-11-05&Signature=xxxxxxxxxxxxxxxxxxxxx"
    }
}

Terminalstatus an SQS senden (einmal alle 5 Sekunden ausführen)

Die zu sendende URL sind die Daten der Daten ["url"] [$ {Status, den Sie senden möchten}]. Im Beispiel wird "Öffnen / Schließen" gesendet.

Terminalseitige Anfrage


curl "https://sqs.ap-northeast-1.amazonaws.com/xxxxxx/sqs-send-request-test-0424?AWSAccessKeyId=xxxxxx&Action=SendMessage&MessageBody=Open%2FClose&SignatureMethod=HmacSHA256&SignatureVersion=2&Timestamp=2020-04-30T10%3A42%3A54&Version=2012-11-05&Signature=xxxxxxxxxxxxxxxxxxxxx"

Jedes Mal, wenn Sie es ausführen, wird eine $ {Status, den Sie senden möchten} -Nachricht im festgelegten Thema registriert. Im Beispiel ist "Öffnen / Schließen" in "sqs-send-request-test-0424" registriert.

capture_2.png

Implementierung

Stellen Sie als vorläufige Vorbereitung eine Warteschlange bei SQS auf. Geben Sie den Namen der gewünschten Warteschlange ein und klicken Sie unten auf dem Bildschirm auf "Quick Create Queue".

capture_5.png

Erstellen Sie außerdem einen IAM-Benutzer für SQS. Setzen Sie AmazonSQSFullAccess auf die Richtlinie. Es sind keine anderen Berechtigungen als SQS erforderlich.

capture_3.png

Rufen Sie nach dem Erstellen des IAM-Benutzers die Zugriffsschlüssel-ID und den geheimen Zugriffsschlüssel ab.

Implementieren Sie Lambda

Erstellen Sie Lambda mit Python 3.8.

Stellen Sie die Lambda-Umgebungsvariablen wie folgt ein.

Umgebungsvariablenschlüssel Zu setzender Wert
AWS_ACCOUNT_NUMBER AWS-Konto-ID-Nummer
(Die 12-stellige Nummer unten links auf dem IAM-Bildschirm)
SQS_ACCOUNT_ID IAM-Benutzerzugriffsschlüssel-ID für SQS
SQS_SECRET_KEY Geheimer Zugriffsschlüssel für IAM-Benutzer, die SQS gewidmet sind

Fügen Sie die folgende Quelle in Lambda ein.

Lambda



# coding: utf-8
import boto3, json, hashlib, hmac, base64, os 
from datetime import datetime, timezone, timedelta
from urllib import parse as url_encode

#Signaturinformationen (SHA256-Format, Signaturversion ist 2)
SIGNATURE_METHOD = "HmacSHA256"
SIGNATURE_VERSION = "2"
ENCODE = "utf-8"

#Informationen zur SQS-Methode (Nachricht mit REST API GET an Queue senden)
HTTP_METHOD = "GET"
SQS_METHOD = "SendMessage"
AWS_VERSION = "2012-11-05"

#Zeitzone
UTC_TIMEZONE = timezone(timedelta(hours = 0), 'UTC')

class Credentials:
    """
Legen Sie die Anmeldeinformationen des IAM-Benutzers fest
    """
    @staticmethod
    def from_iam():
        instance = Credentials()
        instance.aws_access_key = os.environ.get("SQS_ACCOUNT_ID", DEFAULT.SQS_ACCOUNT_ID)
        instance.aws_secret_key = os.environ.get("SQS_SECRET_KEY", DEFAULT.SQS_SECRET_KEY)
        return instance

class Endpoint:
    """
Legen Sie die SQS-Endpunktinformationen fest
    """
    def __init__(self, topic_name):
        self.protocol = "https"
        self.host_name = "sqs.{}.amazonaws.com".format(os.environ.get("AWS_REGION", DEFAULT.AWS_REGION))
        self.url_path = "/{}/{}".format(os.environ.get("AWS_ACCOUNT_NUMBER", DEFAULT.AWS_ACCOUNT_NUMBER), topic_name)

    @property    
    def url(self):
        return f"{self.protocol}://{self.host_name}{self.url_path}"

#Erstellen Sie eine SQS-signierte URL
def create_presigned_url(credential, endpoint, message):

    #Holen Sie sich das aktuelle Datum und die aktuelle Uhrzeit mit UTC und konvertieren Sie es in eine Zeichenfolge
    current_date = datetime.now().astimezone(UTC_TIMEZONE)
    current_date_str = url_encode.quote(
        current_date.strftime('%Y-%m-%dT%H:%M:%S')
    )

    #Erstellen Sie einen Hash mit einem geheimen Zugriffsschlüssel basierend auf den zu sendenden Daten
    return endpoint.url + "?" + create_query(SQS_METHOD, message, credential.aws_access_key, current_date_str, option = {
        #Da die Hash-Daten ein Byte-Array sind, codieren Sie URL + base64, damit sie von GET gesendet werden können.
        "Signature" : url_encode.quote(
            base64.b64encode(
                sign(
                    #Geben Sie den geheimen Zugriffsschlüssel an, der zum Signieren verwendet werden soll
                    credential.aws_secret_key.encode(ENCODE), 
                    #Geben Sie die Übertragungsdaten an (die angegebenen Daten werden mit SHA256 gehasht).
                    create_certificate(endpoint, SQS_METHOD, message, credential.aws_access_key, current_date_str)
                )
            )
        )
    })

#Hash mit geheimem Zugangsschlüssel
def sign(key, msg):
    return hmac.new(key, msg.encode(ENCODE), hashlib.sha256).digest()

#Erstellen Sie Signaturdaten im v2-Format
#Beziehen Sie sich auf das offizielle Dokument für das Format (https)://docs.aws.amazon.com/ja_jp/general/latest/gr/signature-version-2.html)
def create_certificate(endpoint, method, message_body, aws_access_key, current_date_str):
    return "\n".join([
        HTTP_METHOD,
        endpoint.host_name,
        endpoint.url_path,
        create_query(method, message_body, aws_access_key, current_date_str)
    ])

#Abfragedaten normalisieren
#Beziehen Sie sich auf das offizielle Dokument für das Format (https)://docs.aws.amazon.com/ja_jp/general/latest/gr/signature-version-2.html)
def create_query(method, message_body, aws_access_key, current_date_str, option = None):
    query_map = {
        "AWSAccessKeyId" : aws_access_key,
        "Action" : method,
        "MessageBody" : message_body,
        "SignatureMethod" : SIGNATURE_METHOD,
        "SignatureVersion" : SIGNATURE_VERSION,
        "Timestamp" : current_date_str,
        "Version" : AWS_VERSION
    }

    #Die Signatur sollte nur beim Senden nicht in den Hash aufgenommen werden
    #Fügen Sie der Abfrage Daten hinzu, wenn Signatur angegeben ist
    if option is not None:
        query_map.update(option)

    #In das GET-Abfrageformat konvertieren
    return "&".join([
        f"{key}={value}"
        for key, value in query_map.items()
    ])

#Erstellen Sie so viele signierte URLs, wie Sie benötigen
def request(credential, endpoint, data_patterns):
    url = {}
    for pattern in data_patterns:
        url[pattern] = create_presigned_url(credential, endpoint, url_encode.quote(pattern, safe = ""))
    return {
        "url" : url
    }

#POST-Daten von APIGateway-Argumenten (HTTP-API) abrufen (Daten sind auf der APIGateway-Seite base64-codiert)
def get_payload_from_event(event):
    payload_str = ""
    if event["isBase64Encoded"]:
        payload_str = base64.b64decode(event["body"].encode(ENCODE))
    else:
        payload_str = event["body"]
    return json.loads(payload_str)

#Einstiegspunkt beim Ausführen von Lambda
def lambda_handler(event, context):
    payload = get_payload_from_event(event)
    return {
        'statusCode': 200,
        'body': json.dumps(request(Credentials.from_iam(), Endpoint(payload["que_name"]), payload["patterns"]))
    }

# ---------------------------------------------
#Das Folgende ist für die lokale Ausführung
#Geben Sie an, wenn Sie mit einem anderen als Lambda arbeiten
# ---------------------------------------------

#Variablen für die lokale Ausführung
class _Default:
    def __init__(self):
        self.SQS_ACCOUNT_ID = "" #Geben Sie die IAM-Zugriffsschlüssel-ID an
        self.SQS_SECRET_KEY = "" #Geben Sie den geheimen IAM-Schlüssel an
        self.AWS_ACCOUNT_NUMBER = "" #Geben Sie die AWS-Konto-ID an
        self.AWS_REGION = "ap-northeast-1" #Geben Sie die Region an, in der sich SQS befindet
        self.SQS_QUEUE_NAME = "" #Geben Sie den Namen der Zielwarteschlange an
DEFAULT = _Default()

#Einstiegspunkt beim Ausführen des Debugings in der lokalen Umgebung
if __name__ == "__main__":
    print(json.dumps(request(Credentials.from_iam(), Endpoint(DEFAULT.SQS_QUEUE_NAME), [
        "Open/Open",  "Close/Open",  "Wait/Open",
        "Open/Close", "Close/Close", "Wait/Close",
        "Open/Wait",  "Close/Wait",  "Wait/Wait"
    ])))

Legen Sie das erstellte Lambda als Back-End des API-Gateways fest.

capture_6.png

Wenn es in Ordnung ist, wenn Sie sich bewegen, ist es in Ordnung, wenn Sie so weit lesen. Nach der Bereitstellung von API Gateway sollte es genau wie die "fertige Zeichnung" funktionieren.

Von hier an wird es eine detaillierte Geschichte sein.

Detaillierte Geschichte

Ursprünglich verfügt SQS über einen Mechanismus zum Senden von Nachrichten per GET oder POST, ohne das AWS-SDK zu durchlaufen.

Referenz: Stellen Sie eine Abfrage-API-Anfrage https://docs.aws.amazon.com/ja_jp/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-making-api-requests.html

Als anonymer Benutzer an die öffentliche Warteschlange senden

Die einfachste Anforderungsmethode ist wie folgt. Wenn Sie auf der Referenzsite auf die URL klicken, wird eine Nachricht (Daten) an die Warteschlange (MyQueue) gesendet. [^ 2]

[^ 2]: * Version ist die Version von SQS. Vorerst können Sie es senden, ohne an irgendetwas zu denken.

https://sqs.us-east-2.amazonaws.com/123456789012/MyQueue ? 
    Version = 2012-11-05 &
    Action = SendMessage &
    MessageBody = data

Beachten Sie, dass diese Abfrage Nachrichten von anonymen Benutzern sendet. Die Standardwarteschlange kommt nicht mit einem Berechtigungsfehler an.

Um Nachrichten in diesem Status zu empfangen, müssen Sie sie so eingestellt haben, dass sie die öffentliche Offenlegung akzeptieren. Ändern Sie die Warteschlangenberechtigungen als erfasst und aktivieren Sie "Jeder".

capture_4.png

An eine Warteschlange senden, die nur bestimmte Benutzer zulässt

Wenn Sie an einen bestimmten Benutzer senden möchten, ohne die öffentliche Version zu akzeptieren, setzen Sie "Wer bin ich".

https://sqs.us-east-2.amazonaws.com/123456789012/MyQueue ?
    Version = 2012-11-05 &
    Action = SendMessage &
    MessageBody = data &
    AWSAccessKeyId = AKIA****************

Da ich die AWSAccessKeyID festgelegt habe, kann "jemand" kommuniziert werden. Ich weiß jedoch nicht, ob dies wirklich die Person ist. Sie können sich nur als Sie selbst ausgeben.

Verwenden Sie den geheimen Zugangsschlüssel, um Ihre Identität zu beweisen. Da es jedoch nicht möglich ist, den geheimen Zugriffsschlüssel so wie er ist zu senden, senden wir die Hash-Version.

https://sqs.us-east-2.amazonaws.com/123456789012/MyQueue ?
    Version = 2012-11-05 &
    Action = SendMessage &
    MessageBody = data &
    AWSAccessKeyId = AKIA**************** &
    Signature = ********************************** &
    SignatureMethod = HmacSHA256 &
    SignatureVersion = 2 &
    Timestamp = 2020-04-30T10:42:54

Die Signatur ist ein Hash der zu sendenden Daten, wobei der geheime Zugriffsschlüssel als Schlüssel verwendet wird. Die zu sendenden Daten sind die gesamte Abfrage ohne Signatur und die Hostinformationen zusammen.

Die Signaturmethode ist der Algorithmus, mit dem die Signatur gehasht wird. Da es sich um HMAC-SHA256 handelt, werde ich AWS davon erzählen.

Signaturversion ist die Signaturversion. Dieses Mal verwende ich Signatur Version 2.

TimeStamp ist das Datum und die Uhrzeit des Hashings.

Referenz: Signieren des Signaturprozesses für Version 2 https://docs.aws.amazon.com/ja_jp/general/latest/gr/signature-version-2.html

Was ist übrigens der Unterschied zur ursprünglich signierten URL?

Das Verhalten unterscheidet sich von der "signierten URL" des ursprünglichen S3.

** Der Bereich, in dem Daten nach der Ausgabe der URL geändert werden können, ist unterschiedlich **

In SQS ändert sich die Signatur abhängig vom Inhalt der gesendeten Nachricht. Mit S3 können Sie unabhängig vom Inhalt der von Ihnen gesendeten Datei mit derselben Signatur senden.

Um verschiedene Nachrichten mit SQS zu senden, müssen Sie so viele Signaturen ausstellen.

** Die Lebensdauer der von STS übergebenen temporären Anmeldeinformationen ist unterschiedlich **

Für S3 ist die von Lambda ausgegebene temporäre Zugriffsschlüssel-ID verfügbar, bis die signierte URL abläuft. Was passiert, wenn Sie eine temporäre Zugriffsschlüssel-ID und einen geheimen Zugriffsschlüssel auch in SQS ausstellen und gewähren?

Da die Zugriffsschlüssel-ID beim Beenden von Lambda verschwindet, wird die Authentifizierung nicht bestanden, wenn Lambda die URL an API Gateway zurückgibt.

Recommended Posts

Geben Sie eine signierte URL mit AWS SQS aus
Generieren Sie eine vorsignierte URL mit golang
Generieren Sie eine mit S3 signierte URL mit boto
Erstellen Sie ein privates Repository mit AWS CodeArtifact
Ich habe versucht, einen URL-Verkürzungsdienst mit AWS CDK serverlos zu machen
Erstellen Sie mit pulumi eine WardPress-Umgebung auf AWS
Versuchen Sie Tensorflow mit einer GPU-Instanz unter AWS
Erstellen Sie ein billiges Zusammenfassungssystem mit AWS-Komponenten
AWS-Schrittfunktionen zum Lernen anhand eines Beispiels
Geben Sie die von Amazon CloudFront signierte URL in Python aus
[Los] Liefern Sie private Inhalte mit einer von CloudFront signierten URL
[AWS] Ich habe BOT mit LINE WORKS daran erinnert
Ausgabe der zeitlich begrenzten S3-URL mit boto3 (mit Bestätigung der Dateiexistenz)
Erstellen Sie in Docker eine Ebene für AWS Lambda Python
Django + redis Geben Sie eine dedizierte URL aus, die 1 Stunde lang gültig ist
A4 Größe mit Python-Pptx
[blackbird-sqs] Überwachung von AWS SQS
URL-Verkürzung mit Python
Mit Dekorateur dekorieren
Ich habe gerade eine virtuelle Umgebung mit der AWS-Lambda-Schicht erstellt
[AWS] Ich habe BOT mit LINE WORKS (Implementierung) in Erinnerung gerufen.