From ac1b20d910ab109a8593b2f4104007f47df439a8 Mon Sep 17 00:00:00 2001 From: "Dustin C. Hatch" Date: Mon, 5 Sep 2022 09:45:44 -0500 Subject: [PATCH] sns: Save messages to disk Upon receipt of a notification or unsubscribe confirmation message from SNS, after the message signature has been verified, the receiver will now write the re-serialized contents of the message out to the filesystem. This will allow the messages to be inspected later in order to develop additional functionality for this service. The messages are saved in a `messages` director within the current working directory. This directory contains a subdirectory for each SNS topic. Within the topic subdirectories, the each message is saved in a file named with the message timestamp and ID. --- .gitignore | 1 + src/model/sns.rs | 8 ++++---- src/sns/mod.rs | 53 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 58 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index ea8c4bf..de231e1 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ +/messages /target diff --git a/src/model/sns.rs b/src/model/sns.rs index 688cd25..d0f9bf0 100644 --- a/src/model/sns.rs +++ b/src/model/sns.rs @@ -4,7 +4,7 @@ //! the AWS Documentation. //! //! [0]: https://docs.aws.amazon.com/sns/latest/dg/sns-message-and-json-formats.html -use serde::Deserialize; +use serde::{Deserialize, Serialize}; /// Union for all known SNS message types /// @@ -24,7 +24,7 @@ pub enum Message { /// See also: [HTTP/HTTPS subscription confirmation JSON format][0] /// /// [0]: https://docs.aws.amazon.com/sns/latest/dg/sns-message-and-json-formats.html#http-subscription-confirmation-json -#[derive(Deserialize)] +#[derive(Deserialize, Serialize)] #[serde(rename_all = "PascalCase")] pub struct SubscriptionConfirmationMessage { #[serde(rename = "MessageId")] @@ -46,7 +46,7 @@ pub struct SubscriptionConfirmationMessage { /// See also: [HTTP/HTTPS notification JSON format][0] /// /// [0]: https://docs.aws.amazon.com/sns/latest/dg/sns-message-and-json-formats.html#http-notification-json -#[derive(Deserialize)] +#[derive(Deserialize, Serialize)] #[serde(rename_all = "PascalCase")] pub struct NotificationMessage { #[serde(rename = "MessageId")] @@ -67,7 +67,7 @@ pub struct NotificationMessage { /// See also: [HTTP/HTTPS unsubscribe confirmation JSON format][0] /// /// [0]: https://docs.aws.amazon.com/sns/latest/dg/sns-message-and-json-formats.html#http-unsubscribe-confirmation-json -#[derive(Deserialize)] +#[derive(Deserialize, Serialize)] #[serde(rename_all = "PascalCase")] pub struct UnsubscribeConfirmationMessage { #[serde(rename = "MessageId")] diff --git a/src/sns/mod.rs b/src/sns/mod.rs index fd8f639..f341775 100644 --- a/src/sns/mod.rs +++ b/src/sns/mod.rs @@ -2,8 +2,11 @@ pub mod error; pub mod sig; +use std::path::PathBuf; + use log::{debug, error, info}; use reqwest::Url; +use serde::Serialize; use crate::model::sns::*; use error::SnsError; @@ -29,6 +32,7 @@ pub async fn handle_unsubscribe( msg: UnsubscribeConfirmationMessage, ) -> Result<(), SnsError> { verify(&msg, &msg.signing_cert_url).await?; + save_message(&msg.topic_arn, &msg.timestamp, &msg.message_id, &msg); Ok(()) } @@ -38,6 +42,7 @@ pub async fn handle_unsubscribe( /// a file for later inspection. pub async fn handle_notify(msg: NotificationMessage) -> Result<(), SnsError> { verify(&msg, &msg.signing_cert_url).await?; + save_message(&msg.topic_arn, &msg.timestamp, &msg.message_id, &msg); Ok(()) } @@ -141,3 +146,51 @@ async fn confirm_subscription(msg: &SubscriptionConfirmationMessage) { } } } + +fn save_message( + topic: &str, + timestamp: &str, + msgid: &str, + msg: &T, +) { + let mut path = PathBuf::from("messages"); + path.push(topic); + if !path.is_dir() { + if let Err(e) = std::fs::create_dir_all(&path) { + error!("Could not create message storage directory: {}", e); + return; + } + } + path.push(format!("{}.{}", timestamp, msgid)); + match serde_json::to_string(msg) { + Ok(data) => match std::fs::write(&path, &data) { + Ok(_) => info!( + "Wrote message {} to {}", + &msgid, + &path.to_string_lossy() + ), + Err(e) => error!("Could not save message: {}", e), + }, + Err(e) => error!("Failed to serialize message: {}", e), + }; +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_save_message() { + let msg = NotificationMessage { + message_id: "f0bc6e06-24d1-471d-a828-8411c9873996".into(), + topic_arn: "arn:aws:sns:us-east-2-566967686773:dchtest1".into(), + message: "Hello, world!".into(), + subject: None, + timestamp: "2022-09-05T214:29:34.232Z".into(), + signature_version: "0".into(), + signature: "".into(), + signing_cert_url: "".into(), + }; + save_message(&msg.topic_arn, &msg.timestamp, &msg.message_id, &msg); + } +}