質問文1
「Samでnode.jsで複数のlambda関数を作成して、さらにトークン認証のLambda Authorizerを作成して全てのlambda関数にトークン認証のLambda Authorizerを適用する方法を丁寧に教えてください。」
回答
AWS SAMを使用してNode.jsで複数のLambda関数を作成し、それらのLambda関数にトークン認証のLambda Authorizerを適用する方法は以下の手順に従って行います。
1.プロジェクトの作成
最初に、AWS SAM CLIを使用して新しいプロジェクトを作成します。
sam init --runtime nodejs14.x --name my-serverless-app
これにより、Node.js 14.xランタイムを使用する新しいプロジェクトが作成されます。
2.Lambda関数の作成
次に、Lambda関数を追加します。 template.yml ファイルに以下のようなコードを追加します。
Resources:
HelloWorldFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: hello-world/
Handler: app.handler
Runtime: nodejs14.x
Events:
HelloWorldApi:
Type: Api
Properties:
Path: /hello
Method: get
これにより、hello-world ディレクトリに app.js ファイルを作成し、以下のようなコードを追加します。
module.exports.handler = async (event) => {
const response = {
statusCode: 200,
body: JSON.stringify({
message: 'Hello World!'
})
};
return response;
};
これにより、 /hello パスで GET リクエストを受け取ると、Hello World! メッセージが含まれるJSONレスポンスが返されるLambda関数が作成されます。
3.Lambda Authorizerの作成
次に、Lambda Authorizerを追加します。 template.yml ファイルに以下のようなコードを追加します。
Resources:
TokenAuthorizer:
Type: AWS::Serverless::Function
Properties:
CodeUri: token-authorizer/
Handler: app.handler
Runtime: nodejs14.x
Events:
TokenAuthorizerEvent:
Type: TokenAuthorizer
Properties:
RestApiId: !Ref HelloWorldApi
AuthorizerResultTtlInSeconds: 300
これにより、token-authorizer ディレクトリに app.js ファイルを作成し、以下のようなコードを追加します。
module.exports.handler = async (event) => {
const token = event.authorizationToken;
const methodArn = event.methodArn;
// 認証処理を行い、allow/denyの結果を返す処理を追加
return {
principalId: 'user',
policyDocument: {
Version: '2012-10-17',
Statement: [
{
Action: 'execute-api:Invoke',
Effect: effect,
Resource: methodArn
}
]
}
};
};
///
exports.handler = async (event) => {
// 受け取ったBearerトークンを取得
const token = event.authorizationToken.split(' ')[1];
try {
// トークンを検証し、有効であることを確認する
const decoded = jwt.verify(token, process.env.SECRET_KEY);
// ユーザーに対するアクセス許可を許可する場合はポリシーを返す
return generatePolicy(decoded.sub, 'Allow', event.methodArn);
} catch (err) {
// トークンが無効である場合はアクセスを拒否するポリシーを返す
return generatePolicy('user', 'Deny', event.methodArn);
}
};
// IAMポリシーを生成するヘルパー関数
function generatePolicy(principalId, effect, resource) {
const authResponse = {};
authResponse.principalId = principalId;
if (effect && resource) {
const policyDocument = {};
policyDocument.Version = '2012-10-17';
policyDocument.Statement = [];
const statementOne = {};
statementOne.Action = 'execute-api:Invoke';
statementOne.Effect = effect;
statementOne.Resource = resource;
policyDocument.Statement[0] = statementOne;
authResponse.policyDocument = policyDocument;
}
return authResponse;
}
////
exports.handler = function(event, context, callback) {
// Extract the token from the event
var token = event.authorizationToken;
// Verify the token
if (token === 'Bearer my-token') {
// Generate a policy that allows the user to invoke the API Gateway
var policy = generatePolicy('user', 'Allow', event.methodArn);
callback(null, policy);
} else {
// Deny access
callback('Unauthorized');
}
};
function generatePolicy(principalId, effect, resource) {
var authResponse = {};
authResponse.principalId = principalId;
if (effect && resource) {
var policyDocument = {};
policyDocument.Version = '2012-10-17';
policyDocument.Statement = [];
var statementOne = {};
statementOne.Action = 'execute-api:Invoke';
statementOne.Effect = effect;
statementOne.Resource = resource;
policyDocument.Statement[0] = statementOne;
authResponse.policyDocument = policyDocument;
}
return authResponse;
}
これにより、Lambda Authorizer関数が作成され、API Gatewayの /hello パスにトークン認証が適用されます。
4.Lambda関数にLambda AuthorizerをLambda関数に適用します。
template.ymlファイルでLambda関数の定義を以下のように変更します
Resources:
HelloWorldFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: hello-world/
Handler: app.handler
Runtime: nodejs14.x
Events:
HelloWorldApi:
Type: Api
Properties:
Path: /hello
Method: get
Auth:# 追加
Authorizers:
MyAuthorizer: FunctionArn: !GetAtt MyAuthorizerFunction.Arn
Identity:
Headers:
- Authorization
ResultTtlInSeconds: 300# ここまで追加
これにより、/helloパスにGETリクエストを送信する前に、API GatewayがLambda Authorizerを呼び出し、トークン認証を行います。トークン認証が成功した場合、Lambda関数が実行されます。
以上で、AWS SAMを使用してNode.jsで複数のLambda関数を作成し、トークン認証のLambda Authorizerを適用する手順が完了しました。必要に応じて、Lambda Authorizer関数に適切な認証処理を追加することができます。
質問文2
「これをローカルで動作確認する方法を丁寧に教えて」
回答
AWS SAM CLIを使用して、先程作成したLambda関数とLambda Authorizerをローカルで実行して動作確認を行うことができます。以下はその手順です。
1.プロジェクトのビルド
まず、AWS SAM CLIを使用してプロジェクトをビルドします。
sam build
これにより、依存関係をインストールし、Lambda関数とLambda Authorizerのコードをビルドします。
2.ローカルでのLambda関数の実行
次に、ローカルでLambda関数を実行して動作を確認します。
sam local start-api
これにより、API Gatewayがローカルで起動し、http://127.0.0.1:3000/helloでLambda関数にリクエストを送信できます。
3.ローカルでのトークン認証のテスト
ローカルでトークン認証をテストするために、以下のようなコマンドを使用してLambda Authorizer関数を実行します。
sam local invoke TokenAuthorizer --event events/token-authorizer.json
これにより、トークン認証のテストが行われ、ポリシードキュメントが返されます。
以上で、AWS SAM CLIを使用して、Node.jsで作成したLambda関数とLambda Authorizerをローカルで実行して動作確認を行うことができます。必要に応じて、eventsディレクトリに新しいイベントファイルを作成して、より複雑なシナリオでの動作確認を行うことができます。
以下は、token-authorizer.jsonファイルの例です。
{
"type": "TOKEN",
"authorizationToken": "Bearer my-token",
"methodArn": "arn:aws:execute-api:us-west-2:123456789012:my-api-id/dev/GET/hello"
}
この例では、Bearerトークンmy-tokenを使用してLambda Authorizerを呼び出し、API Gatewayの/helloエンドポイントにGETリクエストを送信することを想定しています。methodArnプロパティには、Lambda Authorizerを呼び出すAPI Gatewayのリソースアーノテーションが指定されています。必要に応じて、authorizationTokenとmethodArnプロパティを変更して、テストするイベントをカスタマイズできます。
[通常]
event.methodArnは、Lambda Authorizerが呼び出されるAPI GatewayリソースのARN(Amazon Resource Name)です。ARNは、AWSリソースを一意に識別するための文字列です。
API Gatewayの場合、event.methodArnには、API Gatewayのリソースを指定するARNが含まれます。具体的には、arn:aws:execute-api:{region}:{account_id}:{api_id}/{stage}/{http_method}/{resource_path}のような形式で表されます。ここで、{region}はAWSリージョン名、{account_id}はAWSアカウントID、{api_id}はAPI GatewayのREST APIのID、{stage}はAPI Gatewayステージ名、{http_method}はHTTPメソッド、{resource_path}はAPI Gatewayリソースパスを表します。
Lambda Authorizerは、このARNを使用して、API Gatewayの特定のリソースに対するアクセス権限を決定するために使用されます。つまり、ARNを解析して、呼び出し元がアクセスしようとしているAPI Gatewayリソースを特定し、ユーザーに対するアクセス許可を判断するロジックを実行します。
[ローカル]
ローカルでLambda Authorizerをテストする場合、event.methodArnには実際のAPI GatewayのARNが入ることを想定します。ただし、ローカル環境ではAPI Gatewayが実行されていないため、テストイベントでmethodArnの値をモック化する必要があります。
テストイベントでmethodArnの値をモック化する場合、実際のAPI GatewayのARNに似た値を設定することが推奨されています。例えば、以下のような形式で指定することができます。
{
"methodArn": "arn:aws:execute-api:us-east-1:123456789012:xxxxxx/dev/GET/users"
}
ここで、xxxxxxはAPI GatewayのREST APIのIDに置き換え、GETはHTTPメソッド、/usersはAPI Gatewayリソースパスを表します。これにより、Lambda Authorizerが正常に動作するかどうかを確認することができます。
質問の仕方を変えてみる
「samでnode.jsでLambda Authorizerとlambda関数をそれぞれ作成して認証適用する場合の書き方の詳細を教えて」
回答
Resources:
LambdaAuthorizerFunction:
Type: AWS::Serverless::Function
Properties:
Handler: index.handler
Runtime: nodejs14.x
CodeUri: .
Description: Lambda Authorizer function
MemorySize: 128
Timeout: 30
Events:
Authorizer:
Type: Api
Properties:
RestApiId:
Ref: MyApi
Path: /auth
Method: get
LambdaFunction:
Type: AWS::Serverless::Function
Properties:
Handler: index.handler
Runtime: nodejs14.x
CodeUri: .
Description: Lambda function
MemorySize: 128
Timeout: 30
Events:
GetResource:
Type: Api
Properties:
RestApiId:
Ref: MyApi
Path: /resource
Method: get
AuthorizationType: CUSTOM
AuthorizerId:
Ref: LambdaAuthorizerFunction
MyApi:
Type: AWS::Serverless::Api
Properties:
StageName: prod
DefinitionBody:
swagger: "2.0"
info:
version: "1.0"
title: "My API"
paths:
/resource:
get:
x-amazon-apigateway-integration:
type: aws_proxy
uri:
Fn::Sub: arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${LambdaFunction.Arn}/invocations
httpMethod: POST
responses: {}
/auth:
get:
x-amazon-apigateway-integration:
type: aws_proxy
uri:
Fn::Sub: arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${LambdaAuthorizerFunction.Arn}/invocations
httpMethod: POST
responses: {}
質問
ローカルでLambdaFunctionの認証動作確認を行うには
sam local start-lambda --template template.yaml
sam local invoke <authorizer_function_logical_id> -e authorizer-event.json
sam local start-api
curl -H "Authorization: Bearer <token>" http://localhost:3000/hello
///////////////
sam local start-api
curl http://localhost:3000/auth
curl -H "Authorization: <Token>" http://localhost:3000/resource
aws lambda publish-layer-version --layer-name my-layer --zip-file fileb://my-layer.zip --compatible-runtimes nodejs14.x
Resources:
MyFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: .
Handler: index.handler
Runtime: nodejs14.x
Layers:
- !Ref MyLayer
MyLayer:
Type: AWS::Serverless::LayerVersion
Properties:
LayerName: my-layer
ContentUri: ./my-layer.zip
CompatibleRuntimes:
- nodejs14.x```