結局セッションIDやトークンはLocalStorageとCookieのどちらで持てば良いのか

以下の通り、OWASPによるとLocal Storage にセッションIDを保存するのはやめときなさいと言っているので、私はそれに従おうと思う。

https://cheatsheetseries.owasp.org/cheatsheets/HTML5_Security_Cheat_Sheet.html#local-storage

Do not store session identifiers in local storage as the data is always accessible by JavaScript. Cookies can mitigate this risk using the httpOnly flag.

セッションID自体を盗めなくても、XSSによる攻撃は可能だから、Local Storageに保存しても結局変わらないのでは?という指摘も散見される。しかし私見を述べるならば、それがセッションID自体を盗む攻撃を防げなくてもいい理由にはならないと思っている。

適切に設定されたCookieの値をJavaScriptで取得することは出来ないが、Local Storageの値はJavaScriptで取得することができる。個人的には、適切に設定するという前提で、Cookieに保存するようにしたい。

MQTTには順序性があるがAWS IoT Ruleにはなさそう

以前の記事でMQTTでは概ね順序を重要視していることを述べた。

damedameo.hatenadiary.com

ここで気になるのはIoT関連サービスとしてよく利用するAWS IoTについてだが、残念ながらAWS IoT Ruleの実行順序保証はないようだ。以下はSQSの記述だが、「ルールエンジンは完全に分散されたサービスであるため、...」のくだりは一般的な話に読める。

https://docs.aws.amazon.com/ja_jp/iot/latest/developerguide/sqs-rule-action.html

注記
SQS アクションは、Amazon SQS FIFO (First-In-First-Out) キューをサポートしていません。ルールエンジンは完全に分散されたサービスであるため、SQS アクションがトリガーされたときのメッセージ順序の保証はありません。

なむなむ。

MQTTのv5.0では送信順序通りにPUBLISHが処理されるがv3.1.1では確実ではない

表題のとおりである。これは、過去記事で述べたように、v5.0では再接続時以外でのPUBLISH (PUBREC, PUBREL) の再送が許されないのに対し、v3.1.1では明示的に禁止されていないからである。

damedameo.hatenadiary.com

まずTCPから考えると、TCPは送信順序(シーケンス番号)を受信側で保証する。言い換えると、TCPのパケットは受信側に異なる順序で到達することもあるが、受信側で届いたパケットを送信した順に正しく並べ替える。これにより、上位のアプリケーションは、TCPソケットから順番通りにデータを受け取ることができる。もっといえば、到達パケットを格納するバッファの最前列に歯抜けのパケットがある場合、そのパケットが到達するまでTCPソケットはアプリケーションにデータを渡さないことになる。

ここで改めてMQTTについて考えると、MQTTサーバ (MQTTブローカー) は、PUBLISHを、MQTTクライアントから送信された順番通りに、TCPソケットから読み取れるということになる。これはつまり、MQTTクライアントがPUBLISHのデータをTCPソケットに書き込んだ順番通り、MQTTサーバがデータを処理することができるということである。

また、MQTTサーバ側からMQTTクライアント (SUBSCRIBE元) へPUBLISHする際にも、MQTTクライアント (PUBLISH元) からの到達順にPUBLISHを配信することが必須とされているので、MQTTクライアント(A)から送信されたPUBLISHは、それをSUBSCRIBEするMQTTクライアント(B)においても同じ順番で処理されるはずである。以下、この点についてv3.1.1およびv5.0の仕様を抜粋する。

v3.1.1 https://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718105

When a Server processes a message that has been published to an Ordered Topic, it MUST follow the rules listed above when delivering messages to each of its subscribers. In addition it MUST send PUBLISH packets to consumers (for the same Topic and QoS) in the order that they were received from any given Client

v5.0 https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901240

When a Server processes a message that has been published to an Ordered Topic, it MUST send PUBLISH packets to consumers (for the same Topic and QoS) in the order that they were received from any given Client

一度TCPソケットに送信を依頼されたPUBLISHは、そのTCPコネクションにおいて同じシーケンス番号で取り扱われ続けるので、仮にロスが発生しても同じシーケンス番号で再送される。よって、仮に後発の新しいPUBLISHがロスなく送信されて先に対向へ到達したとしても、TCPは送信順序を保証するためにロスしたPUBLISHの到達を待つので、最終的には正しい順番でMQTTアプリケーションに渡ることになる。

ここでようやく表題の話ができる。v5.0では再接続時のみ再送を許すため、データの取り扱いがTCPコネクションにすべて委任され、対向側で送信順序どおりに処理されることが保証される。一方v3.1.1の場合、MQTTレベルでの再送が発生すると、同じPUBLISH (同じPacket Identifier) が異なるシーケンス番号で送信されてしまう。例えばQoS1だとすると、重複の可能性を踏まえて以下のような違いが生まれる可能性がある。

MQTTレベルで再送される場合にありえる処理順序

<- | PUB1 (ID: 1) | PUB2 (ID: 2) | PUB3 (ID: 3) | PUB1 (ID: 1) | <-

MQTTレベルで再送しない (TCPに任せる) 場合の処理順序

<- | PUB1 (ID: 1) | <- ... (PUBACKを受け取らないままコネクション切断)...   
<- | PUB1 (ID: 1) | PUB2 (ID: 2) | PUB3 (ID: 2) |  <-

※ 切断前にPUBACKさえ受け取れれば重複はしない理解でいる。

なお、コネクションが切断されて再接続した際の再送順序についても、以下の通り前回接続時に送信を試行した順とすることが義務付けられている。これにより、仮に途中でコネクションが切断されたとしても、当初の送信順序どおりに対向でデータが処理されることとなる。

V3.1.1 https://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718105

When it re-sends any PUBLISH packets, it MUST re-send them in the order in which the original PUBLISH packets were sent (this applies to QoS 1 and QoS 2 messages) [MQTT-4.6.0-1]

V5.0 https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901240

When the Client re-sends any PUBLISH packets, it MUST re-send them in the order in which the original PUBLISH packets were sent (this applies to QoS 1 and QoS 2 messages) [MQTT-4.6.0-1]

ここまで話してきて、MQTTでは順序の取り扱いが思った以上に厳格であるということがわかる。コネクションの前提についても以下の通り言及がある。

https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Network_Connections

A Client or Server MUST support the use of one or more underlying transport protocols that provide an ordered, lossless, stream of bytes from the Client to Server and Server to Client [MQTT-4.2-1].

MQTTのv3.1.1とv5.0のPUBLISH再送信は再接続時が基本

MQTTのv3.1.1とv5.0において、PUBLISH (PUBREC, PUBREL) の再送は、再接続に実施されることが基本となっている。特にv5.0では、v3.1.1と異なり、再接続時以外での再送を明示的に禁止している。

まずv3.1.1から引用する
https://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718103

4.4 Message delivery retry
When a Client reconnects with CleanSession set to 0, both the Client and Server MUST re-send any unacknowledged PUBLISH Packets (where QoS > 0) and PUBREL Packets using their original Packet Identifiers [MQTT-4.4.0-1]. This is the only circumstance where a Client or Server is REQUIRED to redeliver messages.

Non normative comment
Historically retransmission of Control Packets was required to overcome data loss on some older TCP networks. This might remain a concern where MQTT 3.1.1 implementations are to be deployed in such environments.

つぎにv5から引用する
https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901238

4.4 Message delivery retry
When a Client reconnects with Clean Start set to 0 and a session is present, both the Client and Server MUST resend any unacknowledged PUBLISH packets (where QoS > 0) and PUBREL packets using their original Packet Identifiers. This is the only circumstance where a Client or Server is REQUIRED to resend messages. Clients and Servers MUST NOT resend messages at any other time [MQTT-4.4.0-1].

If PUBACK or PUBREC is received containing a Reason Code of 0x80 or greater the corresponding PUBLISH packet is treated as acknowledged, and MUST NOT be retransmitted [MQTT-4.4.0-2].

いずれも "When a Client reconnects with ... , both the Client and Server MUST re-send any unacknowledged PUBLISH Packets (where QoS > 0) and PUBREL Packets using their original Packet Identifiers" とある。つまり、再接続時にはACKを受け取っていないPUBLISHやPUBRELを再送しなくてはならない。

次にv3.1.1の引用にある "Non normative comment" に着目すると、古いTCP/IP実装ではControl Packetの再送が必要かもしれないと言っている。つまり、v3.1.1では、「再送信は再接続時に必須」という基本はありつつ、再接続を待たずにControl Packetを再送してもよい、と読める。

次にv5の引用を見ると "This is the only circumstance where a Client or Server is REQUIRED to resend messages. Clients and Servers MUST NOT resend messages at any other time" とある。つまり、「再送信は再接続時に必須」かつ「再接続時以外には再送禁止」と言っている。

実際のところ、v3.1.1実装の機器を使っていると、再接続を待たずに再送している場合があるように思う。