新しいHTTPステータスコード「4xx Preliminary Request Denied」の提案仕様

新しく400番台のステータスコードを定義する『The Preliminary Request Denied HTTP Status Code』という提案がCloudflareのMark Nottingham氏から提出されています。

4xx Preliminary Request Denied

このステータスコードは、サーバーがprefetchやpreloadのリクエストを拒否していることを示す HTTP ステータス コードです。具体的なステータスコードはまだ未定です。

なお、Preliminary requestとは Sec-Purposeヘッダに"prefetch"が指定されたリクエストのことです。

背景

ブラウザによって将来必要なリソースを投機的にフェッチする prefetch が使われるようになっています。

状況によっては次のようなシチュエーションがあると述べられています

  • prefetchすることがパフォーマンスに悪影響があるとサーバ側が知っている
  • 現時点では、Preliminary requestに適切な応答が出来ないため、そのリソースが必要になった際に改めてリクエストしてほしい

こういったときに今までは専用のステータスコードがなかったため、サーバとしてそのようなことを表現することは出来ませんでした。

2022年頃から専用のステータスコードを用意スべきか?という議論は有りましたが、今年になって改めてステータスコードを定義しようという動きがあったようです。

Draftの著者でもあるmnot氏は 503ステータスコードが使われることで、実際に混乱が起こっている旨のコメントをしており。それが今回の提案の動機となっています

これはお客様とのやり取りに基づくもので、上記の通りお客様にご迷惑をおかけしております。Cloudflareでは、この状況をより適切にお知らせし、混乱を避けるため、積極的に改善策を模索しております。

github.com

webでpreloadしたが使わなかったリソースを確認できる Speculative load measurement API

W3CのWeb Performance WGで『Speculative load measurement API』という仕様が議論されています。

github.com

Preload

Preloadという仕組みでは、将来必要になるリソースを投機的に取得するようにブラウザに指示できます。

具体的には、HTTPレスポンスヘッダや HTMLファイル内で

HTTPレスポンスヘッダ

Link: <main.js>;rel="preload";as="script"

HTML

<link rel="preload" href="main.js" as="script" />

Speculative load measurement API

Preloadなどで投機的に取得したリソースが使用されず、不必要にロードされていた可能性もあります。しかし、それを知る方法がありません。そこで、その情報を知るために提案されているのがSpeculative load measurement API』です

取れる情報

pagehideイベントに下記の情報を取得できるようになります。

{
  preloads: [
    { url: '...', as: '...', crossorigin: '...', earlyhint: true, used: true },
    { url: '...', as: '...', crossorigin: '...', earlyhint: true, used: false },
    // ...
  ],
  navigations: [
    { type: 'prefetch', url: '...', tags: '...', eagerness: '...', used: false },
    { type: 'prerender', url: '...', tags: '...', eagerness: '...', used: true },
    // ...
  ]
}

ここで usedの項目 を確認することで、利用されたか確認できます。

利用例

提案仕様に利用例が書かれています。
pagehideイベントの中で取れる情報をPOSTでサーバ側に渡す例です

window.addEventListener('pagehide', (event) => {
  const { preloads, navigations } = event.speculations;

  // Send unused speculation data to analytics endpoint
  fetch('/analytics/unused-speculations', {
    method: 'POST',
    keepalive: true,
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      preloads, navigations
    })
  });
});

Let's EncryptでIPアドレス証明書を発行できるようになったので試した

Let's Encryptが6日間のShort-Lived証明書のサポートを発表しました。それに合わせて、IPアドレス証明書の発行もサポートされました。
6-day and IP Address Certificates are Generally Available

http://1.1.1.1 が実際にIPアドレス証明書を使ってたり、一部の用途でIPアドレス証明書のユースケースもあるので試してみる。

cerbotで発行

certbotも --ip-adress オプションをサポートした (コミットはあるが未リリース) ので実際に発行してみた。

https://160.16.124.39/
(* 現在はIPアドレス証明書なしに変更してしまいました)

良さそう。

方法

まだ未リリースなので、開発者ガイドラインの通り githubリポジトリから最新コードを実行する。

下記のコマンドで発行する (ドメインは必須では有りません。IPアドレスのみで発行可能)。今回は、certonlyで実行

$ certbot certonly -d asnokaze.com   --ip-address 160.16.124.39 --preferred-profile shortlived


まだnginxプラグインなどが未対応と言われたので、authentication方式として、下記を選択

Runs an HTTP server locally which serves the necessary validation files
under the /.well-known/acme-challenge/ request path. Suitable if there is no
HTTP server already running. HTTP challenge only (wildcards not supported).

もちろん、ACMEのHTTP-01認証なのでプライベートIPアドレスの証明書は発行できない。

『CSV++』フォーマットの提案仕様について

コンマ区切りのデータフォーマットであるCSV (Comma-Separated Values)に、階層構造を追加する『CSV++』という拡張仕様がIETFで提案されています。

www.ietf.org

この仕様は、CSVに階層構造を定義します。既存のCSVパーサーでもパーサーエラーにならず、フラットになってはしまうがパースできるように設計されています。

医療情報フォーマットであるHL7 Version 2.xに影響を受けているそうです。

配列構造

キーに [ ] で区切り文字を指定することで、配列となります。

下記は、複数の電話番号・メールアドレスを持つ例です

id,name,phone[|],email[;]
1,John,555-1234|555-5678|555-9012,john@work.com;john@home.com
2,Jane,555-4444,jane@company.com
構造化フィールド

キーに 区切り文字と( )で構造化フィールドを指定できます。

下記は区切り文字^で、緯度経度の地理情報を持つ例です。

id,name,geo^(lat^lon)
1,Location A,34.0522^-118.2437
2,Location B,40.7128^-74.0060


構造化フィールドはネストすることも出来ます。
下記は、位置名と緯度経度を構造として持つ例です

id,location^(name^coords:(lat:lon))
1,Office^34.05:-118.24
2,Home^40.71:-74.00

都度DNS変更不要な、新しいACMEのDNS-PERSIST-01検証

証明書の自動発行にACMEでは、ドメイン検証が幾つか定義されています。その一つにはDNS-01チャレンジは、証明書を発行するたびに指定されたDNSレコードを作成する必要があります。

新しいDNS-PERSIST-01では一度設定すれば、それ以降証明書発行時にはDNSレコードを変更・追加する必要はありません。初回だけで済むので、ACME実行主体に以降はDNS操作の権限を与えなくてすみますね。

DNS-PERSIST-01は、Let's Encryptの記事では2026年に利用可能になると見通しの旨書かれている
letsencrypt.org

DNS-PERSIST-01

DNS-PERSIST-01の仕様は、『ACME Challenge for Persistent DNS TXT Record Validation』であり、現在WG Draftになっています。

Persistに扱うために、ACMEのアカウントとバインディングするのが特徴になっています。例えば、certbotではshow_accountコマンドでアカウント情報を確認できます。またACMEクライアントはアカウントと紐づいている秘密鍵を管理してしています。
(ここでいう秘密鍵は、サーバ証明書秘密鍵ではなく、ACME処理の文脈で登場するアカウントキーです)

$ sudo certbot show_account
Account details for server https://acme-v02.api.letsencrypt.org/directory:
  Account URL: https://acme-v02.api.letsencrypt.org/acme/acct/00000000
  Account Thumbprint: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
  Email contact: none

このアカウント情報を、DNSレコード検証時に入れ込みます。例としては以下の通りです。

```
_validation-persist.example.com. IN TXT ("authority.example;" " accounturi=https://ca.example/acct/123")
```

これにより、次回以降のDNSレコード検証が不要になります。

ただし、ACMEクライアントが管理している秘密鍵を秘匿する必要があります。

(複数サーバで共通のACMEアカウントを使うユースケースってありそうですが、秘密鍵を共有したりするんでしょうかね、、、)

新しいNTP DNSレコードの提案書を書いた

IETFに「NTP DNS Resource Record」というDraftを提出しました。
www.ietf.org

(まさか、このブログで自分の提案仕様をとりあげることになるとは)

NTP DNS Resource Record

このDraftでは、RFC9460のHTTPレコードと同様に、NTPレコードを定義します。

こんな感じのやつです。これはRFC9460で定義される SVCB-compatible RR typeです。

ntp.example.com. 300 IN NTP 1 . ntp-version=4,5

その背景にあるのが、NTPv5のバージョンネゴシエーションの課題です。

NTPの仕様では、サポートしてないNTPバージョンのパケットを受信してもドロップします。そのため、クライアントはNTPのやりとりを開始する前に使用するバージョンを決める必要があります。

しかし、クライアントは事前にサーバがサポートするNTPバージョンを知る方法は現状ありません。そこで、HTTPSレコードと同様に、DNSでNTPサポートバージョンをアドバタイズするという提案です。これにより、事前にサーバがサポートするNTPバージョンを知ることが出来ます。

(現状は、NTPv4のreference timestampフィールドに"NTP5NTP5"と入れることで、NTPv5サポートを通知できます。が、NTPv4サポートを暗黙的に仮定するというのが良くないと考えています。将来のNTPv6~ 以降のデプロイで課題は継続します。)

感想

もともとNTPv5における機能ネゴシエーションについて、メーリングリストで議論していました。電車の中でNTPv5の仕様について悩んでるところ、アイディアが降ってきたので、Internet Draftまで出してみようと書いてみました。

実際には、実装の振る舞いの問題、SVCBレコードで良いのでは?みたいな話とか色々出てきそうですが、一旦はアイディアを書き出してみた感じです。

HTTPのUnencoded-Digestヘッダの提案仕様

IETFで『HTTP Unencoded Digest』という提案仕様がWG Last callになっている。

HTTP Unencoded-Digestヘッダは、Content-Encodingでエンコードする前のデータのダイジェストを表すことが出来る。
例えば、gzip圧縮をかけている場合でも、エンコード前のデータに関して下記のように表せる。

Content-Encoding: gzip
Unencoded-Digest: 
    sha-256=:d435Qo+nKZ+gLcUHn7GQtQ72hiBVAgqoLsZnZPiTGPk=:,
    sha-512=:YMAam51Jz/jOATT6/zvHrLVgOYTGFy1d6GJiOHTohq4yP+pgk4vf2aCs
    yRZOtw8MjkM7iw7yZ/WkppmM44T3qg==:

(見易さのために改行している)

ユースケース

シンプルなユースケースとして以下の例が挙げられる

RFC 9421 HTTP Message Signatures』で署名をつけるときに、エンコード前のUnencoded-Digestヘッダに対して署名をすることで、CDNやプロキシでの再エンコーディング(broteliなど)が行えるようになる