tech memo

調べたこと、試したことの覚書です

ウェブアクセラレータの2019年にリリ-スした新機能の振り返りと機能活用レシピ

これは さくらインターネット Advent Calendar 2019  の10日目の記事です🎄

 

さくらインターネットCDNサービス「ウェブアクセラレータ」の開発・運用をしている稲波です。

CDNサービスって何?」という部分の説明については、こちらの記事内では省略させていただきますので、そのあたりについてを確認されたい方は、上記サービスサイトや下記のさくナレ連載をご覧ください。

ウェブアクセラレータは、2016年10月に正式提供開始したCDNサービスです。*1今年で、3周年を迎えました!

ウェブアクセラレータは随時機能追加を行っており、今年は、6つの新機能をリリースしました!*2

本記事では、2019年にリリースした新機能を振り返り、その機能活用のレシピを書きました。

 

2019年にリリースした6つの新機能

各々の詳しい設定方法については、マニュアル をご覧ください。

1 . コントロールパネルでキャッシュ期間が設定できる機能

こちらは、先週リリースしたばかりの機能です。

今まで、ウェブアクセラレータのキャッシュ期間設定は、オリジンサーバ(お客様のWebサーバ)側でキャッシュ制御用のレスポンスヘッダを付与する設定をしていただく必要がありました。

が、この機能が追加されたことで、コントロールパネルより一括でキャッシュ期間を設定できるようになりました。*3

 

2. リクエスプロトコルの選択機能

リクエスプロトコルについて、今までの「http・httpsの両方を許可する」のほかに、「httpsのみ許可する」「httpsにリダイレクト」が追加になり、選択可能になりました。

オリジンサーバ側で設定することなく、お手軽にリダイレクトの設定などができるようになりました。

 

3. オリジンガード機能

ウェブアクセラレータを経由しないオリジンサーバへの直接アクセスをガードできる機能です。ウェブアクセラレータで発行するヘッダ(X-WebAccel-Guard)のトークン値に一致する場合のみアクセス許可する設定を、オリジンサーバに設定することで、悪意のあるアクセスからオリジンサーバを守ることができます。

オリジンガードトークンの更新準備の機能も先日追加されましたので、トークン値を定期的に変更する際にも、解除することなく切り替え可能です。

 

4. ワンタイムURL機能

指定した有効期限内にのみコンテンツを公開するための機能です。

オリジンサーバにて、レスポンスヘッダ(X-WebAccel-Secret)を使ってシークレットキーと、URLのクエリパラメータをつかって有効期限を設定することで、ウェブアクセラレータ経由で配信した際に期間限定配信(有効期限内にのみアクセス可能)ができます。

※ こちらは、オリジンガードトークン機能と併せてご利用ください(オリジンサーバに直接アクセスできるとシークレットキーが確認できてしまうため、ウェブアクセラレータからの接続のみ許可する必要があります)。

 

5. 操作ログが記録されるようになった

さくらのクラウドのイベントログ機能で、ウェブアクセラレータの操作ログも記録されるようになりました。ウェブアクセラレータの操作について、過去の履歴を確認できます。

 

6. Rangeリクエストをキャッシュできるようになった

Rangeヘッダ付きのリクエストがあったときに、キャッシュ対象の場合は、バックグラウンドでコンテンツ全体を取得しキャッシュできるようになりました。

キャッシュ済みのコンテンツについて、Rangeの指定範囲が異なるリクエストがあった場合にも、キャッシュ済みのコンテンツより配信ができるので、オリジンサーバへのリクエスト負荷が削減できます。

 

機能活用のレシピを考える

機能単体の設定方法については、マニュアル に記載していますが、参考として、機能を組み合わせてつかったりなどで、どう活用することができるのかについて書いてみました。紹介するアプローチ方法は、ごく一部ですので、ウェブアクセラレータ自体は自由に使っていただけます。

※ あくまでこういう活用方法もできますよという紹介で、推奨しているわけではないので、ご承知ください。

 

1. コントロールパネルでキャッシュ期間設定 × オリジンレスポンスにキャッシュ無効の設定 をしてみる

オリジンサーバ(お客様のWebサーバ)側でキャッシュ制御用のレスポンスヘッダを付与する場合の、配信フローは下記になります。キャッシュ対象にCache-Control: s-maxageを設定します。

f:id:nozomi1773:20191205001203p:plain

オリジンのレスポンスヘッダでキャッシュ制御の例

オリジンのレスポンスヘッダにCache-Control: no-store などのキャッシュ無効の設定をしておくことで、その無効ヘッダが設定されたコンテンツは、コントロールパネルでのキャッシュ期間設定のキャッシュ対象外になります。キャッシュさせたくないパスや拡張子に無効設定しておくことで、その箇所はパススルー(ウェブアクセラレータをプロキシとして使う)させ、他は、キャッシュさせるという使い方も可能です。

※ この場合、キャッシュヒット率としては、あまり高くはならない可能性があるのでご注意ください。

f:id:nozomi1773:20191205001312p:plain

コントロールパネルでキャッシュ制御例

という風にキャッシュさせない設定をすることで、それ以外の機能適用条件に合うすべての対象が、キャッシュ期間設定で一括で適用されているといった活用方法もできるのではないかと思います。

キャッシュさせる設定を付与するか、キャッシュさせない設定を付与するか、運用するサイトの都合上便利な方で対応いただけると思います(合わせ技も可能です)。

 

補足 : 特定のヘッダがある場合にno-storeを付与する nginx の設定については、過去に、

という記事を書きました。

こちらの記事自体は、認証ヘッダについて書きましたが、コントロールパネルからキャッシュ設定した場合は機能仕様としてキャッシュ対象となりません。

違うヘッダ、例えば Set-Cookie で似たようなことがしたい場合などがありましたら、参考にしてみてください。

 

2. ワンタイムURL × Rangeリクエストキャッシュ

ワンタイムURLでの配信においても、Rangeリクエストキャッシュが可能です。1コンテンツあたり1GiB以内であればキャッシュ可能なため、サイズが大きめのコンテンツでリクエスト範囲(再生開始位置など)が違う場合でも、ワンタイムURLで限定公開コンテンツのキャッシュ配信をお手軽に実現できます。

f:id:nozomi1773:20191205001424p:plain

ワンタイムURLでもRangeリクエストキャッシュ可

 オリジンへの負荷が軽減できると思います。

※図では省略しましたが、上述の通りオリジンガードとの併用が必要です。

 

以上、新機能の振り返りと、機能活用のレシピでした。

最後まで読んでくださってありがとうございました!

*1:ベータ版は2016年7月28日から提供していました。

*2:勿論、新機能以外にも諸々の改善が実施されていたりします。

*3:適用条件を理解していただく必要があるため、実際に使う場合には、マニュアルを読んでいただきたいです!

nginx で TLS 1.3 の Cipher Suites を設定するメモ

 

この記事の概要

nginx 1.17.5 で確認した話を書いています。今後の進展がある可能性があります。

 

ssl_ciphers では TLS 1.3 の ciphers を設定できない経緯

nginx の ssl_ciphers では TLS 1.3 の ciphers を設定できません。現時点では、TLS 1.3 の ciphers を設定する方法がnginx標準機能としてはありません。

経緯としては下記を見かけました。

#1529 の ざっくりとした概要としては、

  • OpenSSL 1.1.1 pre4以降でTLS 1.3-Only Cipher を設定するための新しいAPI(https://www.openssl.org/docs/manmaster/man3/SSL_CTX_set_ciphersuites.html )が追加されている。このAPIを利用して TLS 1.3-Only Cipher を設定する計画はあるかどうか。(例 : apache の 新オプションのSSLCipherSuiteV1_3)
  • このあたりまだ議論の余地があるところのようで、OpenSSLとしてのアプローチが明確になるまでは、nginx としてはサポートしないようです。
  • nginx を介さずに、TLS 1.3 の ciphers を設定したい場合は、OpenSSLの設定変更をして対応するというアプローチがある。

 

nginx + openssl で TLS 1.3 の Ciphers を設定してみる

nginx 1.17.5 ( built with OpenSSL 1.1.1 ) で、OpenSSLが動的リンクのnginxにて確認しました。

ciphers や ciphers order を確認するツールとしては、ssltest.sh が TLS 1.3 に対応していたため、こちらを利用しました。

1 . ssl_protocols に TLS 1.3 を追加する ( デフォルトの cipher 利用 )
ssl_prefer_server_ciphers on;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-RSA-AES128-SHA; #挙動確認のためTLSv1.2に使うcipherを1つ設定しています

ツールを使って nginx 接続の cipher order を取得すると下記になります。

Cipher order
TLSv1.2: ECDHE-RSA-AES128-SHA
TLSv1.3: TLS_AES_256_GCM_SHA384 TLS_CHACHA20_POLY1305_SHA256 TLS_AES_128_GCM_SHA256

 TLSv1.3 の デフォルトの ciphers order は、

  1. TLS_AES_256_GCM_SHA384
  2. TLS_CHACHA20_POLY1305_SHA256
  3. TLS_AES_128_GCM_SHA256

の順番でした。(

https://github.com/openssl/openssl/blob/OpenSSL_1_1_1/include/openssl/ssl.h#L174-L182

)

 

2. openssl.cnf にて Ciphersuites を定義する

Ubuntu 18.04 の場合は /etc/ssl/openssl.cnf でした。

[ new_oids ] の直前に下記を追加し、nginx を restart し、反映します。

openssl_conf = default_conf

[default_conf]
ssl_conf = ssl_sect

[ssl_sect]
system_default = system_default_sect

[system_default_sect]
Ciphersuites = TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256

 ツールを使って nginx 接続の cipher order を取得すると下記になります。

Cipher order
TLSv1.2: ECDHE-RSA-AES128-SHA
TLSv1.3: TLS_AES_128_GCM_SHA256 TLS_AES_256_GCM_SHA384 TLS_CHACHA20_POLY1305_SHA256

 TLSv1.3 の ciphers order を、

  1. TLS_AES_128_GCM_SHA256
  2. TLS_AES_256_GCM_SHA384
  3. TLS_CHACHA20_POLY1305_SHA256

の順番に変更することができました。

 

蛇足
ssl_prefer_server_ciphers on;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:ECDHE-RSA-AES128-SHA:TLS_CHACHA20_POLY1305_SHA256;

のように ssl_ciphers に TLS 1.3 の cipher suites を記入しても、nginx のconfig test(-t)はクリアしますが、設定として有効とはなりません。(これにより、わたしははじめこの件に気づけていませんでした。)

 

bustedで、Luaのユニットテストを試してみる

この記事の概要

busted を使ったユニットテストを試したメモです。
まだ、使いこなせていないのですが、
簡易に実行する程度までには至ったため一旦書き留めておきます。

busted とは

Olivine-Labsによって公開されているLuaユニットテストツールです。
MITライセンスです。

ドキュメント:

ソース:

実行環境のつくりかた

ドキュメントには、

busted works with lua >= 5.1, moonscript, terra, and LuaJIT >= 2.0.0.

と記載されていました。

Lua、LuaJITの実行環境を整えたのちに、

Ubuntu18.04、CentOS7の場合は下記でセットアップできました。

(環境によっては、途中で足りないパッケージがあり、コンパイル失敗したので、適宜インストールしました。)

【Ubuntuの場合】
# apt-get install -y luarocks
# luarocks install busted
# luarocks install luacov

【CentOS7の場合】
# yum install -y luarocks
# luarocks install busted
# luarocks install luacov

簡易なユニットテストを用意する

 今回は、テスト対象の関数のあるファイルと、別にテストシナリオだけを書いたファイルを分けてみました。

-- テスト対象の関数のあるファイル
-- /etc/nginx/lua/process.lua


-- テスト対象の関数 その1
function is_match(a, b)
if a == b then
return true
end
return false
end

-- テスト対象の関数 その2
function sum(a, b)
return a + b
end

 

-- ユニットテスト
-- ~/unit_test/test.lua

-- 今回はdofileで関数を読み込みました
dofile("/etc/nginx/lua/process.lua")

-- describeでブロックをネストして、グループ毎に実行できるようにハッシュタグ(#)付けします
describe("all test", function()

-- #match とタグ付けし、このブロックのみの実行が -t match でできるようにします
describe("is_match test #match", function()
-- it内に1つのシナリオをかきます
it("is_match 1 & 1 => true", function()
-- is_true(判定) で判定の内容がtrueだとテスト成功
assert.is_true(is_match(1,1) == true)
end)

-- わざと失敗させてみます
it("is_match 1 & 2 => true(false)", function()
assert.is_true(is_match(1,2) == true)
end)
end)

-- #sum とタグ付けし、このブロックのみの実行が -t sum でできるようにします
describe("sum test #sum", function()
it("sum 1 & 2 => false", function()
assert.is_true(sum(1,2) == 3)
end)
it("sum 10 & 20 => false", function()
assert.is_true(sum(10,20) == 30)
end)
end)

end)

 

テストを実行してみる

sumタグを実行

# busted test.lua -t sum
●●
2 successes / 0 failures / 0 errors / 0 pending : 0.00159 seconds

⇒ 2シナリオとも sucess になりました。

 

 matchタグ実行

# busted test.lua -t match
●?
1 success / 1 failure / 0 errors / 0 pending : 0.001331 seconds


Failure → test.lua @ 19
all test is_match test #match is_match 1 & 2 => true(false)
test.lua:20: Expected objects to be the same.
Passed in:
(boolean) false
Expected:
(boolean) true

 ⇒ どのブロックのどのシナリオで失敗しているか確認できます。

 

全件実行

# busted test.lua
●?●●
3 successes / 1 failure / 0 errors / 0 pending : 0.001995 seconds

Failure → test.lua @ 19
all test is_match test #match is_match 1 & 2 => true(false)
test.lua:20: Expected objects to be the same.
Passed in:
(boolean) false
Expected:
(boolean) true

 ⇒ 同上。

 

簡単な使い方のメモでした。
判定方法やエラー出力など柔軟にできるようですので、
使いこなせるようになった際には、別途紹介したいと思います。

 

nginx + lua-nginx-module で Basic認証時だけ 特定のCache-Controlヘッダの値を付与する処理の実装メモ

 

この記事の概要

下記の2パターンにて Basic認証時だけ 特定のCache-Controlヘッダの値を付与する処理を試しました。

  • nginx の ngx_http_auth_basic_module で Basic認証を実装するパターン
  • nginx の プロキシ先のアプリケーションで Basic認証を実装するパターン ( = nginx で リクエストヘッダ: Authorization 、レスポンスヘッダ: WWW-Authenticate を含む要求応答をプロキシする )

 ユースケースとしては、前段(ブラウザ, CDN, キャッシュサーバ等) で、Basic認証対象のコンテンツだけはキャッシュさせたくないので、特定のCache-Controlの値を付与したいときを想定しています。

Cache-Controlの値によるキャッシュ制御の挙動は、各々(ブラウザ,CDN,キャッシュサーバ等)の仕様を確認してください。

今回は一例として、Basic認証時だけ no-store を付与するという内容で試しています。

 

実装その1. nginx の ngx_http_auth_basic_module で Basic認証を実装するパターン

このパターンでは nginx 自体で Basic認証対象のURLパス を指定しているため、その配下に ヘッダ付与を記述します。( auth_basic, auth_basic_user_file の記述方法については本題からそれるのでこの記事では省略しています。 )

 

デフォルトでは、ngx_http_headers_module の add_header では 一部のステータスコードにヘッダを付与することができない仕様です。

Adds the specified field to a response header provided that the response code equals 200, 201 (1.3.10), 204, 206, 301, 302, 303, 304, 307 (1.1.16, 1.0.13), or 308 (1.13.0). Parameter value can contain variables.

※ 抜粋:Module ngx_http_headers_module

なので、Basic認証のレスポンスの一部 :  401 Unauthorized のときには add_header ではヘッダ付与ができません。クライアントとサーバの中間(CDN,プロキシ) の仕様で 401 レスポンスを ネガティブキャッシュする可能性がある場合など、401 にも 明示的にヘッダ付与したいときには add_header の always パラメタを付与する必要があります。

1 - 1. add_header で 実装するパターン ( 401 は対象外 )

付与対象ステータスコードが、200, 204, 206, 301, 302, 303, 304(すべてのバージョン) または 201(1.3.10) または 307 (1.1.16, 1.0.13) または 308 (1.13.0) のみでよく、

401 レスポンスには付与しなくてよい場合に add_header で書くと下記のような記述ができます。

location /basic/ {
add_header Cache-Control "no-store";
auth_basic "test";
auth_basic_user_file .htpasswd;
}

 応答としては、下記になります。

【Authorization ヘッダ無しでリクエスト の 結果抜粋】
# curl -iv http://******.1773.work/basic/

(リクエストヘッダ)
GET /basic/ HTTP/1.1
User-Agent: curl/7.29.0
Host: ******.1773.work
Accept: */*

(レスポンスヘッダ)
HTTP/1.1 401 Unauthorized
Date: Sun, 18 Aug 2019 02:59:34 GMT
Content-Type: text/html
Content-Length: 195
Connection: keep-alive
WWW-Authenticate: Basic realm="test"

※ Cache-Control が付与されない

 

【Authorization ヘッダありでリクエスト の 結果抜粋】
# curl -iv http://******.1773.work/basic/ -u test:******

(リクエストヘッダ)
GET /basic/ HTTP/1.1
Authorization: Basic ********(hash値)
User-Agent: curl/7.29.0
Host: ******.1773.work
Accept: */*

(レスポンスヘッダ)
HTTP/1.1 200 OK
Date: Sun, 18 Aug 2019 02:59:34 GMT
Content-Type: text/html
Content-Length: 15
Last-Modified: Thu, 06 Dec 2018 08:16:29 GMT
Connection: keep-alive
ETag: "5c08dadd-f"
Cache-Control: no-store ⇒ Cache-Control が付与される
Accept-Ranges: bytes

 

1 - 2. add_header で 実装するパターン ( すべてのレスポンスコードが対象 )

If the always parameter is specified (1.7.5), the header field will be added regardless of the response code.

※ 抜粋:Module ngx_http_headers_module

401 レスポンスを含む、すべての ステータスコードに も付与したい場合には、add_header の always パラメタを付与する必要があります。( 2019/09/30 修正 : こちらは元々 lua-nginx-module で記述していましたが、alwaysパラメタで設定可能という内容を知り、修正しました。)

location /basic/ {
add_header Cache-Control "no-store" always;
auth_basic "test";
auth_basic_user_file .htpasswd;
}

1.7.5 未満のバージョンのnginxで always が使えないが、lua-nginx-module は利用可能な場合は下記でも同様に全てのステータスコードに付与可能になります。

location /basic/ {
auth_basic "test";
auth_basic_user_file .htpasswd;

header_filter_by_lua_block {
ngx.header["Cache-Control"] = 'no-store';
}
}

401 の 応答にも 付与ができるようになります。

【Authorization ヘッダ無しでリクエスト の 結果抜粋】
# curl -iv http://******.1773.work/basic/

(リクエストヘッダ)
GET /basic/ HTTP/1.1
User-Agent: curl/7.29.0
Host: ******.1773.work
Accept: */*

(レスポンスヘッダ)
HTTP/1.1 401 Unauthorized
Date: Sun, 18 Aug 2019 03:13:05 GMT
Content-Type: text/html
Content-Length: 195
Connection: keep-alive
WWW-Authenticate: Basic realm="test"
Cache-Control: no-store ⇒ Cache-Control が付与される

 

実装その2. nginx の プロキシ先のアプリケーションで Basic認証を実装するパターン

このパターンでは nginx では Basic認証対象のURLパスを管理していません。要求応答ヘッダ(リクエストヘッダ: Authorization 、レスポンスヘッダ: WWW-Authenticate)有無で判定して Cache-Control を付与する例です。

location / {
proxy_pass http://127.0.0.1:****/;

header_filter_by_lua_block {
if ngx.req.get_headers()["Authorization"] ~= nil or ngx.resp.get_headers()["WWW-Authenticate"] ~= nil then
ngx.header["Cache-Control"] = 'no-store'
end
}
}

 応答としては、下記になります。

【Authorization ヘッダ無しでリクエスト の 結果抜粋】
# curl -iv http://******.1773.work/basic/

(リクエストヘッダ)
GET /basic/ HTTP/1.1
User-Agent: curl/7.29.0
Host: ******.1773.work
Accept: */*

(レスポンスヘッダ)
HTTP/1.1 401 Unauthorized
Date: Sun, 18 Aug 2019 04:11:48 GMT
Content-Type: text/html
Content-Length: 180
Connection: keep-alive
WWW-Authenticate: Basic realm="test"
Cache-Control: no-store ⇒ Cache-Control が付与される

 

【Authorization ヘッダありでリクエスト の 結果抜粋】
# curl -iv http://******.1773.work/basic/ -u test:******

(リクエストヘッダ)
GET /basic/ HTTP/1.1
Authorization: Basic ********(hash値)
User-Agent: curl/7.29.0
Host: ******.1773.work
Accept: */*

(レスポンスヘッダ)
HTTP/1.1 200 OK
Date: Sun, 18 Aug 2019 04:11:41 GMT
Content-Type: text/html
Content-Length: 612
Connection: keep-alive
Last-Modified: Wed, 27 Mar 2019 02:38:45 GMT
ETag: "5c9ae235-264"
Accept-Ranges: bytes
Cache-Control: no-store ⇒ Cache-Control が付与される

 

【Basic認証以外のURLへのリクエスト の 結果抜粋】
# curl -iv http://******.1773.work/

(リクエストヘッダ)
GET / HTTP/1.1
User-Agent: curl/7.29.0
Host: ******.1773.work
Accept: */*

(レスポンスヘッダ)
HTTP/1.1 200 OK
Date: Sun, 18 Aug 2019 04:21:28 GMT
Content-Type: text/html
Content-Length: 612
Connection: keep-alive
Last-Modified: Wed, 27 Mar 2019 02:38:45 GMT
ETag: "5c9ae235-264"
Accept-Ranges: bytes

※ Cache-Control は付与されない

 

Basic認証時 の Cache-Controlヘッダ付与についてのメモでした。

Apache Traffic Server の gold_tests をはじめて書いたときに試行錯誤したメモ

 

はじめに

Apache Traffic Server ( GitHub - apache/trafficserver: Apache Traffic Server ) の tests/gold_tests 配下にある 各テストを参考に はじめて gold_tests を作成するのを試みた際に、悩んだ点・気づいた点などを、覚えているうちに書き留めておきます。

 

実行環境のつくりかた

今回、私は、gold_tests 実行環境については hnakamur さんの記事を参考に Dockerにて作成しました。

また、追加の手順として、自分のつくったテストだけ実行したいときには、gold_tests ディレクトリ配下を autest-site と 自分の作成したテストディレクトリのみ にして実行するのがお手軽だったので、そちらの方法でやってました。

【gold_tests配下の全件実行 のイメージ】
cd ~build/dev/trafficserver/tests
env-test/bin/autest -D gold_tests --ats-bin /usr/local/bin

【自分のgold_testsのみ実行 のイメージ】
mkdir ~build/tmp
mv ~build/dev/trafficserver/tests/gold_tests/* ~build/tmp/
mv ~build/tmp/autest-site ~build/dev/trafficserver/tests/gold_tests/
mv ~build/tmp/my-tests ~build/dev/trafficserver/tests/gold_tests/
cd ~build/dev/trafficserver/tests
env-test/bin/autest -D gold_tests --ats-bin /usr/local/bin
  

gold_tests での試行錯誤① Hierarchical Caching 構成にしたい

tests/gold_tests 配下を見回った限り Hierarchical Caching が無さそうでしたが、gold_tests 内で traffic server のプロセスを2つ作成して、各々の config を設定したところ 1コンテナ内に Child用プロセス と Parent用プロセス を動作させても問題なさそうでした。
私が試した方法では、なぜか DNS が timeout するタイミングがあるという問題があったので、コンテナ内の /etc/hosts にダミーの名前を追記しつつ、config に proxy.config.hostdb.host_file.path で呼び出すという方法で試していました (この方法だと動いたので...) 。
【gold_tests / master(9.0.0) で動かしたミニマムに記述の例】

## プロセスの作成
## ts = Child用 , ts2 = Parent用 として 2つ作成します。

ts = Test.MakeATSProcess("ts")
ts2 = Test.MakeATSProcess("ts2")

## Child の config
ts.Disk.plugin_config.AddLine('xdebug.so')
ts.Disk.remap_config.AddLine(
'map . http://origin.example.com'
)
ts.Disk.parent_config.AddLine(
'dest_domain=. parent="127.0.0.1:{port}"'.format(port=ts2.Variables.port)
)
ts.Disk.records_config.update({
'proxy.config.http.parent_proxy.self_detect' : 0,
'proxy.config.http.no_dns_just_forward_to_parent' : 1,
'proxy.config.http.request_via_str' : 'ApacheTrafficServerChild',
'proxy.config.http.response_via_str' : 'ApacheTrafficServerChild',
'proxy.config.log.logging_enabled' : 3,
'proxy.config.diags.debug.enabled': 1,
'proxy.config.diags.debug.tags': 'http|dns',
'proxy.config.diags.output.debug': 'L',
'proxy.config.hostdb.host_file.path' : '/etc/hosts',
})

## Parent の config
ts2.Disk.plugin_config.AddLine('xdebug.so')
ts2.Disk.remap_config.AddLine(
'map / http://127.0.0.1:{0}'.format(server.Variables.Port)
)
ts2.Disk.records_config.update({
'proxy.config.http.request_via_str' : 'ApacheTrafficServerParent',
'proxy.config.http.response_via_str' : 'ApacheTrafficServerParent',
'proxy.config.log.logging_enabled' : 3,
'proxy.config.diags.debug.enabled': 1,
'proxy.config.diags.debug.tags': 'http|dns',
'proxy.config.diags.output.debug': 'L',
'proxy.config.hostdb.host_file.path' : '/etc/hosts',
})
  
【DockerFileに追加】
USER root
RUN echo "127.0.0.1 origin.example.com" >> /etc/hosts
 

gold_tests での試行錯誤② 同一URLへのリクエストに対してのオリジンからのレスポンスを、違うものにしたい

例えば URI "/default" に対して 200 OK , 304 Not Modified の 2パターンを応答させるようにしたい、といったときのアプローチ方法です。

オリジンプロセスの作成 MakeOriginServer にて、lookup_key を指定しないときはデフォルトの lookup_key は PATH のみの挙動となっていました。

【lookup_key を指定しないとき】
server = Test.MakeOriginServer("server")

request_header1 = {"headers":
"GET /default HTTP/1.1\r\n" +
"Host: www.example.com\r\n" +
"\r\n",
"timestamp": "12345678",
"body" : "",
}
response_header6b = {"headers":
"HTTP/1.1 200 OK\r\n" +
"Content-Length: 4\r\n" +
"Connection: close\r\n" +
"Cache-Control: max-age=0\r\n" +
"ETag: \"5ca41161-1a\"\r\n" +
"\r\n",
"timestamp": "12345678",
"body": "test"
}
server.addResponse("sessionlog.json", request_header1, response_header1)

request_header2 = {"headers":
"GET /default HTTP/1.1\r\n" +
"Host: www.example.com\r\n" +
"\r\n",
"timestamp": "12345678",
"body" : "",
}
response_header2 = {"headers":
"HTTP/1.1 304 Not Modified\r\n" +
"Connection: close\r\n" +
"ETag: \"5ca41161-1a\"\r\n" +
"\r\n",
"timestamp": "12345678",
"body": None
}
server.addResponse("sessionlog.json", request_header2, response_header2)
 ⇒ この場合 "GET /default HTTP/1.1" でリクエストすると最後に定義した response_header2 が常にかえってくるという挙動になってしまいました...

 

などをみていて、HTTPヘッダを lookup_key に使えそうだったので、フック用のダミーのヘッダ X-Update を用意してみました。

【lookup_key をカスタマイズする】
server = Test.MakeOriginServer("server",lookup_key="{%X-Update}{PATH}")

request_header1 = {"headers":
"GET /default HTTP/1.1\r\n" +
"Host: www.example.com\r\n" +
"X-Update: no\r\n" +
"\r\n",
"timestamp": "12345678",
"body" : "",
}
response_header1 = {"headers":
"HTTP/1.1 200 OK\r\n" +
"Content-Length: 4\r\n" +
"Connection: close\r\n" +
"Cache-Control: max-age=0\r\n" +
"ETag: \"5ca41161-1a\"\r\n" +
"\r\n",
"timestamp": "12345678",
"body": "test"
}
server.addResponse("sessionlog.json", request_header1, response_header1)

request_header2 = {"headers":
"GET /default HTTP/1.1\r\n" +
"Host: www.example.com\r\n" +
"X-Update: yes\r\n" +
"\r\n",
"timestamp": "12345678",
"body" : "",
}
response_header2 = {"headers":
"HTTP/1.1 304 Not Modified\r\n" +
"Connection: close\r\n" +
"ETag: \"5ca41161-1a\"\r\n" +
"\r\n",
"timestamp": "12345678",
"body": None
}
server.addResponse("sessionlog.json", request_header2, response_header2)

  ⇒ この場合 "GET /default HTTP/1.1" + "X-Update: no" でリクエストするとresponse_header1 が, "GET /default HTTP/1.1" + "X-Update: yes" でリクエストするとresponse_header2かえってくるという挙動をさせることができました。

同一URLに対してオリジンからのレスポンスを変えたいときはこの手の仕込みが必要そうでした。

 

PowerDNS ALIASレコード / TTL の 挙動確認

はじめに

先日、DNS ANAMEレコード / PowerDNS ALIASレコードについて下記の記事を投稿しました。本記事内で一部は専門用語などで記述していますが、それらの専門用語の説明は下記の記事にて確認していただけます。

本記事においては PowerDNS ALIASレコード の TTLについての挙動確認を記録しています。

 

ANAMEレコード の TTLについての 仕様確認

Internet Draft にて「Address-specific DNS aliases (ANAME)」として仕様提案されている TTL周りの仕様を、現時点での最新である draft-03 にて確認しました。

【5.3. TTLs】にて記述されている仕様は下記の内容でした。

  • Sibling Address Record は、固定TTLを持つ権威サーバから提供される。
  • 通常 Sibling Address RecordTTLは、Target Address Record TTL (Target Address RecordのTTLが ANAMEのTTLより小さい場合は、ANAME の TTL)と同じであると予想される。
  • ただし 正確なメカニズムは指定されていないため、 Sibling Address RecordTTLが小さくなる場合がある。 

 

PowerDNS ALIASレコード の TTLについての動作確認

正確な挙動は、実際に試してみないと分からなさそうだったので確認しました。

何パターンか試してみた内容です。

① Sibling Address Record が Aレコード , ANAME's TTL > Sibling's TTL 

【ANAME @ DNS-1】
(owner) (ttl) (class) (TYPE) (target)
example.1773.work. 60 IN ALIAS origin.1773.work.

【Sibling Address @ DNS-2】
(owner) (ttl) (class) (TYPE) (target)
origin.1773.work. 10 IN A 192.168.254.100

 
【事前確認、DNS-2に直接確認】
origin.1773.work. 10 IN A 192.168.254.100

【ANAME確認、DNS-1に確認】
exsample.1773.work. 10 IN A 192.168.254.100

 ⇒ TTLは、Sibling Address(かつTarget Address) Record のTTLに置き換わった。

 

② Sibling Address Record が Aレコード , ANAME's TTL < Sibling's TTL 

【ANAME @ DNS-1】
(owner) (ttl) (class) (TYPE) (target)
example.1773.work. 60 IN ALIAS origin.1773.work.

【Sibling Address @ DNS-2】
(owner) (ttl) (class) (TYPE) (target)
origin.1773.work. 100 IN A 192.168.254.100

 
【事前確認、DNS-2に直接確認】
origin.1773.work. 100 IN A 192.168.254.100

【ANAME確認、DNS-1に確認】
exsample.1773.work. 100 IN A 192.168.254.100

 ⇒ TTLは、Sibling Address(かつTarget Address) Record のTTLに置き換わった。

 

③ Sibling Address Record が CNAMEレコード , ANAME's TTL > Sibling's TTL > Target's TTL 

【ANAME @ DNS-1】
(owner) (ttl) (class) (TYPE) (target)
example.1773.work. 60 IN ALIAS cname.1773.work.

【Sibling Address @ DNS-2】
(owner) (ttl) (class) (TYPE) (target)
cname.1773.work. 10 IN CNAME origin.1773.work.

【Target Address @ DNS-3】
(owner) (ttl) (class) (TYPE) (target)
origin.1773.work. 5 IN A 192.168.254.10
origin.1773.work. 5 IN A 192.168.254.20
origin.1773.work. 5 IN A 192.168.254.30
 
【事前確認、DNS-3に直接確認】
origin.1773.work. 5 IN A 192.168.254.10
origin.1773.work. 5 IN A 192.168.254.20
origin.1773.work. 5 IN A 192.168.254.30

【事前確認、DNS-2に直接確認】
cname.1773.work. 10 IN CNAME origin.1773.work.
origin.1773.work. 5 IN A 192.168.254.10
origin.1773.work. 5 IN A 192.168.254.20
origin.1773.work. 5 IN A 192.168.254.30

【ANAME確認、DNS-1に確認】
exsample.1773.work. 5 IN A 192.168.254.20
exsample.1773.work. 5 IN A 192.168.254.10
exsample.1773.work. 5 IN A 192.168.254.30
  ⇒ TTLは、Target Address Record のTTLに置き換わった。
 

④ Sibling Address Record が CNAMEレコード , ANAME's TTL > Sibling's TTL < Target's TTL

【ANAME @ DNS-1】
(owner) (ttl) (class) (TYPE) (target)
example.1773.work. 60 IN ALIAS cname.1773.work.

【Sibling Address @ DNS-2】
(owner) (ttl) (class) (TYPE) (target)
cname.1773.work. 10 IN CNAME origin.1773.work.

【Target Address @ DNS-3】
(owner) (ttl) (class) (TYPE) (target)
origin.1773.work. 90 IN A 192.168.254.10
origin.1773.work. 90 IN A 192.168.254.20
origin.1773.work. 90 IN A 192.168.254.30
 
【事前確認、DNS-3に直接確認】
origin.1773.work. 90 IN A 192.168.254.10
origin.1773.work. 90 IN A 192.168.254.20
origin.1773.work. 90 IN A 192.168.254.30

【事前確認、DNS-2に直接確認】
cname.1773.work. 10 IN CNAME origin.1773.work.
origin.1773.work. 90 IN A 192.168.254.10
origin.1773.work. 90 IN A 192.168.254.20
origin.1773.work. 90 IN A 192.168.254.30

【ANAME確認、DNS-1に確認】
exsample.1773.work. 90 IN A 192.168.254.30
exsample.1773.work. 90 IN A 192.168.254.20
exsample.1773.work. 90 IN A 192.168.254.10
  ⇒ TTLは、Target Address Record のTTLに置き換わった。

 

PowerDNS ALIASレコード TTL関連の feature request の動向

現時点では未実装ではありますが、機能要望としては、下記の feature request がありました。 

この request においては CDN RR の TTL が 1 などの短期間に設定されている場合においても、一定時間キャッシュさせたいなどのケースにおいて利用する、ALIASレコード側のTTLをつかってTTLを上書き(調整)するオプションが要望されていました。

 

PowerDNS ALIASレコード の TTL を確認してみた 紹介でした。 

DNS ANAMEレコード (PowerDNS の ALIASレコード) を試してみる

DNS ANAMEレコード / PowerDNS ALIASレコード 関連記事

 

 

はじめに

Address-specific DNS aliases (ANAME) 」というDNS関連の Internet Draft が、2017年よりIETFに提出されており、つい先日 draft-03 が提出されました 🎉 

  • draft-00 ( 2017-03-24 提出, 2017-11-25 Expire )
  • draft-01 ( 2018-01-11 提出, 2018-07-15 Expire )
  • draft-02 ( 2018-10-15 提出, 2019-04-22 Expire )
  • draft-03 ( 2019-04-15 提出, 2019-10-17 Expire )  ← NEW

普段DNS関連技術は触れていないのですが、諸用により ANAMEレコード関連の情報を確認したり、draft-03 を確認したりなどをしていたため、微々たる情報量ではありますが、得た情報をこちらに書き留めておきます。

PowerDNS では ANAMEレコードの一部の機能を「ALIASレコード」として実装しているため、この機能を試してみた内容と併せて紹介しておきます。

なお、この記事においては、ANAMEレコードについての情報の一部分の紹介になります。すべての情報は書いていませんので、障りとしての参考情報程度にしていただけたらと思います。

 

CNAMEレコードの制約、ANAME レコードがなぜ必要なのか?

CNAMEレコードの制約

DNSプロバイダの DNS Made Easy が作成されている ANAMEレコードの紹介動画 ( Introducing the ANAME Record - YouTube ) がとても分かりやすかったのでお勧めです。英語が問題ない方はこちらの動画を確認していただくと、4分程度でざっと雰囲気を把握できるかと思います。

DNSではCNAMEレコードを使ってドメイン名の別名を定義することができます。しかし、CNAMEレコードには仕様上の制約があり、下記のことができません。

① Zone Apex(別名: Root Domain / Naked Domain) では CNAMEレコードを 作成することができない

私が所有している"1773.work"というドメインの例

(NG) 1773.work ← これはZone ApexなのでCNAME作成できない
(OK) example.1773.work ← これはサブドメインなのでCNAME作成可能

② CNAMEレコードはユニークでなければならない (同一の名前を他のレコードと共有することができない)

<NG その1> CNAMEを複数書いたりはできない

 examle.1773.work.    IN    CNAME    sv1.1773.work.
 examle.1773.work.    IN    CNAME    sv2.1773.work.

<NG その2> MXなど他のリソースレコードタイプと重複したりもできない

 example.1773.work.   IN    MX    10    mail.1773.work.
 example.1773.work.   IN    CNAME    origin.1773.work.

また、一般的なCNAMEレコードの参照では、DNS lookup を2度実行する必要があるため、参照速度が遅くなります。1度目のlookupでCNAMEレコードを見つけ、2度目のlookupで参照先のIPアドレスを見つけます。

これらの制約を解決するANAMEレコード

ANAMEレコードでは、CNAMEレコードのようにドメイン名の別名を定義できますが、上記のCNAMEレコードの仕様上の制約が取り除かれています

 

ANAME レコード 表示形式 と 専門用語

draft-03 より確認した  ANAMEレコードの表示形式 および 専門用語を紹介します。

表示形式

ANAMEレコードの表示形式はCNAMEレコードと同様です。

(owner)    (ttl)    (class)    ANAME    (target)
専門用語

Address Record : リソースレコードタイプが A または AAAA の DNSリソースレコード(名前やTTLなどを含むRRset全体)

Address Type : リソースレコードタイプ が A または AAAA のもの

Address Query : 任意の Address Type に対する DNSクエリ

Sibling Address Record : ANAMEと同じowner名のAddress Recordで、ANAMEの置き換えの対象

Target Address Record : ANAMEの最尾の対象 を解決して取得した Address Record

 

ANAMEレコードと、PowerDNS の ALIAS レコード について

PowerDNS は OSS の DNSサーバです。

Internet Draft 内 の Appendix では PowerDNS での実装について下記のように述べられています。

Appendix A. Implementation status

PowerDNS currently implements a similar authoritative-only feature using "ALIAS" records, which are expanded by the primary server and transfered as address records to secondaries.
======
PowerDNSは、現在、"ALIAS"レコードを使用して同様の権威のみの機能を実装しています。
プライマリサーバによって拡張され、アドレスレコードとしてセカンダリに転送されます。

 ( Address-specific DNS aliases (ANAME)  - draft-03 より抜粋)

 

PowerDNS Authoritaive Server の ALIAS レコードを試してみる

PowerDNS Authoritative Server ( 以降、PowerDNS と略 ) の 導入およびセットアップについては、公式ドキュメント  を参考に実施しました。

 PowerDNS での ALIASレコードの制御には pdns.conf に下記 3つのパラメタを記述しました。

  • expand-alias=yes : ALIASレコード拡張を有効にする。(v4.1.0以降)
  • resolver=(任意のアドレス):53 : ALIAS(と内部スタブリゾルバ)のリゾルバアドレス。(v4.1.0以降) 今回は自分の個人運用しているDNSサーバに向けています。
  • outgoing-axfr-expand-alias=yes : AXFRでのALIASレコード拡張を有効にする。(この内容についてはまだ検証できていませんが、一旦投入しています)
DNSレコードの準備

PowerDNSではいくつかのバックエンドの選択があります。私は PostgreSQL を選択しましたので、そちらの形式でレコードを準備します。

今回問い合わせ対象の name を "example.1773.work" とします。

■ ALIAS(ANAME)レコードの準備 (DNS-1 にて準備)

pdns=# SELECT * FROM domains ; 
id | name | master | last_check | type | notified_serial | account
----+--------------------+--------+------------+--------+-----------------+---------
1 | example.1773.work | | | NATIVE | |
pdns=# SELECT id, domain_id, name, type, content, ttl FROM records ;
id | domain_id | name | type | content | ttl
----+-----------+-------------------+-------+------------------+------
1 | 1 | example.1773.work | SOA | (略) | 60
2 | 1 | example.1773.work | NS | (略) | 60
3 | 1 | example.1773.work | ALIAS | origin.1773.work | 60

 ALIAS(ANAME)に利用するnameはSOA,NSと共用しており、CNAMEでは制限のため利用できないパターンになります。

■ Sibling Address Record の準備 (DNS-2 にて準備)

pdns.conf にてresolverとして指定したDNSサーバにレコードを準備します。こちらはNSDなので、その形式で準備しました。

origin.1773.work.    IN    A    10.20.100.10
DNS lookupの実行

この状態で DNS-1 に対して lookup を実行します。結果は下記のようになりました。

 # dig example.1773.work
 
 ; <<>> DiG 9.11.3-1ubuntu1.5-Ubuntu <<>> example.1773.work
 ;; global options: +cmd
 ;; Got answer:
 ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 35817
 ;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
 
 ;; OPT PSEUDOSECTION:
 ; EDNS: version: 0, flags:; udp: 512
 ;; QUESTION SECTION:
 ;example.1773.work.                        IN      A
 
 ;; ANSWER SECTION:
 example.1773.work.         358     IN      A       10.20.100.10
 
 ;; Query time: 190 msec
 ;; SERVER: (問い合わせ先)#53(問い合わせ先)
 ;; WHEN: Thu Apr 26 01:32:03 JST 2019
 ;; MSG SIZE  rcvd: 59
 

 

ANSWER SECTION にて 解決された Target Address Record が参照できることを確認しました。

 

DNS ANAMEレコード (PowerDNS ALIASレコード) を試してみた 紹介でした。