aws / aws/aws-lambda-rust-runtime

SNS `Timestamp` doesn't round-trip: serialization drops `.000` subseconds, corrupting the signed string-to-sign

オープン
#1,161 コメント 0 件 リアクション 0 件 担当者 0 名 GitHub で見る
主要言語
Rust
スター
3.6k
フォーク
396
PR マージ指標
30日以内にマージされた PR はありません

説明

The SNS event structs (`SnsMessage`, `SnsSubscriptionMessage`, `SnsMessageObj`) parse `Timestamp` into `chrono::DateTime`, and re-serializing does not reproduce the string SNS sent: chrono's default serializer uses `SecondsFormat::AutoSi`, which omits the subsecond part when it is zero.

```rust
let ts = "2019-01-02T12:45:07.000Z"; // Timestamp from the SNS sample event in the Lambda docs
let parsed: chrono::DateTime = serde_json::from_value(serde_json::json!(ts))?;
assert_eq!(serde_json::to_value(parsed)?, "2019-01-02T12:45:07Z"); // ".000" is gone
```

SNS itself always emits exactly three fractional digits, including `.000` on whole seconds. The [sample event in the Lambda developer guide](https://docs.aws.amazon.com/lambda/latest/dg/with-sns.html) shows `"Timestamp": "2019-01-02T12:45:07.000Z"`, and I confirmed the fixed three-digit format against a captured production payload whose signature verifies against the corresponding `SimpleNotificationService-*.pem` signing certificate (chained to Amazon Root CA 1).

## Impact

The SNS message signature covers the `Timestamp` string verbatim — the `signature` field docs on these structs say so. Anyone verifying SNS signatures on Lambda-delivered events who rebuilds the string-to-sign from these structs computes a canonical string that differs from what SNS signed whenever the message was published on a whole second. Roughly 1 in 1000 valid messages fails verification and gets dropped — silent, rare, and invisible in testing (any timestamp with nonzero milliseconds round-trips fine). I confirmed end to end with signed envelopes: a `.000Z` message verifies as delivered and fails after a round trip through `SnsMessage`; a `.719Z` message passes both ways.

Secondary effect: test events generated by serializing these structs (e.g. via the `builders` feature) carry timestamps in a format real SNS never produces.

## Proposed fix (non-breaking)

Pin serialization to SNS's actual format, leaving deserialization and the field type unchanged:

```rust
// custom_serde
pub fn serialize_rfc3339_millis(
dt: &DateTime,
s: S,
) -> Result {
s.serialize_str(&dt.to_rfc3339_opts(SecondsFormat::Millis, true))
}
```

```rust
#[serde(serialize_with = "crate::custom_serde::serialize_rfc3339_millis")]
pub timestamp: DateTime,
```

With this, every timestamp SNS actually produces round-trips byte-identically, so the reconstructed string-to-sign matches and generated fixtures match real payloads. I'm happy to send this PR.

## Alternative (breaking)

Preserve the raw string — `pub timestamp: String`, or a wrapper holding both the raw string and the parsed `DateTime`, serializing the raw string verbatim. This is the only variant that stays correct even if AWS ever changes its timestamp precision, but it breaks every consumer of the field (and diverges from `aws-lambda-go`, whose `SNSEntity` uses `time.Time` with the same limitation). Mentioning it for completeness; the non-breaking fix above covers the observed format.

Regardless of the fix, it may be worth a doc note on these fields that signature verification is best performed against the raw payload bytes — that is what AWS's official validators do, and it is immune to representation issues entirely.

コントリビューションガイド

コントリビューションガイドを開く

調査の方向性

まず SNS イベント構造体 SnsMessage、SnsSubscriptionMessage、SnsMessageObj を見つけ、それらのタイムスタンプのシリアライズと custom_serde モジュールを調べます。提示された .000Z と .719Z の例でラウンドトリップを確認します。完了の条件は、SNS のタイムスタンプが小数点以下3桁を正確に保持し、再構築された署名付き文字列が元の文字列と一致することです。

索引モデルが issue の本文から書いたものです。

評価

技術スタック
rust
領域
backend, cloud
issue の種類
バグ
難易度
3/5
見積もり時間
1〜2日
活発さ
静か
明瞭さ
おおむね明確
初心者へのやさしさ
68/100

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。