RustでAWS LambdaからRDS Proxyに接続したいがSecrets Managerの情報を取得できず、MySQLにアクセスできない問題を解消したい

前提

Wasmでブラウザ上にて動作するアプリケーションを作りたいと思い、現在制作に取り掛かっています。

このアプリケーションのバックエンドの永続化の手段としてAWSのRDSを選択することに決めました。

使っている言語はRustで、バックエンドの構成としては大体こちら

RDS Proxy 経由で RDS に接続する

をほぼ引用するという形をとり、RDS Proxyを経由してRDSへと接続しようと考えています。

実現したいこと

curlやreqwestクレート等を用いてAPI Gatewayを通じてAWS Lambdaをキックし、RDS ProxyさらにはRDSへと接続するバックエンドを作成したいです。

当面の目標としては、RDS Proxyの画面にアクセスして確認できるQueryRequestsのグラフにアクセス履歴が表示されていない状態ですので、これを表示されるようにすることです。

なお、RDSはMySQLを利用しようと思っていて、Rust側としてはsqlxクレートを使おうと考えています。

発生している問題・エラーメッセージ

エラーメッセージではなくログになりますが、下記の該当ソースコードをAWSにアップロードしブラウザ上でテストを実行したときに出力されるログを掲載させていただきます。

補足として

AWS Secrets Managerに作成したシークレットの名前は"SecretsManager"です。

Lambda関数の名前は"Test_Function_02"です。

リージョンは"ap-northeast-3"です。

一部情報は念のため"x"で伏せています。

改行がないため少し見づらいですが、そのままを掲載する方が良いと思ったのでご容赦をお願いいたします。

START RequestId: 41e749a7-dc70-4118-9610-13fde7eaxxxx Version: $LATEST 2022-10-17T08:49:28.071756Z INFO lambda_function_01: region="ap-northeast-3" 2022-10-17T08:49:28.271553Z INFO lambda_function_01: get_secret_value_string="GetSecretValue { handle: Handle { client: Client { connector: DynConnector, middleware: DynMiddleware, retry_policy: Standard { config: Config { initial_retry_tokens: 500, retry_cost: 5, no_retry_increment: 1, timeout_retry_cost: 10, max_attempts: 3, initial_backoff: 1s, max_backoff: 20s, base: 0x5566f7xxxx }, shared_state: CrossRequestRetryState { quota_available: Mutex { data: 500, poisoned: false, .. } } }, timeout_config: Config { api: Api { call: Unset, call_attempt: Unset }, http: Http { connect: Unset, write: Unset, read: Unset, tls_negotiation: Unset }, tcp: Tcp { connect: Unset, write: Unset, read: Unset } }, sleep_impl: Some(TokioSleep) }, conf: Config }, inner: Builder { secret_id: None, version_id: None, version_stage: None } }" 2022-10-17T08:49:28.271607Z INFO lambda_function_01: secret_id_string="GetSecretValue { handle: Handle { client: Client { connector: DynConnector, middleware: DynMiddleware, retry_policy: Standard { config: Config { initial_retry_tokens: 500, retry_cost: 5, no_retry_increment: 1, timeout_retry_cost: 10, max_attempts: 3, initial_backoff: 1s, max_backoff: 20s, base: 0x5566f7xxxx }, shared_state: CrossRequestRetryState { quota_available: Mutex { data: 500, poisoned: false, .. } } }, timeout_config: Config { api: Api { call: Unset, call_attempt: Unset }, http: Http { connect: Unset, write: Unset, read: Unset, tls_negotiation: Unset }, tcp: Tcp { connect: Unset, write: Unset, read: Unset } }, sleep_impl: Some(TokioSleep) }, conf: Config }, inner: Builder { secret_id: Some(\"SecretsManager\"), version_id: None, version_stage: None } }" 2022-10-17T08:49:28.365589Z INFO lambda_function_01: sent_string="Err(ServiceError { err: GetSecretValueError { kind: Unhandled(Error { code: Some(\"AccessDeniedException\"), message: Some(\"User: arn:aws:sts::67913522xxxx:assumed-role/Test_Function_02-role-lip4xxxx/Test_Function_02 is not authorized to perform: secretsmanager:GetSecretValue on resource: SecretsManager because no identity-based policy allows the secretsmanager:GetSecretValue action\"), request_id: Some(\"bb4d7bc5-f651-49ec-9b20-7d6eb371xxxx\"), extras: {} }), meta: Error { code: Some(\"AccessDeniedException\"), message: Some(\"User: arn:aws:sts::67913522xxxx:assumed-role/Test_Function_02-role-lip4xxxx/Test_Function_02 is not authorized to perform: secretsmanager:GetSecretValue on resource: SecretsManager because no identity-based policy allows the secretsmanager:GetSecretValue action\"), request_id: Some(\"bb4d7bc5-f651-49ec-9b20-7d6eb371xxxx\"), extras: {} } }, raw: Response { inner: Response { status: 400, version: HTTP/1.1, headers: {\"x-amzn-requestid\": \"bb4d7bc5-f651-49ec-9b20-7d6eb371xxxx\", \"content-type\": \"application/x-amz-json-1.1\", \"content-length\": \"308\", \"date\": \"Mon, 17 Oct 2022 08:49:28 GMT\", \"connection\": \"close\"}, body: SdkBody { inner: Once(Some(b\"{\\\"__type\\\":\\\"AccessDeniedException\\\",\\\"Message\\\":\\\"User: arn:aws:sts::67913522xxxx:assumed-role/Test_Function_02-role-lip4xxxx/Test_Function_02 is not authorized to perform: secretsmanager:GetSecretValue on resource: SecretsManager because no identity-based policy allows the secretsmanager:GetSecretValue action\\\"}\")), retryable: true } }, properties: SharedPropertyBag(Mutex { data: PropertyBag, poisoned: false, .. }) } })" 2022-10-17T08:49:28.365643Z INFO lambda_function_01: value_string="\"No value!\"" END RequestId: 41e749a7-dc70-4118-9610-13fde7eaxxxx REPORT RequestId: 41e749a7-dc70-4118-9610-13fde7eaxxxx Duration: 599.81 ms Billed Duration: 635 ms Memory Size: 128 MB Max Memory Used: 25 MB Init Duration: 35.15 ms

該当のソースコード

toml

[package]name = "lambda_function_01"version = "0.1.0"edition = "2021" [dependencies]aws-config = "0.49.0"aws-sdk-secretsmanager = "0.19.0"lambda_runtime = "0.7.0"serde_json = "1.0.86"sqlx = { version = "0.6.2", features = ["runtime-tokio-rustls", "mysql"] }tokio = { version = "1.21.2", features = ["full"] }tracing = { version = "0.1", features = ["log"] }tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] }

rust

use aws_config::meta::region::RegionProviderChain;use aws_sdk_secretsmanager::{output::GetSecretValueOutput, Client};use lambda_runtime::{service_fn, Error, LambdaEvent};use serde_json::{json, Value};use tracing::info;use tracing_subscriber; #[tokio::main]async fn main() -> Result<(), Error> { let func = service_fn(func); lambda_runtime::run(func).await?; Ok(())} async fn func(_event: LambdaEvent<Value>) -> Result<Value, Error> { tracing_subscriber::fmt::init(); let region_provider = RegionProviderChain::default_provider().or_else("ap-northeast-3"); let region = region_provider.region().await.unwrap().as_ref().to_string(); info!(region); let shared_config = aws_config::from_env().region(region_provider).load().await; let client = Client::new(&shared_config); let get_secret_value = client.get_secret_value(); let get_secret_value_string = format!("{:?}", get_secret_value); info!(get_secret_value_string); let secret_id = get_secret_value.secret_id("SecretsManager"); let secret_id_string = format!("{:?}", secret_id); info!(secret_id_string); let sent = secret_id.send().await; let sent_string = format!("{:?}", sent); info!(sent_string); let resp = sent.unwrap_or(GetSecretValueOutput::builder().build()); let value = resp.secret_string().unwrap_or("No value!").to_string(); let value_string = format!("{:?}", value); info!(value_string); // ...上記でSecrets情報ができるはずなのでこれを使いRDS Proxyを通してRDSに接続したい... // しかしエラーが発生してしまうので秘密情報を取得できない // let username = "admin"; // let password = "xxxxxx-xxxxxx-xxxxxx"; // let host = "xxxxx.proxy-xxxxxxxxxxxx.ap-northeast-3.rds.amazonaws.com"; // let database = "test_db"; // let url = format!("mysql://{}:{}@{}/{}", username, password, host, database); // let _pool = sqlx::mysql::MySqlPoolOptions::new() // .max_connections(5) // .connect(&url) // .await?; // また、上記のように秘密情報をベタがきしたらタイムアウト(10秒に設定しています)によりエラーが返る Ok(json!({ "statusCode": 200, "headers": { "content-type": "application/json" }, "body": "Hello world!", }))}

出力結果

なお、テストの実行結果として以下のように動作をしています。

{ "body": "Hello world!", "headers": { "content-type": "application/json" }, "statusCode": 200 }

実施手順としては

% cargo lambda build --release --arm64 --output-format zip

で生成されたbootstrap.zipを直接ブラウザからアップロードし、ブラウザ上にあるテストタブに切り替えてテストボタンを押下して実施しています。

補足

ちなみにベタがきの部分は冒頭の引用サイトの内容を参考にして実行を試みました。

伏せておいた情報としては、パスワードにはSecretsManagerのパスワードを使い、ホストにはRDS Proxyのエンドポイントを使い接続を試しています。

さいごに

どうぞ、お分かりの方がいらっしゃいましたらご教示のほどよろしくお願いいたします。

コメントを投稿

0 コメント