@@ -5,17 +5,33 @@ use aws_config::{BehaviorVersion, Region};
55use aws_credential_types:: provider:: future:: ProvideCredentials as ProvideCredentialsFuture ;
66use aws_sdk_kms:: config:: { Credentials , ProvideCredentials } ;
77use serde:: { Deserialize , Serialize } ;
8+ use std:: collections:: hash_map:: DefaultHasher ;
9+ use std:: hash:: { Hash , Hasher } ;
10+ use std:: ops:: Deref ;
811use thirdweb_core:: auth:: ThirdwebAuth ;
912use thirdweb_core:: iaw:: AuthToken ;
1013use vault_types:: enclave:: auth:: Auth as VaultAuth ;
1114
1215use crate :: error:: EngineError ;
1316
17+ /// Cache for AWS KMS clients to avoid recreating connections
18+ pub type KmsClientCache = moka:: future:: Cache < u64 , aws_sdk_kms:: Client > ;
19+
1420impl SigningCredential {
1521 /// Create a random private key credential for testing
1622 pub fn random_local ( ) -> Self {
1723 SigningCredential :: PrivateKey ( PrivateKeySigner :: random ( ) )
1824 }
25+
26+ /// Inject KMS cache into AWS KMS credentials (useful after deserialization)
27+ pub fn with_aws_kms_cache ( self , kms_client_cache : & KmsClientCache ) -> Self {
28+ match self {
29+ SigningCredential :: AwsKms ( creds) => {
30+ SigningCredential :: AwsKms ( creds. with_cache ( kms_client_cache. clone ( ) ) )
31+ }
32+ other => other,
33+ }
34+ }
1935}
2036
2137#[ derive( Debug , Clone , Serialize , Deserialize ) ]
@@ -38,6 +54,18 @@ pub struct AwsKmsCredential {
3854 pub secret_access_key : String ,
3955 pub key_id : String ,
4056 pub region : String ,
57+ #[ serde( skip) ]
58+ pub kms_client_cache : Option < KmsClientCache > ,
59+ }
60+
61+ impl Hash for AwsKmsCredential {
62+ fn hash < H : Hasher > ( & self , state : & mut H ) {
63+ self . access_key_id . hash ( state) ;
64+ self . secret_access_key . hash ( state) ;
65+ self . key_id . hash ( state) ;
66+ self . region . hash ( state) ;
67+ // Don't hash the cache - it's not part of the credential identity
68+ }
4169}
4270
4371impl ProvideCredentials for AwsKmsCredential {
@@ -57,14 +85,71 @@ impl ProvideCredentials for AwsKmsCredential {
5785}
5886
5987impl AwsKmsCredential {
60- pub async fn get_signer ( & self , chain_id : Option < ChainId > ) -> Result < AwsSigner , EngineError > {
88+ /// Create a new AwsKmsCredential with cache
89+ pub fn new (
90+ access_key_id : String ,
91+ secret_access_key : String ,
92+ key_id : String ,
93+ region : String ,
94+ kms_client_cache : KmsClientCache ,
95+ ) -> Self {
96+ Self {
97+ access_key_id,
98+ secret_access_key,
99+ key_id,
100+ region,
101+ kms_client_cache : Some ( kms_client_cache) ,
102+ }
103+ }
104+
105+ /// Inject cache into this credential (useful after deserialization)
106+ pub fn with_cache ( mut self , kms_client_cache : KmsClientCache ) -> Self {
107+ self . kms_client_cache = Some ( kms_client_cache) ;
108+ self
109+ }
110+
111+ /// Create a cache key from the credential
112+ fn cache_key ( & self ) -> u64 {
113+ let mut hasher = DefaultHasher :: new ( ) ;
114+ self . hash ( & mut hasher) ;
115+ hasher. finish ( )
116+ }
117+
118+ /// Create a new AWS KMS client (without caching)
119+ async fn create_kms_client ( & self ) -> Result < aws_sdk_kms:: Client , EngineError > {
61120 let config = aws_config:: defaults ( BehaviorVersion :: latest ( ) )
62121 . credentials_provider ( self . clone ( ) )
63122 . region ( Region :: new ( self . region . clone ( ) ) )
64123 . load ( )
65124 . await ;
66- let client = aws_sdk_kms:: Client :: new ( & config) ;
125+ Ok ( aws_sdk_kms:: Client :: new ( & config) )
126+ }
67127
128+ /// Get a cached AWS KMS client, creating one if it doesn't exist
129+ async fn get_cached_kms_client ( & self ) -> Result < aws_sdk_kms:: Client , EngineError > {
130+ match & self . kms_client_cache {
131+ Some ( cache) => {
132+ let cache_key = self . cache_key ( ) ;
133+
134+ cache
135+ . try_get_with ( cache_key, async {
136+ tracing:: debug!( "Creating new KMS client for key: {}" , cache_key) ;
137+ self . create_kms_client ( ) . await
138+ } )
139+ . await
140+ . map_err ( |e| e. deref ( ) . clone ( ) )
141+ }
142+ None => {
143+ // Fallback to creating a new client without caching
144+ tracing:: debug!( "No cache available, creating new KMS client" ) ;
145+ self . create_kms_client ( ) . await
146+ }
147+ }
148+ }
149+
150+ /// Get signer (uses cache if available)
151+ pub async fn get_signer ( & self , chain_id : Option < ChainId > ) -> Result < AwsSigner , EngineError > {
152+ let client = self . get_cached_kms_client ( ) . await ?;
68153 let signer = AwsSigner :: new ( client, self . key_id . clone ( ) , chain_id) . await ?;
69154 Ok ( signer)
70155 }
0 commit comments