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)はクリアしますが、設定として有効とはなりません。(これにより、わたしははじめこの件に気づけていませんでした。)

 

追記 ( 2020年7月7日 )

2020年7月7日に公開された IPAのTLS設定ガイドラインの参考資料「TLS暗号設定 暗号スイートの設定例」にて、現行のnginxバージョン(資料内では1.16.1と1.17.6)では、TLS1.3 の cipher suites が設定できない為、openssl.cnf で設定と記述されていることを確認しました。

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レコード) を試してみた 紹介でした。

TimescaleDB + Grafana で 時系列データを可視化する

TimescaleDB と Grafana

TimescaleDB とは?

  • PostgreSQL の EXTENSION の 時系列データベース
  • Apache License v2 で公開されているOSS

Grafana とは?

  • ログ・データなどの可視化ツール
  • Apache License v2 で公開されているOSS

TimescaleDB と Grafana の組み合わせ

Grafana v5.3 で TimescaleDB への対応が機能追加されました。

この機能追加により TimescaleDB に格納した時系列データを Grafana を使って可視化できるようになりました。

こちらの内容を触ってみたので、書き留めておきます。

 

TimescaleDB、Grafana の セットアップログ

TimescaleDB/Grafanaともに、公式ドキュメントに、ディストリビューション毎のセットアップ手順が記載されていつので、そちらを確認してください。

私は Ubuntu Server 18.04 LTS 上にオールインワンで構築してみました。そのログを置いておきます。

(インストールバージョン)

PostgreSQL 10.5
TimescaleDB 0.12.1
Grafana 5.3.0-beta

※ 2018年10月頃にセットアップしたので、現在の最新バージョンより古め(TimescaleDBもv0.xx)です。手順は基本的には一緒だと思うのですが、正しくは公式ドキュメントを参照してください。

 

① PostgreSQL の インスト―ル

# apt-get update

# apt-get install postgresql

# psql --version
psql (PostgreSQL) 10.5 (Ubuntu 10.5-0ubuntu0.18.04)

# systemctl status postgresql
● postgresql.service - PostgreSQL RDBMS
Loaded: loaded (/lib/systemd/system/postgresql.service; enabled; vendor preset: enabled)
Active: active (exited) since Fri 2018-09-28 17:25:06 JST; 27s ago
Main PID: 2725 (code=exited, status=0/SUCCESS)
Tasks: 0 (limit: 1118)
CGroup: /system.slice/postgresql.service

 

② TimescaleDB の インスト―ル

## add-apt-repository を使うための事前準備
# apt install software-properties-common

# add-apt-repository ppa:timescale/timescaledb-ppa
# apt-get update

## PG 10.2+はこのバージョンをインストールする
# apt install timescaledb-postgresql-10

 

③ postgresql.conf の設定変更

# cd /etc/postgresql/10/main/
# vi postgresql.conf

## shared_preload_libraries に timescaledb を追加する
==============================
<変更前>
#shared_preload_libraries = '' # (change requires restart)

<変更後>
shared_preload_libraries = 'timescaledb' # (change requires restart)
==============================

## synchronous_commit を無効にする
==============================
<変更前>
#synchronous_commit = on # synchronization level;

<変更後>
synchronous_commit = off # synchronization level;
==============================

## max_locks_per_transaction を とりあえず最小にする
==============================
<変更前>
#max_locks_per_transaction = 64 # min 10

<変更後>
max_locks_per_transaction = 10 # min 10
==============================

## 反映
# service postgresql restart

 

④ postgresユーザのパスワード設定しておく ユーザ/postgresqlの両方

## ユーザパスワードの設定
# passwd postgres

# su - postgres

## posgresqlユーザパスワードの設定
postgres$ psql

postgres=# ALTER ROLE postgres with password 'postgres';

postgres=# SELECT * FROM pg_shadow;
usename | usesysid | usecreatedb | usesuper | userepl | usebypassrls | passwd | valuntil | useconfig
----------+----------+-------------+----------+---------+--------------+-------------------------------------+----------+-----------
postgres | 10 | t | t | t | t | md53175bce1d3201d16594cebf9d7eb3f9d | |
(1 row)

postgres=# \q

試用なので、postgresユーザ&パスワードpostgresで接続しています。

 

⑤ TimescaleDB用 の データベース 作成

# psql -U postgres -h localhost
Password for user postgres:

postgres=# CREATE database tutorial;
CREATE DATABASE

postgres=# \c tutorial

# CREATE EXTENSION IF NOT EXISTS timescaledb CASCADE;
WARNING:
WELCOME TO
_____ _ _ ____________
|_ _(_) | | | _ \ ___ \
| | _ _ __ ___ ___ ___ ___ __ _| | ___| | | | |_/ /
| | | | _ ` _ \ / _ \/ __|/ __/ _` | |/ _ \ | | | ___ \
| | | | | | | | | __/\__ \ (_| (_| | | __/ |/ /| |_/ /
|_| |_|_| |_| |_|\___||___/\___\__,_|_|\___|___/ \____/
Running version 0.12.1
For more information on TimescaleDB, please visit the following links:
1. Getting started: https://docs.timescale.com/getting-started
2. API reference documentation: https://docs.timescale.com/api
3. How TimescaleDB is designed: https://docs.timescale.com/introduction/architecture
Note: TimescaleDB collects anonymous reports to better understand and assist our users.
For more information and how to disable, please see our docs https://docs.timescaledb.com/using-timescaledb/telemetry.
CREATE EXTENSION

## 作成したDBへの接続確認
postgres$ psql -d tutorial
psql (10.5 (Ubuntu 10.5-0ubuntu0.18.04))
SSL connection (protocol: TLSv1.2, cipher: ECDHE-RSA-AES256-GCM-SHA384, bits: 256, compression: off)
Type "help" for help.

tutorial=# \dt
Did not find any relations.

 

⑥ Grafana のインストール

# wget https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana_5.3.0-beta1_amd64.deb

# dpkg -i grafana_5.3.0-beta1_amd64.deb

# systemctl status grafana-server
● grafana-server.service - Grafana instance
Loaded: loaded (/usr/lib/systemd/system/grafana-server.service; disabled; ven
Active: inactive (dead)
Docs: http://docs.grafana.org

## HTTPポート番号の確認
# grep http_port /etc/grafana/grafana.ini
;http_port = 3000

# systemctl start grafana-server

 

⑦ Grafana のセットアップ

  • http://(対象サーバ):3000 にログイン 
  • http://(対象サーバ):3000/datasources よりデータソースを登録する

(設定例)

f:id:nozomi1773:20190414232500p:plain

(標準)

Name: 任意のデータソース名、Default: お好みで設定、Type: PostgreSQL

(PostgreSQL接続)

Host: サーバ+ポート、Database: 対象のDB名、User: 接続ユーザ、Password: 接続ユーザのパスワード、SSL Mode: 接続時のSSL利用

(PostgreSQL詳細)

Version: PostgreSQLのバージョン、TimescaleDB: チェックする、Min time interval: お好みで設定

 

TimescaleDB に デモ用のログを投入

Webサーバへのアクセスログを想定したデモ用のログを生成してみます。

(保有カラム)

  • time: 日時
  • http_host:  { first.com, second.com, third.com }
  • schema: { http, https }
  • http_status: { 1xx, 2xx, 3xx, 4xx, 5xx }

 

TimescaleDB用のテーブルの作成

データ投入するテーブルを作成します。

# psql -U postgres -d tutorial
Password for user postgres:
psql (10.5 (Ubuntu 10.5-0ubuntu0.18.04))
SSL connection (protocol: TLSv1.2, cipher: ECDHE-RSA-AES256-GCM-SHA384, bits: 256, compression: off)
Type "help" for help.

tutorial=# CREATE TABLE demo (
tutorial(# time TIMESTAMPTZ NOT NULL,
tutorial(# http_host TEXT NOT NULL,
tutorial(# schema TEXT NULL,
tutorial(# http_status TEXT NULL
tutorial(# );
CREATE TABLE

tutorial=# EXPLAIN SELECT * FROM demo ;
QUERY PLAN
---------------------------------------------------------
Seq Scan on demo (cost=0.00..16.10 rows=610 width=104)
(1 row)

HyperTableを作成します。(テーブルが空でないと作成できません)

tutorial=# SELECT create_hypertable('demo', 'time') ;
create_hypertable
--------------------
(31,public,demo,t)
(1 row)

tutorial=# EXPLAIN SELECT * FROM demo ;
QUERY PLAN
---------------------------------------------------------------
Append (cost=0.00..16.10 rows=610 width=104)
-> Seq Scan on demo (cost=0.00..16.10 rows=610 width=104)
(2 rows)

 

デモ用ログ投入実行

雑にランダムなデータを作成 して 投入しました。

 

テーブルに格納されたログを確認

tutorial=# SELECT * FROM demo ORDER BY time ;
time | http_host | schema | http_status
-------------------------------+------------+--------+-------------
2019-04-15 19:01:00.123457+09 | second.com | http | 3xx
2019-04-15 19:01:00.123457+09 | third.com | http | 4xx
2019-04-15 19:01:00.123457+09 | second.com | https | 4xx
2019-04-15 19:01:00.123457+09 | third.com | http | 2xx
2019-04-15 19:01:00.123457+09 | first.com | https | 2xx
2019-04-15 19:01:00.123457+09 | second.com | https | 3xx
2019-04-15 19:01:00.123457+09 | second.com | http | 2xx
(略)

 

Grafanaで可視化

ダッシュボード の Edit で 登録します。デフォルトのMetricではGUIベースで登録できます。

f:id:nozomi1773:20190416000337p:plain

SQLで記述する場合 Toggle Edit Mode を選択します。

f:id:nozomi1773:20190416000531p:plain

f:id:nozomi1773:20190416000613p:plain

上記は、通常の関数countを使っています。こちらでグラフを描画すると、下記のようになりした。

f:id:nozomi1773:20190416000704p:plain

 

TimescaleDBの関数を用いて可視化

 TimescaleDBには強力な関数が複数存在します。その一部を試してみます。

 ① 時系列最後尾のデータを取得

SELECT http_host, 
last(http_status, time) AS last_status
FROM demo
GROUP BY http_host

f:id:nozomi1773:20190416002817p:plain

 ② 丸め時間 15分で集計

SELECT 
time_bucket('15 minutes', time) AS "time",
count(http_status) AS "2xx"
FROM demo
WHERE http_status = '2xx'
GROUP BY time
ORDER BY 1

f:id:nozomi1773:20190416004707p:plain

 

  TimescaleDB と Grafana を組わせて使う紹介でした。