![[Rust] SAMをつかってLambda Authorizerのビルド&デプロイ](https://devio2023-media.developers.io/wp-content/uploads/2023/08/aws-lambda.png)
[Rust] SAMをつかってLambda Authorizerのビルド&デプロイ
この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
Introduction
ここのblogにもあるように、
(betaですが)cargo-lambdaをつかってSAMでRustコードのビルドが可能です。
本稿では、SAMを使用して、Rustで実装したAWS Lambdaをビルドおよびデプロイする
方法について解説します。
最初にSAMの基本について簡単に説明し、RustでLambdaを実装して
Lambda Authorizerも設定してみます。
SAM?
サーバーレスアーキテクチャはインフラの管理をシンプルにしながら、
スケーラブルなアプリを構築することを可能にします。
AWS Serverless Application Model(SAM)は、
AWSでサーバーレスアプリを簡単に開発するためのツールです。
SAMはAWS CloudFormationの拡張しており、YAML形式の設定ファイルを記述することで
アプリのインフラ設定やリソース定義が可能です。
Environments
- MacBook Pro (13-inch, M1, 2020)
- OS : MacOS 13.5.2
- Rust : 1.75.0
- aws-cli : 2.2.35
AWSアカウントはセットアップ済みとします。
Setup
まずはCargo Lambdaをインストールします。
% cargo install cargo-lambda
SAMはHomebrewでインストールできるのでbrew installを実行。
% brew tap aws/tap % brew install aws-sam-cli % sam --version SAM CLI, version 1.107.0
ビルドコマンド実行時にSAMはCargo Lambdaを使うようにするため、
↓のように環境変数を設定しておきましょう。
% export SAM_CLI_BETA_RUST_CARGO_LAMBDA=1
Try
ではSAMをつかってRust用テンプレートを作成します。
% sam init
AWS Quick StartからHello Worldを選択して、
rust (provided.al2)のrumtimeを指定しましょう。
samconfig.tomlファイルに下記パラメータを追加します。
[default.build.parameters] ・ ・ beta_features = true [default.sync.parameters] ・ ・ beta_features = true
そのままビルドとデプロイができる状態になってますので、
とりあえず確認してみましょう。
sam buildでビルドします。
% cd /path/your/example-project % sam build --beta-features
デプロイして動作確認してみましょう。
% sam deploy --guided
途中いくつか質問されますが、↓の認証に関する質問だけYesで回答して、
他はデフォルトでOK。
API Gatewayも作成されているので、発行されたURLにアクセスすれば
Lambdaが実行されてメッセージが表示されます。
※ dockerをいれてればsam invokeコマンド使ってローカルで動かすことも可能
HelloWorldFunction has no authentication. Is this okay? [y/N]: Y
必要なくなったらきれいにしておきましょう。
% sam delete
impliment Lambda Authorizer
Lambda Authorizerを使えばLambdaに簡単に認証機能を実装することができます。
今回はリクエストパラメータベースのLambda Authorizerを使って
先程のHelloWorldApi Lambdaに認証機能を追加してみましょう。
まずはtemplate.yamlにAuthorizerの設定を追加します。
  Resources:
    AuthApi:
      Type: AWS::Serverless::Api
      Properties:
        StageName: Prod
        Auth:
          DefaultAuthorizer: ProxyAuthorizer
          Authorizers:
            ProxyAuthorizer:
              FunctionArn: !GetAtt AuthorizerFunction.Arn
    AuthorizerFunction:
      Type: AWS::Serverless::Function
      Metadata:
        BuildMethod: rust-cargolambda
      Properties:
        CodeUri: ./proxy_authorizer
        Handler: bootstrap
        Runtime: provided.al2023
        Environment:
          Variables:
            RUST_BACKTRACE: "1"
・
・
そして、template.yamlのHelloWorldFunction部分に RestApiIdで認証用Lambdaを関連付けます。
  Events:
    HelloWorld:
      Type: Api
      Properties:
        Path: /hello
        Method: get
        RestApiId: !Ref AuthApi #←追加
次は認証用Lambdaを作成します。
プロジェクトディレクトリにauthorizerディレクトリを作成し、
src/main.rsを以下の内容で作成します。
//authorizer/src/main.rs
use aws_lambda_events::event::apigw::{
    ApiGatewayCustomAuthorizerPolicy, ApiGatewayCustomAuthorizerRequest,
    ApiGatewayCustomAuthorizerResponse, IamPolicyStatement,
};
use lambda_runtime::{run, service_fn, Error, LambdaEvent};
use serde_json::json;
use tokio;
// API Gatewayのポリシーに関する定数
const POLICY_VERSION: &str = "2012-10-17";
const EXECUTE_API_ACTION: &str = "execute-api:Invoke";
const POLICY_EFFECT_ALLOW: &str = "Allow";
const PRINCIPAL_ID: &str = "user";
// 許可されたトークンの値を定義する定数
const AUTHORIZED_TOKEN: &str = "allow";
/// Lambda関数のエントリーポイント。
///
/// AWS Lambdaランタイムを起動し、カスタム認証ハンドラーを登録します。
#[tokio::main]
async fn main() -> Result<(), Error> {
    run(service_fn(my_auth_handler)).await
}
/// カスタム認証handler
///
/// API Gatewayからの認証リクエストを処理し、適切なIAMポリシーを返す。
///
/// # Arguments
///
/// * `event` - API Gatewayからのカスタム認証リクエスト
///
/// # Returns
///
/// 認証が成功した場合は`ApiGatewayCustomAuthorizerResponse`を、失敗した場合は`Error`を返す
async fn my_auth_handler(
    event: LambdaEvent<ApiGatewayCustomAuthorizerRequest>,
) -> Result<ApiGatewayCustomAuthorizerResponse, Error> {
    let token = event.payload.authorization_token;
    match token {
        Some(ref token_value) if token_value == AUTHORIZED_TOKEN => {
            let policy = create_allow_policy(event.payload.method_arn.unwrap());
            Ok(create_authorizer_response(policy))
        }
        Some(_) => Err(Error::from("Unauthorized")),
        None => Err(Error::from("Missing token")),
    }
}
/// 認証成功時に許可ポリシードキュメントを生成する。
///
/// # Arguments
///
/// * `method_arn` - API GatewayのリソースARN
///
/// # Returns
///
/// `ApiGatewayCustomAuthorizerPolicy`のインスタンスを返す。
fn create_allow_policy(method_arn: String) -> ApiGatewayCustomAuthorizerPolicy {
    let stmt = IamPolicyStatement {
        action: vec![EXECUTE_API_ACTION.to_string()],
        resource: vec![method_arn],
        effect: Some(POLICY_EFFECT_ALLOW.to_owned()),
    };
    ApiGatewayCustomAuthorizerPolicy {
        version: Some(POLICY_VERSION.to_string()),
        statement: vec![stmt],
    }
}
/// 認証成功時のレスポンスを生成する。
///
/// # Arguments
///
/// * `policy` - 認証に成功したときに使用するIAMポリシー
///
/// # Returns
///
/// `ApiGatewayCustomAuthorizerResponse`のインスタンスを返する。
fn create_authorizer_response(policy: ApiGatewayCustomAuthorizerPolicy) -> ApiGatewayCustomAuthorizerResponse {
    let context = json!({ "simpleKey": "simpleValue" });
    ApiGatewayCustomAuthorizerResponse {
        principal_id: Some(PRINCIPAL_ID.to_string()),
        policy_document: policy,
        context,
        usage_identifier_key: None,
    }
}
Lambdaへのリクエスト時にAuthorizationヘッダにallowと指定していれば
実行を許可します。
Cargo.tomlは↓のようにします。
[package]
name = "lambda-authorizer"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
lambda_runtime = "0.6.0"
serde = "1.0.136"
tokio = { version = "1", features = ["macros"] }
tracing = { version = "0.1", features = ["log"] }
tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] }
aws-config = { version = "1.1.1", features = ["behavior-version-latest"] }
serde_json = "1.0.108"
再度ビルド&デプロイで動作確認してみましょう。
ちなみにこの状態だと、デプロイ時に認証関連の確認は出ません。
% sam build --beta-features % sam deploy --guided
curlで適当なAuthorizationヘッダを指定すると、
認証用Lambdaによりrejectされます。
% curl -X GET <発行されたendpoint URL> -H "Authorization:hoo"/
{"message":"Unauthorized"}%   
正しいヘッダを渡せば、HelloWorldApi Lambdaが実行されます。
% curl -X GET <発行されたendpoint URL> -H "Authorization:allow"
{"message":"Hello World!"}%   
Summary
今回はSAMを使って、RustでLambdaの実装をしてみました。
(まだbetaですが)ビルドもデプロイも簡単なのでぜひお試しください。









