Lambda関数に付与するIAMロールについて | AWS豆知識

DynamoDBへのアクセス許可

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "SidDynamoDB",
            "Effect": "Allow",
            "Action": [
                "dynamodb:PutItem",
                "dynamodb:DeleteItem",
                "dynamodb:GetItem",
                "dynamodb:Scan",
                "dynamodb:Query",
                "dynamodb:UpdateItem"
            ],
            "Resource": [
                DynamoDBに作成したテーブルのARN
                テーブルに設定したLSIやGSIのインデックスについても個別に指定する
            ]
        }
    ]
}

S3への許可

ワイルドカードを利用している
s3:*Object → Objectに対する操作すべてということになる

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "SidS3ListBucket",
            "Effect": "Allow",
            "Action": [
                "s3:ListBucket"
            ],
            "Resource": [
                "arn:aws:s3:::バケット名"
            ]
        },
        {
            "Sid": "SidS3ObjectAction",
            "Effect": "Allow",
            "Action": [
                "s3:*Object"
            ],
            "Resource": [
                "arn:aws:s3:::バケット名/*"
            ]
        }
    ]
}

AWS Cognitoの認証機能をAPI Gatewayの呼び出しに利用する | AWSサーバレス構築

参考にするチュートリアル

英語版チュートリアル(新しめ)
日本語チュートリアル(古いけど有益)
Javascriptコードの在り処

また、事前に当ブログ記事「簡易チュートリアル」を参照のこと。

JavaScript

AWS SDK

amazon-cognito-identity.min.js と aws-cognito-sdk.min.js を、公式チュートリアルのコードから拝借する。(ventorフォルダ)

config.js

Cognitoで作成したユーザープールの「プールID」「アプリクライアントID」「リージョン」を入力する。

window._config = {
    cognito: {
        userPoolId: 'プールID',
        userPoolClientId: 'アプリクライアントID',
        region: 'リージョン'
    },
    api: {
        invokeUrl: 'APIGatewayでデプロイしたステージのURL'
    }
};

cognito-auth.js

ログインIDとパスを入力するinput、ログインボタンをhtmlページ上に用意すること。(login.html参照)

// 認証トークン管理変数。全jsファイル間で共有
var tokenManager = window.tokenManager || {};

// 起動時処理
(function cognitoAuthoMain($) {

    // ユーザープールの取得

    var poolData = {
        UserPoolId: _config.cognito.userPoolId,
        ClientId: _config.cognito.userPoolClientId
    };

    var userPool;

    if (!(_config.cognito.userPoolId &&  _config.cognito.userPoolClientId && _config.cognito.region)) {
        return;
    }

    userPool = new AmazonCognitoIdentity.CognitoUserPool(poolData);

    if (typeof AWSCognito !== 'undefined') {
        AWSCognito.config.region = _config.cognito.region;
    }

    // ===

    // tokenManager に 関数を設定しておく

    // サインアウト処理
    tokenManager.signOut = function signOut() {
        userPool.getCurrentUser().signOut();
    };

    // 認証処理。「check-login-state.js」ファイルにて利用される。
    tokenManager.authToken = new Promise(function fetchCurrentAuthToken(resolve, reject) {
    
        var cognitoUser = userPool.getCurrentUser();

        if (cognitoUser) {
            cognitoUser.getSession(function sessionCallback(err, session) {
                if (err) {
                    reject(err);
                } else if (!session.isValid()) {
                    resolve(null);
                } else {
                    resolve(session.getIdToken().getJwtToken());
                }
            });
        } else {
            resolve(null);
        }
    });


    // ===

    // AWS Cognito 認証処理

    // ログイン処理
    function loginFunc(email, password, onSuccess, onFailure) {
        var authenticationDetails = new AmazonCognitoIdentity.AuthenticationDetails({
            Username: email,
            Password: password
        });

        var cognitoUser = createCognitoUser(email);
        cognitoUser.authenticateUser(authenticationDetails, {
            onSuccess: onSuccess,
            onFailure: onFailure
        });
    }

    function createCognitoUser(email) {
        return new AmazonCognitoIdentity.CognitoUser({
            Username: email,
            Pool: userPool
        });
    }

    // ===

    // ページ内処理・イベント

    // ページ読み込み時の処理
    $(function onDocReady() {

        // ログインボタンにイベント追加
        let aTag_login = document.getElementById("a_login");
        if( aTag_login ) {
            aTag_login.href = "javascript:void(0);";
            aTag_login.onclick = function() { handleLogin(); };
        }

        // ログアウトボタンにイベント追加
        let aTag_logout = document.getElementById("a_logout");
        if( aTag_logout ) {
            aTag_logout.href = "javascript:void(0);";
            aTag_logout.onclick = function() { handleLogout(); };
        }
    });

    // ログインボタン押下時
    function handleLogin() {

        // フォームに入力された値を取得
        var email = $('#login_input_email').val();
        var password = $('#login_input_password').val();
        
        // 入力チェック
        if( (email.length == 0) || (password.length == 0) ) {
            alert('入力されていない項目があります。ご確認ください。');
            return;
        }

        // ===

        // ログイン処理実行
        // 入力された値でログイン判定、成功したならページ遷移
        loginFunc( email, password,

            function loginSuccess() {

                console.log('Successfully Logged In');

                // ログイン成功でトップページへ遷移
                window.location.href = topPageUrl;
            },

            function loginError(err) {

                console.log('Failed Logged In');

                alert('ログインに失敗しました。IDとパスワードをご確認ください。');
            }
        );
    }

    // ログアウトボタン押下時
    function handleLogout() {

        // ログアウト処理
        tokenManager.signOut();

        // アラート表示
        alert('ログアウトしました。');

        // ログアウトでログインページへ遷移
        window.location.href = loginPageUrl;
    }

}(jQuery));

check-login-state.js

// 認証トークン管理変数。全jsファイル間で共有
var tokenManager = window.tokenManager || {};

// 以降のjsにてAPI認証に使われるトークン。
var authToken;

// 起動時処理
(function checkLoginStateMain($) {
    
    // 認証失敗時に戻すページのURL。今回はログイン画面に戻す想定。
    let loginPageUrl = 'login.html';
    
    // ===

    tokenManager.authToken.then(function setAuthToken(token) {

        if (token) {
            authToken = token;
        } else {
            alert("ページにアクセスするにはログインを行ってください。ログイン画面に移動します。");
            window.location.href = loginPageUrl;
        }

    }).catch(function handleTokenError(error) {
        
        alert(error);
        window.location.href = loginPageUrl;
    });

}(jQuery));

call-api.js

「check-login-state.js」にて取得したトークン「authToken」を、API呼び出し時に渡している。

// 認証トークン管理変数。全jsファイル間で共有
var tokenManager = window.tokenManager || {};

// 引数に、Lambdaに渡すJSONデータと、完了時の関数を渡す
// completeFuncは、引数にJSONデータを受け取るので、引数を1つ渡せるような関数とする 
function requestApi( jsonData, completeFunc ) {
    $.ajax({
        method: 'POST',
        
        url: _config.api.invokeUrl + '/admin',
        
        headers: {
            Authorization: authToken
        },

        data: jsonData,
        
        contentType: 'application/json',
        
        success: completeFunc,
        
        error: function ajaxError(jqXHR, textStatus, errorThrown) {
            console.error('Error requesting: ', textStatus, ', Details: ', errorThrown);
            console.error('Response: ', jqXHR.responseText);
            alert("データベースサーバーとの通信に失敗しました。");
        }
    });
}

main.js

call-api.js で準備したAPIコール関数を実行する。APIから応答を受けた後の処理も記述している。

(function MainFunction($) {

    // Lambda側に渡したいデータをJSON形式で準備
    var requestJsonData = {
                            "Key1": "Value1",
                            "Key2": "Value2"
                          };

    // Lambda実行完了時の処理
    function completeRequest( result ) {
        
        // 引数resultは受け取った時点でJSONデータとして認識できる
        // キーを指定してデータを得られる。複数データも配列として指定して取得すれば良い。
        
        // console.log('Response received from API: ', result);
        // const responseData = JSON.parse( result );
        
        console.log( result["ReceivedVal1"] );
        console.log( result["ReceivedVal2"] );
    }

    // ページ読み込み時の処理
    $(function onDocReady() {

        // Lambda実行
        requestApi( JSON.stringify(requestJsonData), completeRequest );
    });

}(jQuery));

Html

login.html

<!doctype html>
<html lang="ja">

<head>
<meta charset="utf-8">
<title>ログインページ サンプル</title>
</head>

<body>
<h1>ログインページ サンプル</h1>

<table>
<tr><th>ID:</th><td><input id="login_input_email" class="input_text" type="text" required /></td></tr>
<tr><th>パスワード:</th><td><input id="login_input_password" class="input_text" type="password" required /></td></tr>
</table>

<p id="btn_login"><a id="a_login">ログイン</a></p>

<!-- javascript -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>

<!-- Cognito認証 -->
<script src="html/js/vendor/aws-cognito-sdk.min.js"></script>
<script src="html/js/vendor/amazon-cognito-identity.min.js"></script>
<script src="html/js/config.js"></script>
<script src="html/js/cognito-auth.js"></script>

</body>
</html>

index.html

<!doctype html>
<html lang="ja">

<head>
<meta charset="utf-8">
<title>AWSチュートリアル簡易版テストページ</title>
</head>

<body>

<div>Hello World</div>

<!-- jquery -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>

<!-- 認証処理 -->
<script src="js/vendor/aws-cognito-sdk.min.js"></script>
<script src="js/vendor/amazon-cognito-identity.min.js"></script>
<script src="js/config.js"></script>
<script src="js/cognito-auth.js"></script>
<script src="js/check-login-state.js"></script>

<!-- API呼び出し処理 -->
<script src="js/config.js"></script>
<script src="js/call-api.js"></script>
<script src="js/main.js"></script>

</body>
</html>

LambdaでDynamoDBにアクセスする記述方法(Python) | AWS豆知識

  • import

  • テーブル取得

  • 書き込み

  • 読み出し

  • 消去

Lambda関数に付与する権限

権限を付与するためにはIAMロールを利用する。
IAMについては「IAM ロール ポリシーについて」を、
実際に付与する権限の書き方例については「Lambda関数に付与するIAMロールについて」を山参照

import

import boto3
from boto3.dynamodb.conditions import Key, Attr

テーブル取得

# dynamoDBオブジェクト
self.dynamodb = boto3.resource('dynamodb')

# テーブル取得
targetTable = self.dynamodb.Table('DynamoDBに存在するテーブル名')

書き込み

# 登録データ
registerData = {
    "name": "名前",
    "RegisterDate": "登録日"
}

# 値を書き込む
queryResult = targetTable.put_item(
    Item = registerData
}

読み出し

# 全件取得
queryResult = targetTable.scan()

# DeviceId が DeviceA のものを取得
# Keyに指定している「DeviceId」がパーティションキーである必要がある。
queryResult = targetTable.query(
    KeyConditionExpression = Key( "DeviceId" ).eq( "DeviceA" )
)

# DeviceId が DeviceA のものを取得(LSI 登録日の昇順で取得)
# LSI:ローカルセカンダリインデックス
# DynamoDB側で「DeviceId-RegisterDate-index」という名前でインデックスが登録されている
queryResult = targetTable.query(
    IndexName = "DeviceId-RegisterDate-index",
    KeyConditionExpression = Key( "DeviceId" ).eq( "DeviceA" )
)

# 指定された顧客名の貸出情報を取得(GSI 貸出日時の昇順で取得)
# GSI:グローバルセカンダリインデックス
# DynamoDB側で「ClientName-RentalDate-index」という名前でインデックスが登録されている
queryResult = targetTable.query(
    IndexName = "ClientName-RentalDate-index",
    KeyConditionExpression = Key( "ClientName" ).eq( clientName )
)

消去

# 消去
queryResult = targetTable.delete_item(
    Key = { "DeviceId": deviceId }
)

# LSIやGSIで設定された2項目でデータを指定して消去
queryResult = targetTable.delete_item(
    Key = { "DeviceId": deviceId, "RegisterDate": registerDate }
)

IAM ロール ポリシーについて | AWS豆知識

f:id:dendouji:20210319154511p:plain

S3とか、Lambdaとか、サービスには個別に権限を割り当てられます。

例えば、Lambda関数がDynamoDBのデータにアクセスしたい場合、権限を与えていないと拒否されてしまいます。IAMで権限を作成して、それをLambdaに割り当てます。

「ポリシー」と呼ばれるものに権限を構築していきます。ポリシーの記述によってどのサービスのどのアクションを許可するか、細かく指定ができます。

「ロール」と呼ばれるものにいくつかの「ポリシー」を割り当て、その「ロール」を各種AWSサービスに割り当てることで、そのサービスに権限を与えられることができます。

JavaSciptでAWS Gatewayを呼び出す | AWSサーバレス構築 Webページ作成

ロードマップ

公式チュートリアルで参考とするファイル

公式チュートリアルで使われているソースコード
aws-serverless-webapp-workshop(GitHub)

このうち、
・config.js
・cognito-auth.js
・ride.js
を参考にする。

config.js

API GatewayのデプロイURLと、認証を利用する場合にCognitoの情報を保持する構造体データ
複数のJavaScriptファイル間でグローバル変数として振る舞う。

window._config = {
    cognito: {
        userPoolId: '',
        userPoolClientId: '',
        region: ''
    },
    api: {
        invokeUrl: 'APIGatewayでデプロイしたステージのURL'
    }
};

cognito-auth.js

APIを呼び出す時に認証を必要とさせたいときに利用する。今回の簡易版チュートリアルでは使用しない。

ride.js(call-api.js)

ajaxを利用してAPIを呼び出している。API呼び出し部分だけを抜き出して、新たにJavaScriptファイル「call-api.js」を用意する。

// 引数に、Lambdaに渡すJSONデータと、完了時の関数を渡す
// completeFuncは、引数にJSONデータを受け取るので、引数を1つ渡せるような関数とする 
function requestApi( jsonData, completeFunc ) {
    $.ajax({
        method: 'POST',
        
        url: _config.api.invokeUrl + '/testresource',
        
        data: jsonData,
        
        contentType: 'application/json',
        
        success: completeFunc,

        error: function ajaxError(jqXHR, textStatus, errorThrown) {
            console.error('Error requesting: ', textStatus, ', Details: ', errorThrown);
            console.error('Response: ', jqXHR.responseText);
            alert("データベースサーバーとの通信に失敗しました。");
        }
    });
}

使い方

main.js

call-api.js で準備したAPIコール関数を実行する。APIから応答を受けた後の処理も記述している。

(function MainFunction($) {

    // Lambda側に渡したいデータをJSON形式で準備
    var requestJsonData = {
                            "Key1": "Value1",
                            "Key2": "Value2"
                          };

    // Lambda実行完了時の処理
    function completeRequest( result ) {
        
        // 引数resultは受け取った時点でJSONデータとして認識できる
        // キーを指定してデータを得られる。複数データも配列として指定して取得すれば良い。
        
        // console.log('Response received from API: ', result);
        // const responseData = JSON.parse( result );
        
        console.log( result["ReceivedVal1"] );
        console.log( result["ReceivedVal2"] );
    }

    // ページ読み込み時の処理
    $(function onDocReady() {

        // Lambda実行
        requestApi( JSON.stringify(requestJsonData), completeRequest );
    });

}(jQuery));

index.html

これまでに用意したJavaScriptファイルを順番に読み込んで実行させている。

ブラウザのコンソール上にサーバーから送られている文字列が表示されればOK。
チュートリアルの目的達成。

<!doctype html>
<html lang="ja">

<head>
<meta charset="utf-8">
<title>AWSチュートリアル簡易版テストページ</title>
</head>

<body>

<div>Hello World</div>

<!-- javascript -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>

<script src="js/config.js"></script>
<script src="js/call-api.js"></script>
<script src="js/main.js"></script>

</body>
</html>

AWS API GatewayでLambda関数を呼び出す | AWSサーバレス構築 RESTful API

ロードマップ

RESTful API とは

REST = 簡易な手順でWebサービスへのアクセスを可能にする仕組みのこと。
RESTful API = REST に則ったAPI

Webページ(html, javascript)とサーバー(Lambda)をつなぐ役目をしているのがAPI Gateway

API作成手順

新規作成

① 4種類あるうちの「REST API」を選択して構築する
② 「新しいAPI」を選択し、API名入力、「エッジ最適化」を選択

f:id:dendouji:20210319175200p:plain

リソースの追加

① 「アクション」→「リソースの作成」を選択
② リソース名を入力し、API Gateway CORS を有効にする。例では(testresouceという名前)
③ 作成完了。このリソース名を指定してAPIを呼び出すことになる

f:id:dendouji:20210329143304p:plain

メソッドの追加

① 「アクション」→「メソッドの作成」を選択
② メソッドの種類は「POST」
③ 「チェックマーク」を押して確定

f:id:dendouji:20210329143312p:plain

④ 統合タイプ「Lambda関数」、「Lambdaプロキシ統合の使用にチェック」、「」で作成した関数を指定
⑤ 関数を呼び出すための権限を Amazon API Gateway に付与するように求められたら、[OK]を選択

f:id:dendouji:20210329143319p:plain

デプロイ

① 「アクション」→「APIのデプロイ」を選択
② ステージ名に「prod」を入力
APIを呼び出すためのURLが発行される

f:id:dendouji:20210329143654p:plain

「発行されたURL + リソース名」でAPIを呼び出せるようになり、APIを呼び出すとそれに紐ついたlambda関数を実行できる。

AWS Lambdaでサーバー側の処理を実装する | AWSサーバレス構築 サーバレスバックエンド

ロードマップ

新規作成

関数を「一から作成」を選択して作成。ランタイムはプログラムを記述する言語を選択するところで、今回はPythonを選択

実行ロールは「基本的なLambdaアクセス権限で新しいロールを作成」を選択すると、Amazon CloudWatch Logs にログをアップロードするアクセス権限を持つIAMロールとポリシーが作成されて、CloudWatchLogsというサービスも自動で登録される。Lambdaの実行ログがCloudWatchに蓄積されるようになる。

f:id:dendouji:20210319174942p:plain

f:id:dendouji:20210319174956p:plain


記述例

  • lambda_handler関数の引数「event」に、コール元から渡されたjsonデータが含まれる

  • json.loads( jsonDataStr ) は、文字列をjson形式データ(辞書型データ)に変換

  • json.dumps( jsonData ) は、json形式データ(辞書型データ)を文字列に変換する

  • 通信で送る場合は文字列データにして送り、受信先でその文字列をデータ型に置き換えて使用する。

import json

def lambda_handler(event, context):
    
    # 渡されたデータ受け取り
    event_body = json.loads( event["body"] )
    
    receivedValue1 = event_body["Key1"]
    receivedValue2 = event_body["Key2"]
    
    # 返信するデータを作成(辞書型データ)
    returnJsonData = { "Name": "Yamada", 
                       "ReceivedVal1": receivedValue1,
                       "ReceivedVal2": receivedValue2 }
    
    # 返信処理(bodyに辞書型データを文字列型に変換したものをセット)
    return {
        'statusCode': 200,
        'headers': {
            'Access-Control-Allow-Headers': 'Content-Type',
            'Access-Control-Allow-Origin': '*',
            'Access-Control-Allow-Methods': 'OPTIONS,POST,GET'
        },
        'body': json.dumps( returnJsonData )
    }

returnにある「header」については、オリジン間リソース共有 (CORS)を参照