Contrôle de l’accès aux API WebSocket avec un mécanisme d’autorisation AWS Lambda REQUEST - Amazon API Gateway

Contrôle de l’accès aux API WebSocket avec un mécanisme d’autorisation AWS Lambda REQUEST

La fonction de mécanisme d’autorisation Lambda des API WebSocket est similaire à celle des API REST, hormis les exceptions suivantes :

  • Vous pouvez uniquement utiliser une fonction de mécanisme d’autorisation Lambda pour la route $connect.

  • Vous ne pouvez pas utiliser de variables de chemin (event.pathParameters), car le chemin d’accès est fixe.

  • event.methodArn est différent de son équivalent de l’API REST, car il ne possède pas de méthode HTTP. Dans le cas de $connect, methodArn se termine par "$connect" :

    arn:aws:execute-api:region:account-id:api-id/stage-name/$connect
  • Les variables de contexte dans event.requestContext sont différentes de celles des API REST.

L’exemple suivant montre une entrée de mécanisme d’autorisation REQUEST pour une API WebSocket :

{ "type": "REQUEST", "methodArn": "arn:aws:execute-api:us-east-1:123456789012:abcdef123/default/$connect", "headers": { "Connection": "upgrade", "content-length": "0", "HeaderAuth1": "headerValue1", "Host": "abcdef123.execute-api.us-east-1.amazonaws.com", "Sec-WebSocket-Extensions": "permessage-deflate; client_max_window_bits", "Sec-WebSocket-Key": "...", "Sec-WebSocket-Version": "13", "Upgrade": "websocket", "X-Amzn-Trace-Id": "...", "X-Forwarded-For": "...", "X-Forwarded-Port": "443", "X-Forwarded-Proto": "https" }, "multiValueHeaders": { "Connection": [ "upgrade" ], "content-length": [ "0" ], "HeaderAuth1": [ "headerValue1" ], "Host": [ "abcdef123.execute-api.us-east-1.amazonaws.com" ], "Sec-WebSocket-Extensions": [ "permessage-deflate; client_max_window_bits" ], "Sec-WebSocket-Key": [ "..." ], "Sec-WebSocket-Version": [ "13" ], "Upgrade": [ "websocket" ], "X-Amzn-Trace-Id": [ "..." ], "X-Forwarded-For": [ "..." ], "X-Forwarded-Port": [ "443" ], "X-Forwarded-Proto": [ "https" ] }, "queryStringParameters": { "QueryString1": "queryValue1" }, "multiValueQueryStringParameters": { "QueryString1": [ "queryValue1" ] }, "stageVariables": {}, "requestContext": { "routeKey": "$connect", "eventType": "CONNECT", "extendedRequestId": "...", "requestTime": "19/Jan/2023:21:13:26 +0000", "messageDirection": "IN", "stage": "default", "connectedAt": 1674162806344, "requestTimeEpoch": 1674162806345, "identity": { "sourceIp": "..." }, "requestId": "...", "domainName": "abcdef123.execute-api.us-east-1.amazonaws.com", "connectionId": "...", "apiId": "abcdef123" } }

L’exemple suivant de fonction de mécanisme d’autorisation Lambda est une version WebSocket de la fonction de mécanisme d’autorisation Lambda pour les API REST dans Exemples supplémentaires de fonctions de mécanisme d’autorisation Lambda :

Node.js
// A simple REQUEST authorizer example to demonstrate how to use request // parameters to allow or deny a request. In this example, a request is // authorized if the client-supplied HeaderAuth1 header and QueryString1 query parameter // in the request context match the specified values of // of 'headerValue1' and 'queryValue1' respectively. export const handler = function(event, context, callback) { console.log('Received event:', JSON.stringify(event, null, 2)); // Retrieve request parameters from the Lambda function input: var headers = event.headers; var queryStringParameters = event.queryStringParameters; var stageVariables = event.stageVariables; var requestContext = event.requestContext; // Parse the input for the parameter values var tmp = event.methodArn.split(':'); var apiGatewayArnTmp = tmp[5].split('/'); var awsAccountId = tmp[4]; var region = tmp[3]; var ApiId = apiGatewayArnTmp[0]; var stage = apiGatewayArnTmp[1]; var route = apiGatewayArnTmp[2]; // Perform authorization to return the Allow policy for correct parameters and // the 'Unauthorized' error, otherwise. var authResponse = {}; var condition = {}; condition.IpAddress = {}; if (headers.HeaderAuth1 === "headerValue1" && queryStringParameters.QueryString1 === "queryValue1") { callback(null, generateAllow('me', event.methodArn)); } else { callback(null, generateDeny('me', event.methodArn)); } } // Helper function to generate an IAM policy var generatePolicy = function(principalId, effect, resource) { // Required output: var authResponse = {}; authResponse.principalId = principalId; if (effect && resource) { var policyDocument = {}; policyDocument.Version = '2012-10-17'; // default version policyDocument.Statement = []; var statementOne = {}; statementOne.Action = 'execute-api:Invoke'; // default action statementOne.Effect = effect; statementOne.Resource = resource; policyDocument.Statement[0] = statementOne; authResponse.policyDocument = policyDocument; } // Optional output with custom properties of the String, Number or Boolean type. authResponse.context = { "stringKey": "stringval", "numberKey": 123, "booleanKey": true }; return authResponse; } var generateAllow = function(principalId, resource) { return generatePolicy(principalId, 'Allow', resource); } var generateDeny = function(principalId, resource) { return generatePolicy(principalId, 'Deny', resource); }
Python
# A simple REQUEST authorizer example to demonstrate how to use request # parameters to allow or deny a request. In this example, a request is # authorized if the client-supplied HeaderAuth1 header and QueryString1 query parameter # in the request context match the specified values of # of 'headerValue1' and 'queryValue1' respectively. import json def lambda_handler(event, context): print(event) # Retrieve request parameters from the Lambda function input: headers = event['headers'] queryStringParameters = event['queryStringParameters'] stageVariables = event['stageVariables'] requestContext = event['requestContext'] # Parse the input for the parameter values tmp = event['methodArn'].split(':') apiGatewayArnTmp = tmp[5].split('/') awsAccountId = tmp[4] region = tmp[3] ApiId = apiGatewayArnTmp[0] stage = apiGatewayArnTmp[1] route = apiGatewayArnTmp[2] # Perform authorization to return the Allow policy for correct parameters # and the 'Unauthorized' error, otherwise. authResponse = {} condition = {} condition['IpAddress'] = {} if (headers['HeaderAuth1'] == "headerValue1" and queryStringParameters["QueryString1"] == "queryValue1"): response = generateAllow('me', event['methodArn']) print('authorized') return json.loads(response) else: response = generateDeny('me', event['methodArn']) print('unauthorized') return json.loads(response) # Help function to generate IAM policy def generatePolicy(principalId, effect, resource): authResponse = {} authResponse['principalId'] = principalId if (effect and resource): policyDocument = {} policyDocument['Version'] = '2012-10-17' policyDocument['Statement'] = [] statementOne = {} statementOne['Action'] = 'execute-api:Invoke' statementOne['Effect'] = effect statementOne['Resource'] = resource policyDocument['Statement'] = [statementOne] authResponse['policyDocument'] = policyDocument authResponse['context'] = { "stringKey": "stringval", "numberKey": 123, "booleanKey": True } authResponse_JSON = json.dumps(authResponse) return authResponse_JSON def generateAllow(principalId, resource): return generatePolicy(principalId, 'Allow', resource) def generateDeny(principalId, resource): return generatePolicy(principalId, 'Deny', resource)

Pour configurer la fonction Lambda précédente en tant que fonction de mécanisme d’autorisation REQUEST pour une API WebSocket, suivez la même procédure que pour les API REST.

Pour configurer la route $connect afin qu’elle utilise ce mécanisme d’autorisation Lambda dans la console, sélectionnez ou créez la route $connect. Dans la section Paramètres de la demande de route, choisissez Modifier. Sélectionnez votre mécanisme d’autorisation dans le menu déroulant Autorisation, puis choisissez Enregistrer les modifications.

Pour tester le mécanisme d’autorisation, vous devez créer une nouvelle connexion. La modification du mécanisme d’autorisation dans $connect n’affecte pas le client déjà connecté. Lorsque vous vous connectez à votre API WebSocket, vous devez fournir des valeurs pour toutes les sources d’identité configurées. Par exemple, vous pouvez vous connecter en envoyant une chaîne de requête et un en-tête en utilisant wscat comme dans l’exemple suivant :

wscat -c 'wss://myapi.execute-api.us-east-1.amazonaws.com/beta?QueryString1=queryValue1' -H HeaderAuth1:headerValue1

Si vous tentez de vous connecter sans valeur d’identité valide, vous recevrez une réponse 401 :

wscat -c wss://myapi.execute-api.us-east-1.amazonaws.com/beta error: Unexpected server response: 401