質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
AWS Lambda

AWS Lambdaは、クラウド上でアプリを実行できるコンピューティングサービス。サーバーのプロビジョニングや管理を要せず複数のイベントに対してコードを実行します。カスタムロジック用いた他AWSサービスの拡張やAWSの規模やパフォーマンスを用いたバックエンドサービスを作成できます。

Amazon Cognito

Amazon Cognitoは、Webアプリケーションやモバイルアプリケーションの認証、許可、ユーザー管理をサポートするサービスです。ユーザー登録とサインインを行うか、FacebookやAmazon、Googleなどのサードパーティーを通じてサインインできる機能を提供します。

MySQL

MySQL(マイエスキューエル)は、TCX DataKonsultAB社などが開発するRDBMS(リレーショナルデータベースの管理システム)です。世界で最も人気の高いシステムで、オープンソースで開発されています。MySQLデータベースサーバは、高速性と信頼性があり、Linux、UNIX、Windowsなどの複数のプラットフォームで動作することができます。

ハッシュ

ハッシュは、高速にデータ検索を行うアルゴリズムのことです。

Rust

Rustは、MoFoが支援するプログラミング言語。高速性を維持しつつも、メモリ管理を安全に行うことが可能な言語です。同じコンパイル言語であるC言語やC++では困難だったマルチスレッドを実装しやすく、並行性という点においても優れています。

Q&A

解決済

1回答

1421閲覧

【Rust】AWS LambdaでCognitoの認証情報からユーザIDを割り出しMySQLで利用したいがシークレットハッシュが生成できない

akira_kano1101

総合スコア25

AWS Lambda

AWS Lambdaは、クラウド上でアプリを実行できるコンピューティングサービス。サーバーのプロビジョニングや管理を要せず複数のイベントに対してコードを実行します。カスタムロジック用いた他AWSサービスの拡張やAWSの規模やパフォーマンスを用いたバックエンドサービスを作成できます。

Amazon Cognito

Amazon Cognitoは、Webアプリケーションやモバイルアプリケーションの認証、許可、ユーザー管理をサポートするサービスです。ユーザー登録とサインインを行うか、FacebookやAmazon、Googleなどのサードパーティーを通じてサインインできる機能を提供します。

MySQL

MySQL(マイエスキューエル)は、TCX DataKonsultAB社などが開発するRDBMS(リレーショナルデータベースの管理システム)です。世界で最も人気の高いシステムで、オープンソースで開発されています。MySQLデータベースサーバは、高速性と信頼性があり、Linux、UNIX、Windowsなどの複数のプラットフォームで動作することができます。

ハッシュ

ハッシュは、高速にデータ検索を行うアルゴリズムのことです。

Rust

Rustは、MoFoが支援するプログラミング言語。高速性を維持しつつも、メモリ管理を安全に行うことが可能な言語です。同じコンパイル言語であるC言語やC++では困難だったマルチスレッドを実装しやすく、並行性という点においても優れています。

0グッド

0クリップ

投稿2023/04/29 00:47

編集2023/04/29 01:23

実現したいこと

AWSとRustを使ってサーバーレスアプリケーションを作っています。

最終的には、

  • Cognitoの認証情報を用いてエンドユーザの認証をする
  • 認証情報からLambda関数内でユーザIDを識別する
  • RDSでユーザIDから当該エンドユーザのデータを特定しアクセスする

ようなことがしたいです。

LambdaからコネクションするRDSではMySQLを使います。

エンドユーザは他のエンドユーザが扱うデータは見れないようにします。

このためのアクセスの制御にはユーザIDカラムでフィルタすることで制御するように記述します。

前提

認証情報にはAWS Cognitoからエンドユーザに認証されたユーザ情報を使いますが、ひとまず動作するかを確認することが必要なため、ユーザープールにユーザを一人登録しておき、それを利用することにします。

イメージ説明
イメージ説明
イメージ説明

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

セキュリティを考え一部伏せています。また字数の関係上必要のないログは除外しています。

console

1% cargo run 2 Finished dev [unoptimized + debuginfo] target(s) in 0.16s 3 Running `target/debug/cookie` 42023-04-29T00:04:49.113131Z INFO cookie: secret_hash: f69a01d94cae7dxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 52023-04-29T00:04:49.137752Z INFO cookie: username: 5f5fa04d-0ad9-4fxxxxxxxxxxxxxxxxxxxx 62023-04-29T00:04:49.137757Z INFO cookie: password: mukwxxxxxxxxxxxxxxxx 72023-04-29T00:04:49.137763Z INFO cookie: client_id: 5132aixxxxxxxxxxxxxxxxxxxx 82023-04-29T00:04:49.276863Z WARN cookie: response: Err(ServiceError(ServiceError { source: ResourceNotFoundException(ResourceNotFoundException { message: Some("User pool client 5132aixxxxxxxxxxxxxxxxxxxx does not exist."), meta: ErrorMetadata { code: Some("ResourceNotFoundException"), message: Some("User pool client 5132aixxxxxxxxxxxxxxxxxxxx does not exist."), extras: Some({"aws_request_id": "49378c3a-99ee-4exxxxxxxxxxxxxxxxxxxx"}) } }), raw: Response { inner: Response { status: 400, version: HTTP/2.0, headers: {"date": "Sat, 29 Apr 2023 00:04:49 GMT", "content-type": "application/x-amz-json-1.1", "content-length": "110", "x-amzn-requestid": "49378c3a-99ee-4exxxxxxxxxxxxxxxxxxxx", "x-amzn-errortype": "ResourceNotFoundException:", "x-amzn-errormessage": "User pool client 5132aixxxxxxxxxxxxxxxxxxxx does not exist."}, body: SdkBody { inner: Once(Some(b"{\"__type\":\"ResourceNotFoundException\",\"message\":\"User pool client 5132aixxxxxxxxxxxxxxxxxxxx does not exist.\"}")), retryable: true } }, properties: SharedPropertyBag(Mutex { data: PropertyBag, poisoned: false, .. }) } })) 9Error: service error 10 11Caused by: 12 0: ResourceNotFoundException: User pool client 5132aixxxxxxxxxxxxxxxxxxxx does not exist. 13 1: ResourceNotFoundException: User pool client 5132aixxxxxxxxxxxxxxxxxxxx does not exist.

該当のソースコード

toml

1name = "cookie" 2version = "0.1.0" 3edition = "2021" 4 5# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 6 7[dependencies] 8anyhow = "1.0.70" 9aws-config = "0.55.1" 10aws-sdk-cognitoidentityprovider = "0.26.0" 11aws_get_secret_value = { git = "https://github.com/kano1101/aws_get_secret_value.git" } 12hex = "0.4.3" 13hmac = "0.12.1" 14jsonwebtoken = "8.3.0" 15serde = "1.0.160" 16sha2 = "0.10.6" 17tokio = { version = "1.28.0", features = ["full"] } 18tracing = { version = "0.1", features = ["log"] } 19tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] }

rust

1use jsonwebtoken::{decode, DecodingKey, Validation}; 2 3fn get_user_id_from_token(token: &str, secret: &str) -> Option<String> { 4 let validation = Validation::default(); // デフォルトの検証オプションを使用 5 6 // トークンの検証とデコード 7 match decode::<TokenClaims>( 8 token, 9 &DecodingKey::from_secret(secret.as_ref()), 10 &validation, 11 ) { 12 Ok(decoded) => Some(decoded.claims.sub), // "sub"属性はCognitoのユーザIDを表す 13 Err(_) => None, 14 } 15} 16 17#[derive(Debug, serde::Deserialize)] 18struct TokenClaims { 19 sub: String, 20 // 他のトークンクレームを必要に応じて追加 21} 22 23fn get_secret_hash( 24 username: &str, 25 secret_key: &str, 26 client_id: &str, 27 user_pool_id: &str, 28) -> anyhow::Result<String> { 29 use hmac::{Hmac, Mac}; 30 use sha2::Sha256; 31 32 let message = format!("{}{}{}", user_pool_id, client_id, username).to_string(); 33 let message = message.as_bytes(); 34 35 // HMAC-SHA256のインスタンスを作成し、秘密のキーを設定する 36 let mut hmac = 37 Hmac::<Sha256>::new_from_slice(secret_key.as_bytes()).expect("Invalid key length"); 38 39 // メッセージをハッシュ化する 40 hmac.update(message); 41 42 // ハッシュを取得する 43 let result = hmac.finalize(); 44 45 // バイト列から16進数文字列に変換する 46 let secret_hash = result 47 .into_bytes() 48 .iter() 49 .map(|byte| format!("{:02x}", byte)) 50 .collect::<String>(); 51 tracing::info!("secret_hash: {}", secret_hash); 52 53 Ok(secret_hash) 54} 55 56use aws_sdk_cognitoidentityprovider as cognitoidentityprovider; 57 58async fn get_id_token( 59 username: &str, 60 password: &str, 61 secret_hash: &str, 62 client_id: &str, 63) -> anyhow::Result<String> { 64 let config = aws_config::load_from_env().await; 65 66 let client = cognitoidentityprovider::Client::new(&config); 67 tracing::info!("username: {username}"); 68 tracing::info!("password: {password}"); 69 tracing::info!("client_id: {client_id}"); 70 71 // Cognitoから発行されたIDトークンを取得する 72 let response_builder = client 73 .initiate_auth() 74 .auth_flow(cognitoidentityprovider::types::AuthFlowType::UserPasswordAuth) 75 .auth_parameters("USERNAME".to_string(), username.to_string()) 76 .auth_parameters("PASSWORD".to_string(), password.to_string()) 77 .auth_parameters("SECRET_HASH".to_string(), secret_hash.to_string()) 78 .client_id(client_id.to_string()); 79 80 let response = response_builder.send().await; 81 tracing::warn!("response: {:?}", response); 82 let response = response?; 83 84 // IDトークンを出力する 85 let id_token = response 86 .authentication_result() 87 .ok_or(anyhow::anyhow!("missing authentication result"))? 88 .id_token() 89 .ok_or(anyhow::anyhow!("missing id token"))?; 90 91 Ok(id_token.to_string()) 92} 93 94#[tokio::main] 95async fn main() -> anyhow::Result<()> { 96 tracing_subscriber::fmt::init(); 97 98 let secret_value = 99 aws_get_secret_value::get_secret_value("ap-northeast-1", "SecretsManager").await?; 100 101 let username = secret_value["COGNITO_USERNAME"] 102 .as_str() 103 .ok_or(anyhow::anyhow!("fail to get username"))?; 104 105 let password = secret_value["COGNITO_PASSWORD"] 106 .as_str() 107 .ok_or(anyhow::anyhow!("fail to get password"))?; 108 109 let client_id = secret_value["COGNITO_CLIENT_ID"] 110 .as_str() 111 .ok_or(anyhow::anyhow!("fail to get client id"))?; 112 113 let user_pool_id = secret_value["USER_POOL_ID"] 114 .as_str() 115 .ok_or(anyhow::anyhow!( 116 "fail to get user pool id from secret value." 117 ))?; 118 119 let secret_name = secret_value["APPLICATION_SECRET"] 120 .as_str() 121 .ok_or(anyhow::anyhow!( 122 "fail to get application secret from secret value." 123 ))?; 124 125 let secret_hash = &get_secret_hash(username, secret_name, client_id, user_pool_id)?; 126 127 let token = get_id_token(username, password, secret_hash, client_id).await?; 128 129 match get_user_id_from_token(&token, secret_name) { 130 Some(user_id) => { 131 println!("ユーザID: {}", user_id); 132 // ユーザIDを使ってMySQLのデータにアクセスするなどの処理を行う 133 } 134 None => println!("トークンの検証に失敗しました。"), 135 } 136 137 Ok(()) 138}``` 139 140そして、AWS Lambdaの中の処理に、ユーザIDを割り出す様にロジックを記述します。 141 142```rust 143async fn get_id_token(username: &str, password: &str, client_id: &str) -> anyhow::Result<String> { 144 let config = aws_config::load_from_env().await; 145 tracing::info!("環境設定を取得しました。"); 146 147 let client = cognitoidentityprovider::Client::new(&config); 148 tracing::info!("環境設定からクライアントを取得しました。"); 149 150 tracing::info!("次の三つの値をinitiate_auth()のオブジェクトに設定します。"); 151 tracing::info!("username: {username}"); 152 tracing::info!("password: {password}"); 153 tracing::info!("client_id: {client_id}"); 154 155 // Cognitoから発行されたIDトークンを取得する 156 let response_builder = client 157 .initiate_auth() 158 .auth_flow(cognitoidentityprovider::types::AuthFlowType::UserPasswordAuth) 159 .auth_parameters("USERNAME".to_string(), username.to_string()) 160 .auth_parameters("PASSWORD".to_string(), password.to_string()) 161 .client_id(client_id.to_string()); 162 // .timeout(std::time::Duration::from_secs(4)); 163 tracing::info!("response builderを構築しました。"); 164 tracing::warn!("response_builder: {:?}", response_builder); 165 166 let response = response_builder.send().await; 167 tracing::info!("レスポンスを送信しました。"); 168 tracing::warn!("response: {:?}", response); 169 let response = response?; 170 tracing::info!("レスポンスを解体しました。"); 171 172 // IDトークンを出力する 173 let id_token = response 174 .authentication_result() 175 .ok_or(anyhow::anyhow!("missing authentication result"))? 176 .id_token() 177 .ok_or(anyhow::anyhow!("missing id token"))?; 178 tracing::info!("IDトークンを取得しました。"); 179 180 Ok(id_token.to_string()) 181}

シークレットハッシュを生成してみようとしましたがうまくいきません。

これを生成する方法、もしくは他に解決方法があればそれを知りたいです。

込み入った内容で恐縮ですが、解決方法をお分かりの方いらっしゃいましたらご教示いただけると幸いです。

よろしくお願いいたします。

補足情報(FW/ツールのバージョンなど)

OSはmacOSです。

console

1cargo --version 2cargo 1.65.0 (4bc8f24d3 2022-10-20)

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

自己解決

自己解決しました。

toml

1[package] 2name = "cookie" 3version = "0.1.0" 4edition = "2021" 5 6# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 8[dependencies] 9anyhow = "1.0.70" 10aws-config = "0.55.1" 11aws-sdk-cognitoidentityprovider = "0.26.0" 12aws_get_secret_value = { git = "https://github.com/kano1101/aws_get_secret_value.git" } 13base64 = "0.21.0" 14hex = "0.4.3" 15hmac = "0.12.1" 16jsonwebkey-convert = "0.3.0" 17jsonwebtoken = "8.3.0" 18reqwest = { versiont = "0.11.17", features = ["json"] } 19serde = "1.0.160" 20serde_json = "1.0.96" 21sha2 = "0.10.6" 22tokio = { version = "1.28.0", features = ["full"] } 23tracing = { version = "0.1", features = ["log"] } 24tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] }

rust

1async fn get_user_id_from_token(token: &str) -> Option<String> { 2 use jsonwebtoken::Algorithm::*; 3 4 let kid = jsonwebtoken::decode_header(token).ok()?.kid?; 5 6 let validation = Validation::new(RS256); 7 8 use jsonwebkey_convert::{JsonWebKey, JsonWebKeySet}; 9 10 // JWKSのエンドポイントURL 11 let jwks_url = "https://cognito-idp.ap-northeast-1.amazonaws.com/ap-northeast-1_XXXXXXXXXX/.well-known/jwks.json"; 12 13 // JWKSを取得 14 let response = reqwest::get(jwks_url).await.ok()?; 15 let jwks: JsonWebKeySet = response.json().await.ok()?; // JWKSをパースする適切なデータ型に置き換える 16 17 // JWKSからRSA公開鍵のコンポーネント(nとe)を取得 18 let rsa_key: JsonWebKey = jwks.keys.into_iter().find(|key| match &key { 19 JsonWebKey::RSAPublicKey { value, .. } => value.generic.kid.as_ref() == Some(&kid), 20 _ => false, 21 })?; 22 23 // 適切な方法で適切なキーを選択する 24 let modulus = rsa_key.rsa_public_key()?.n.to_base64url(); 25 let exponent = rsa_key.rsa_public_key()?.e.to_base64url(); 26 27 let modulus = modulus.as_str(); 28 let exponent = exponent.as_str(); 29 30 // RSA公開鍵を作成し、デコーディングキーを生成 31 let decoding_key = DecodingKey::from_rsa_components(modulus, exponent).ok()?; 32 33 Some( 34 decode::<TokenClaims>(token, &decoding_key, &validation) 35 .or_else(|err| { 36 tracing::info!("err: {}", err); 37 Err(err) 38 }) 39 .and_then(|ok| { 40 tracing::info!("ok: {:?}", ok); 41 Ok(ok) 42 }) 43 .ok()? 44 .claims 45 .sub, 46 ) 47} 48 49#[derive(Debug, serde::Deserialize)] 50struct TokenClaims { 51 sub: String, 52 // 他のトークンクレームを必要に応じて追加 53}

rust

1// secret_keyはクライアントシークレットのこと 2fn get_secret_hash(username: &str, secret_key: &str, client_id: &str) -> anyhow::Result<String> { 3 use base64::Engine as _; 4 use hmac::{Hmac, Mac}; 5 use sha2::Sha256; 6 7 let mut mac = 8 Hmac::<Sha256>::new_from_slice(secret_key.as_bytes()).expect("HMAC initialization failed"); 9 mac.update(username.as_bytes()); 10 mac.update(client_id.as_bytes()); 11 12 let result = mac.finalize().into_bytes(); 13 let secret_hash = base64::engine::general_purpose::STANDARD.encode(result); 14 15 Ok(secret_hash) 16}

rust

1use aws_sdk_cognitoidentityprovider as cognitoidentityprovider; 2 3async fn get_id_token( 4 username: &str, 5 password: &str, 6 secret_hash: &str, 7 client_id: &str, 8 user_pool_id: &str, 9) -> anyhow::Result<String> { 10 let config = aws_config::load_from_env().await; 11 12 let client = cognitoidentityprovider::Client::new(&config); 13 14 let response_builder = client 15 .admin_initiate_auth() 16 .auth_flow(cognitoidentityprovider::types::AuthFlowType::AdminUserPasswordAuth) 17 .auth_parameters("USERNAME".to_string(), username.to_string()) 18 .auth_parameters("PASSWORD".to_string(), password.to_string()) 19 .auth_parameters("SECRET_HASH".to_string(), secret_hash.to_string()) 20 .client_id(client_id.to_string()) 21 .user_pool_id(user_pool_id.to_string()); 22 23 let response = response_builder.send().await; 24 let response = response?; 25 26 let id_token = response 27 .authentication_result() 28 .ok_or(anyhow::anyhow!("missing authentication result"))? 29 .id_token() 30 .ok_or(anyhow::anyhow!("missing id token"))?; 31 32 Ok(id_token.to_string()) 33}

rust

1#[tokio::main] 2async fn main() -> anyhow::Result<()> { 3 tracing_subscriber::fmt::init(); 4 tracing::error!("error message"); 5 tracing::warn!("warn message"); 6 tracing::info!("info message"); 7 tracing::debug!("debug message"); 8 tracing::trace!("trace message"); 9 10 let secret_value = 11 aws_get_secret_value::get_secret_value("ap-northeast-1", "***SECRETS_MANAGER***").await?; 12 13 let username = secret_value["COGNITO_USERNAME"] 14 .as_str() 15 .ok_or(anyhow::anyhow!("fail to get username"))?; 16 17 let password = secret_value["COGNITO_PASSWORD"] 18 .as_str() 19 .ok_or(anyhow::anyhow!("fail to get password"))?; 20 21 let client_id = secret_value["COGNITO_CLIENT_ID"] 22 .as_str() 23 .ok_or(anyhow::anyhow!("fail to get client id"))?; 24 25 let user_pool_id = secret_value["USER_POOL_ID"] 26 .as_str() 27 .ok_or(anyhow::anyhow!( 28 "fail to get user pool id from secret value." 29 ))?; 30 31 let secret_key = secret_value["APPLICATION_SECRET"] 32 .as_str() 33 .ok_or(anyhow::anyhow!( 34 "fail to get application secret from secret value." 35 ))?; 36 37 let secret_hash = &get_secret_hash(username, secret_key, client_id)?; 38 39 let token = get_id_token(username, password, secret_hash, client_id, user_pool_id).await?; 40 41 match get_user_id_from_token(&token).await { 42 Some(user_id) => { 43 println!("ユーザID: {}", user_id); 44 // ユーザIDを使ってMySQLのデータにアクセスするなどの処理を行う 45 } 46 None => println!("トークンの検証に失敗しました。"), 47 } 48 49 Ok(()) 50}

これで動作しました。

投稿2023/05/02 11:51

akira_kano1101

総合スコア25

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問