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ですが)ビルドもデプロイも簡単なのでぜひお試しください。