@@ -131,13 +131,18 @@ impl VaultCredentialDriver {
131131 & self ,
132132 request : StoreCredentialRequest ,
133133 ) -> Result < CredentialHandle , Status > {
134- let token = self . auth_token ( ) . await ?;
135134 let logical_path = if let Some ( existing_handle) = request. existing_handle . as_ref ( ) {
136135 Self :: logical_path_from_handle ( existing_handle) ?
137136 } else {
138137 managed_secret_path ( & request. provider_name , & request. credential_key )
139138 } ;
140139 validate_secret_path ( & logical_path) . map_err ( Status :: invalid_argument) ?;
140+ validate_managed_secret_path (
141+ & request. provider_name ,
142+ & request. credential_key ,
143+ & logical_path,
144+ ) ?;
145+ let token = self . auth_token ( ) . await ?;
141146 let reference = VaultSecretReference {
142147 api_path : api_path_for_reference (
143148 & self . settings . mount ,
@@ -157,9 +162,14 @@ impl VaultCredentialDriver {
157162 }
158163
159164 pub async fn delete_credential ( & self , request : DeleteCredentialRequest ) -> Result < ( ) , Status > {
160- let token = self . auth_token ( ) . await ?;
161165 let handle = Self :: handle_from_request ( "delete" , request. handle ) ?;
162166 let logical_path = Self :: logical_path_from_handle ( & handle) ?;
167+ validate_managed_secret_path (
168+ & request. provider_name ,
169+ & request. credential_key ,
170+ & logical_path,
171+ ) ?;
172+ let token = self . auth_token ( ) . await ?;
163173 let api_path = delete_api_path_for_reference (
164174 & self . settings . mount ,
165175 self . settings . kv_version ,
@@ -172,11 +182,16 @@ impl VaultCredentialDriver {
172182 & self ,
173183 requests : Vec < ResolveCredentialRequest > ,
174184 ) -> Result < Vec < ResolvedCredential > , Status > {
175- let token = self . auth_token ( ) . await ?;
176185 let mut responses = Vec :: with_capacity ( requests. len ( ) ) ;
186+ let mut resolved_requests = Vec :: with_capacity ( requests. len ( ) ) ;
177187 for request in requests {
178188 let handle = Self :: handle_from_request ( & request. request_id , request. handle ) ?;
179189 let logical_path = Self :: logical_path_from_handle ( & handle) ?;
190+ validate_managed_secret_path (
191+ & request. provider_name ,
192+ & request. credential_key ,
193+ & logical_path,
194+ ) ?;
180195 let reference = VaultSecretReference {
181196 api_path : api_path_for_reference (
182197 & self . settings . mount ,
@@ -186,9 +201,14 @@ impl VaultCredentialDriver {
186201 key : STORED_VALUE_KEY . to_string ( ) ,
187202 kv_version : self . settings . kv_version ,
188203 } ;
204+ resolved_requests. push ( ( request. request_id , reference) ) ;
205+ }
206+
207+ let token = self . auth_token ( ) . await ?;
208+ for ( request_id, reference) in resolved_requests {
189209 let value = self . resolve_secret_value ( & reference, & token) . await ?;
190210 responses. push ( ResolvedCredential {
191- request_id : request . request_id ,
211+ request_id,
192212 value,
193213 expires_at_ms : 0 ,
194214 } ) ;
@@ -690,6 +710,20 @@ fn managed_secret_path(provider_name: &str, credential_key: &str) -> String {
690710 format ! ( "openshell/provider-credentials/{}" , & hex[ ..40 ] )
691711}
692712
713+ fn validate_managed_secret_path (
714+ provider_name : & str ,
715+ credential_key : & str ,
716+ logical_path : & str ,
717+ ) -> Result < ( ) , Status > {
718+ let expected = managed_secret_path ( provider_name, credential_key) ;
719+ if logical_path == expected {
720+ return Ok ( ( ) ) ;
721+ }
722+ Err ( Status :: invalid_argument ( format ! (
723+ "vault credential handle path does not match the managed path for provider credential '{credential_key}'"
724+ ) ) )
725+ }
726+
693727async fn read_secret_file ( path : & Path , description : & str ) -> Result < String , Status > {
694728 let contents = tokio:: fs:: read_to_string ( path) . await . map_err ( |err| {
695729 Status :: unauthenticated ( format ! (
@@ -920,6 +954,19 @@ mod tests {
920954 assert ! ( err. message( ) . contains( "path segments" ) ) ;
921955 }
922956
957+ #[ test]
958+ fn handle_rejects_unexpected_managed_path ( ) {
959+ let err = validate_managed_secret_path (
960+ "nvidia-prod" ,
961+ "NVIDIA_API_KEY" ,
962+ "openshell/provider-credentials/other" ,
963+ )
964+ . unwrap_err ( ) ;
965+
966+ assert_eq ! ( err. code( ) , Code :: InvalidArgument ) ;
967+ assert ! ( err. message( ) . contains( "managed path" ) ) ;
968+ }
969+
923970 #[ tokio:: test]
924971 async fn store_and_resolve_token_file_kv2_secret ( ) {
925972 let mock_server = MockServer :: start ( ) . await ;
@@ -985,10 +1032,9 @@ mod tests {
9851032 #[ tokio:: test]
9861033 async fn store_with_existing_handle_reuses_logical_path ( ) {
9871034 let mock_server = MockServer :: start ( ) . await ;
1035+ let logical_path = managed_secret_path ( "nvidia-prod" , "NVIDIA_API_KEY" ) ;
9881036 Mock :: given ( method ( "POST" ) )
989- . and ( path (
990- "/v1/secret/data/openshell/provider-credentials/existing" ,
991- ) )
1037+ . and ( path ( format ! ( "/v1/secret/data/{logical_path}" ) ) )
9921038 . and ( header ( "x-vault-token" , "dev-token" ) )
9931039 . and ( body_string_contains ( "updated-secret" ) )
9941040 . respond_with ( ResponseTemplate :: new ( 200 ) )
@@ -1010,21 +1056,20 @@ mod tests {
10101056 provider_name : "nvidia-prod" . to_string ( ) ,
10111057 credential_key : "NVIDIA_API_KEY" . to_string ( ) ,
10121058 value : "updated-secret" . to_string ( ) ,
1013- existing_handle : Some ( handle ( "v1:openshell/provider-credentials/existing" ) ) ,
1059+ existing_handle : Some ( handle ( & format ! ( "v1:{logical_path}" ) ) ) ,
10141060 } )
10151061 . await
10161062 . unwrap ( ) ;
10171063
1018- assert_eq ! ( stored. handle, "v1:openshell/provider-credentials/existing" ) ;
1064+ assert_eq ! ( stored. handle, format! ( "v1:{logical_path}" ) ) ;
10191065 }
10201066
10211067 #[ tokio:: test]
10221068 async fn delete_token_file_kv2_secret ( ) {
10231069 let mock_server = MockServer :: start ( ) . await ;
1070+ let logical_path = managed_secret_path ( "nvidia-prod" , "NVIDIA_API_KEY" ) ;
10241071 Mock :: given ( method ( "DELETE" ) )
1025- . and ( path (
1026- "/v1/secret/metadata/openshell/provider-credentials/existing" ,
1027- ) )
1072+ . and ( path ( format ! ( "/v1/secret/metadata/{logical_path}" ) ) )
10281073 . and ( header ( "x-vault-token" , "dev-token" ) )
10291074 . respond_with ( ResponseTemplate :: new ( 204 ) )
10301075 . mount ( & mock_server)
@@ -1044,7 +1089,7 @@ mod tests {
10441089 . delete_credential ( DeleteCredentialRequest {
10451090 provider_name : "nvidia-prod" . to_string ( ) ,
10461091 credential_key : "NVIDIA_API_KEY" . to_string ( ) ,
1047- handle : Some ( handle ( "v1:openshell/provider-credentials/existing" ) ) ,
1092+ handle : Some ( handle ( & format ! ( "v1:{logical_path}" ) ) ) ,
10481093 } )
10491094 . await
10501095 . unwrap ( ) ;
@@ -1105,10 +1150,9 @@ mod tests {
11051150 #[ tokio:: test]
11061151 async fn resolve_maps_missing_key ( ) {
11071152 let mock_server = MockServer :: start ( ) . await ;
1153+ let logical_path = managed_secret_path ( "nvidia-prod" , "NVIDIA_API_KEY" ) ;
11081154 Mock :: given ( method ( "GET" ) )
1109- . and ( path (
1110- "/v1/secret/data/openshell/provider-credentials/missing-key" ,
1111- ) )
1155+ . and ( path ( format ! ( "/v1/secret/data/{logical_path}" ) ) )
11121156 . respond_with ( ResponseTemplate :: new ( 200 ) . set_body_json ( serde_json:: json!( {
11131157 "data" : {
11141158 "data" : { }
@@ -1132,7 +1176,7 @@ mod tests {
11321176 request_id: "credential-0" . to_string( ) ,
11331177 provider_name: "nvidia-prod" . to_string( ) ,
11341178 credential_key: "NVIDIA_API_KEY" . to_string( ) ,
1135- handle: Some ( handle( "v1:openshell/provider-credentials/missing-key" ) ) ,
1179+ handle: Some ( handle( & format! ( "v1:{logical_path}" ) ) ) ,
11361180 } ] )
11371181 . await
11381182 . unwrap_err ( ) ;
@@ -1144,10 +1188,9 @@ mod tests {
11441188 #[ tokio:: test]
11451189 async fn resolve_maps_permission_denied ( ) {
11461190 let mock_server = MockServer :: start ( ) . await ;
1191+ let logical_path = managed_secret_path ( "nvidia-prod" , "NVIDIA_API_KEY" ) ;
11471192 Mock :: given ( method ( "GET" ) )
1148- . and ( path (
1149- "/v1/secret/data/openshell/provider-credentials/denied" ,
1150- ) )
1193+ . and ( path ( format ! ( "/v1/secret/data/{logical_path}" ) ) )
11511194 . respond_with ( ResponseTemplate :: new ( 403 ) )
11521195 . mount ( & mock_server)
11531196 . await ;
@@ -1167,7 +1210,7 @@ mod tests {
11671210 request_id: "credential-0" . to_string( ) ,
11681211 provider_name: "nvidia-prod" . to_string( ) ,
11691212 credential_key: "NVIDIA_API_KEY" . to_string( ) ,
1170- handle: Some ( handle( "v1:openshell/provider-credentials/denied" ) ) ,
1213+ handle: Some ( handle( & format! ( "v1:{logical_path}" ) ) ) ,
11711214 } ] )
11721215 . await
11731216 . unwrap_err ( ) ;
0 commit comments