@@ -78,6 +78,70 @@ async fn sqs_send_receive_encrypted_round_trips_through_kms() {
7878 ) ;
7979}
8080
81+ /// Regression: ReceiveMessage against an SSE-KMS queue whose key became
82+ /// unusable (disabled) must return an error WITHOUT destroying the batch.
83+ /// Previously the messages were popped out of the queue before the decrypt
84+ /// `?` unwound, permanently losing them. Real SQS leaves them visible.
85+ #[ tokio:: test]
86+ async fn sqs_receive_decrypt_failure_does_not_destroy_messages ( ) {
87+ let server = TestServer :: start ( ) . await ;
88+ let sqs = server. sqs_client ( ) . await ;
89+ let kms = server. kms_client ( ) . await ;
90+
91+ // Customer-managed key we can disable (managed alias/aws/sqs can't be).
92+ let key = kms. create_key ( ) . send ( ) . await . unwrap ( ) ;
93+ let key_id = key. key_metadata ( ) . unwrap ( ) . key_id ( ) . to_string ( ) ;
94+
95+ let queue = sqs
96+ . create_queue ( )
97+ . queue_name ( "decrypt-fail" )
98+ . attributes (
99+ aws_sdk_sqs:: types:: QueueAttributeName :: KmsMasterKeyId ,
100+ & key_id,
101+ )
102+ . send ( )
103+ . await
104+ . unwrap ( ) ;
105+ let queue_url = queue. queue_url ( ) . unwrap ( ) . to_string ( ) ;
106+
107+ sqs. send_message ( )
108+ . queue_url ( & queue_url)
109+ . message_body ( "do-not-lose-me" )
110+ . send ( )
111+ . await
112+ . unwrap ( ) ;
113+
114+ // Disable the key: the next receive must fail to decrypt.
115+ kms. disable_key ( ) . key_id ( & key_id) . send ( ) . await . unwrap ( ) ;
116+ let failed = sqs
117+ . receive_message ( )
118+ . queue_url ( & queue_url)
119+ . max_number_of_messages ( 1 )
120+ . send ( )
121+ . await ;
122+ assert ! (
123+ failed. is_err( ) ,
124+ "ReceiveMessage must error when the KMS key is disabled, got: {failed:?}"
125+ ) ;
126+
127+ // Re-enable the key: the message must still be there (not destroyed).
128+ kms. enable_key ( ) . key_id ( & key_id) . send ( ) . await . unwrap ( ) ;
129+ let recovered = sqs
130+ . receive_message ( )
131+ . queue_url ( & queue_url)
132+ . max_number_of_messages ( 1 )
133+ . send ( )
134+ . await
135+ . unwrap ( ) ;
136+ let msgs = recovered. messages ( ) ;
137+ assert_eq ! (
138+ msgs. len( ) ,
139+ 1 ,
140+ "message must survive a decrypt failure and be receivable after the key is re-enabled"
141+ ) ;
142+ assert_eq ! ( msgs[ 0 ] . body( ) , Some ( "do-not-lose-me" ) ) ;
143+ }
144+
81145#[ tokio:: test]
82146async fn sqs_unencrypted_queue_does_not_record_kms_usage ( ) {
83147 use aws_sdk_sqs:: types:: QueueAttributeName ;
0 commit comments