実現したいこと
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)
回答1件
あなたの回答
tips
プレビュー
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。