

# API Gateway で WebSocket API へのアクセスを制御および管理する
<a name="apigateway-websocket-api-control-access"></a>

API Gateway は、WebSocket API へのアクセスを制御および管理するための複数のメカニズムをサポートしています。

認証と認可に次のメカニズムを使用することができます。
+ **標準の AWS IAM ロールとポリシー**は、柔軟で堅牢なアクセス制御を提供します。IAM ロールとポリシーを使用して、API を作成および管理できるユーザーと、API を呼び出すことができるユーザーを制御できます。詳細については、「[IAM 認可を使用して WebSocket API へのアクセスを制御する](apigateway-websocket-control-access-iam.md)」を参照してください。
+ **IAM タグ**は、アクセスをコントロールするために、IAM ポリシーと共に使用できます。詳細については、「[タグを使用して API Gateway REST API リソースへのアクセスをコントロールする](apigateway-tagging-iam-policy.md)」を参照してください。
+ **Lambda オーソライザー** は、API へのアクセスを制御する Lambda 関数です。詳細については、「[AWS Lambda REQUEST オーソライザーを使用して WebSocket API へのアクセスを制御する](apigateway-websocket-api-lambda-auth.md)」を参照してください。

セキュリティ体制を向上させるには、すべての WebSocket API で `$connect` ルートのオーソライザーを設定することをお勧めします。これは、さまざまなコンプライアンスフレームワークに準拠するために必要になる場合があります。詳細については、*AWS Security Hub ユーザーガイド*の「[Amazon API Gateway のコントロール](https://docs.aws.amazon.com/securityhub/latest/userguide/apigateway-controls.html)」を参照してください。

**Topics**
+ [IAM 認可を使用して WebSocket API へのアクセスを制御する](apigateway-websocket-control-access-iam.md)
+ [AWS Lambda REQUEST オーソライザーを使用して WebSocket API へのアクセスを制御する](apigateway-websocket-api-lambda-auth.md)

# IAM 認可を使用して WebSocket API へのアクセスを制御する
<a name="apigateway-websocket-control-access-iam"></a>

WebSocket API の IAM 認可は [REST API](api-gateway-control-access-using-iam-policies-to-invoke-api.md) と似ていますが、次のような例外があります。
+ 既存のアクション (`execute-api`、`ManageConnections`) に加えて、`Invoke` アクションは `InvalidateCache` をサポートしています。`ManageConnections` は @connections API へのアクセスを制御します。
+ WebSocket ルートは別の ARN 形式を使用します。

  ```
  arn:aws:execute-api:region:account-id:api-id/stage-name/route-key
  ```
+ この `@connections` API は、REST API と同じ ARN 形式を使用します。

  ```
  arn:aws:execute-api:region:account-id:api-id/stage-name/POST/@connections
  ```

**重要**  
[IAM 認可](#apigateway-websocket-control-access-iam)を使用する場合は、[Signature Version 4 (SigV4)](https://docs.aws.amazon.com/IAM/latest/UserGuide/create-signed-request.html) でリクエストに署名する必要があります。

たとえば、クライアントに次のポリシーを設定できます。次の例では、全員がメッセージ (`Invoke`) を `prod` ステージのシークレットルートを除くすべてのルートに送信でき、全員がすべてのステージの接続されたクライアント (`ManageConnections`) にメッセージを送信するのを防ぎます。

------
#### [ JSON ]

****  

```
{
    "Version":"2012-10-17",		 	 	 
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "execute-api:Invoke"
            ],
            "Resource": [
                "arn:aws:execute-api:us-east-1:111122223333:api-id/prod/*"
            ]
        },
        {
            "Effect": "Deny",
            "Action": [
                "execute-api:Invoke"
            ],
            "Resource": [
                "arn:aws:execute-api:us-east-1:111122223333:api-id/prod/secret"
            ]
        },
        {
            "Effect": "Deny",
            "Action": [
                "execute-api:ManageConnections"
            ],
            "Resource": [
                "arn:aws:execute-api:us-east-1:111122223333:api-id/*"
            ]
        }
    ]
}
```

------

# AWS Lambda REQUEST オーソライザーを使用して WebSocket API へのアクセスを制御する
<a name="apigateway-websocket-api-lambda-auth"></a>

WebSocket API の Lambda オーソライザー関数は、[REST API](apigateway-use-lambda-authorizer.md#api-gateway-lambda-authorizer-lambda-function-create) と似ていますが、次のような例外があります。
+  Lambda オーソライザー関数は、`$connect` ルートにのみ使用できます。
+ パス変数 (`event.pathParameters`) を使用することはできません。これは、パスが固定されているためです。
+ `event.methodArn` は、HTTP メソッドがないため、REST API の同等のメソッドとは異なります。`$connect` の場合、`methodArn` は `"$connect"` で終了します。

  ```
  arn:aws:execute-api:region:account-id:api-id/stage-name/$connect
  ```
+ `event.requestContext` のコンテキスト変数は、REST API のコンテキスト変数とは異なります。

 次の例では、WebSocket API の `REQUEST` オーソライザーへの入力を示しています。

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

次の例では、Lambda オーソライザー関数は、[その他の Lambda オーソライザー関数の例](apigateway-use-lambda-authorizer.md#api-gateway-lambda-authorizer-lambda-function-create) の REST API の Lambda オーソライザー関数の WebSocket バージョンです。

------
#### [ 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)
```

------

前述の Lambda 関数を WebSocket API の `REQUEST` オーソライザー関数として設定する場合、[REST API](configure-api-gateway-lambda-authorization.md#configure-api-gateway-lambda-authorization-with-console) と同じ手順に従います。

コンソールでこの Lambda オーソライザーを使用するよう `$connect` ルートを設定するには、`$connect` ルートを選択または作成します。**[ルートリクエスト設定]** セクションで、**[編集]** を選択します。**[承認]** ドロップダウンメニューで承認者を選択し、**[変更を保存]** を選択します。

オーソライザーをテストするには、新しい接続を作成する必要があります。`$connect` でオーソライザーを変更しても、接続済みのクライアントに影響はありません。WebSocket API に接続するときは、設定済みの ID ソースの値を指定する必要があります。たとえば、次の例のように `wscat` を使用し、有効なクエリ文字列およびヘッダーを送信して接続できます。

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

有効な ID 値なしに接続しようとすると、`401` レスポンスが送信されます。

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