はじめに
Route53にゾーンファイルをインポートするのに1000レコード以上あったので、bindtoroute53.plを使ってみました。
EC2インスタンスを使ってインポートしてみました。
ついでに、roadworkerを使ってroute53のレコードを編集もしました。
route53からBIND形式に戻す方法も書きます。
EC2の準備
インスタンスに接続したところから始めます。
$ ssh -i key xxx.xxx.xxx.xxx
Last login: Thu Jun 28 10:43:56 2018 from yyy.yyy.yyy.yyy
__| __|_ )
_| ( / Amazon Linux AMI
___|\___|___|
https://aws.amazon.com/amazon-linux-ami/2018.03-release-notes/
$ sudo su
# passwd
念の為rootのパスワードを設定しておきます。
以降、rootで作業します。
CPANの準備
bindtoroute53.plにはCPANモジュールが必要とのことなのでインストールします。
yum install -y cpan
perl -MCPAN -e shell
→Enter連打
cpan shell -- CPAN exploration and modules installation (v1.9800)
Enter 'h' for help.
cpan[1]> quit
Terminal does not support GetHistory.
Lockfile removed.
*** Remember to restart your shell before running cpan again ***
# exit
$ exit
exitしなさいと言われたのでログインし直します。
CPANモジュールをインストールしていきます。
$ su
Password:
yum install -y gcc
(中略)
perl -MCPAN -e 'install Net::DNS::ZoneFile'
perl -MCPAN -e 'install NetAddr::IP'
perl -MCPAN -e 'install Net::DNS'
perl -MCPAN -e 'install Net::IP'
perl -MCPAN -e 'install Digest::HMAC'
perl -MCPAN -e 'install Digest::SHA1'
perl -MCPAN -e 'install Digest::MD5'
perl -MCPAN -e 'install MIME::Base64'
gccが必要になるので先にインストールしておきます。
# perl -MCPAN -e 'install Digest::SHA1'
Reading '/root/.cpan/Metadata'
Database was generated on Thu, 28 Jun 2018 04:54:22 GMT
Running install for module 'Digest::SHA1'
Running make for G/GA/GAAS/Digest-SHA1-2.13.tar.gz
Checksum for /root/.cpan/sources/authors/id/G/GA/GAAS/Digest-SHA1-2.13.tar.gz ok
CPAN.pm: Building G/GA/GAAS/Digest-SHA1-2.13.tar.gz
Checking if your kit is complete...
Looks good
Generating a Unix-style Makefile
Writing Makefile for Digest::SHA1
Writing MYMETA.yml and MYMETA.json
cp SHA1.pm blib/lib/Digest/SHA1.pm
Running Mkbootstrap for SHA1 ()
chmod 644 "SHA1.bs"
"/usr/bin/perl" -MExtUtils::Command::MM -e 'cp_nonempty' -- SHA1.bs blib/arch/auto/Digest/SHA1/SHA1.bs 644
"/usr/bin/perl" "/usr/share/perl5/vendor_perl/ExtUtils/xsubpp" -typemap '/usr/share/perl5/ExtUtils/typemap' -typemap '/root/.cpan/build/Digest-SHA1-2.13-ZYeggO/typemap' SHA1.xs > SHA1.xsc
mv SHA1.xsc SHA1.c
gcc -c -D_REENTRANT -D_GNU_SOURCE -fno-strict-aliasing -pipe -fstack-protector -I/usr/local/include -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 -O2 -g -pipe -Wall -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector --param=ssp-buffer-size=4 -m64 -mtune=generic -DVERSION=\"2.13\" -DXS_VERSION=\"2.13\" -fPIC "-I/usr/lib64/perl5/CORE" SHA1.c
/bin/sh: gcc: command not found
make: *** [SHA1.o] Error 127
GAAS/Digest-SHA1-2.13.tar.gz
/usr/bin/make -- NOT OK
'YAML' not installed, will not store persistent state
Running make test
Can't test without successful make
Running make install
Make had returned bad status, install seems impossible
gcc: command not found
なんてわかりやすいエラーでしょう。
bindtoroute53.plの準備
bindtoroute53.plスクリプトをダウンロードしておきます。
cd /usr/local/src
wget http://awsmedia.s3.amazonaws.com/catalog/attachments/bindtoroute53.pl
chmod +x bindtoroute53.pl
BIND to Amazon Route 53 Conversion Tool(aws.amazon.com)
bindファイルをxmlへ変換
変換してみます。
インスタンスに、zoneファイルをアップロードしてあるものとします。
ls /usr/local/src
bindtoroute53.pl domain.zone
./bindtoroute53.pl --ignore-origin-ns --ignore-soa --origin < /usr/local/src/domain.zone \
> /usr/local/src/domain.zone.xml
(中略)
ResourceRecords exceeds 1000. Try separating into multiple batches. at ./bindtoroute53.pl line 220.
セパレートしろと言われているんでしょうか。
990行ごとに分割してみます。
split -l 990 domain.zone -d sep
ls | grep sep
sep00 sep01 sep02 sep03
sep00~03まで出来上がりました。
ただし、zoneファイルが省略して記載されている場合、省略部分で切れてしまう可能性があるので、ファイルの行数を確認して分けた方が無難です。
sed -n 1,990p domain.zone > sep00
sed -n 991,1970p domain.zone > sep01
sed -n 1971,2951p domain.zone > sep02
sed -n 2952,3500p domain.zone > sep03
./bindtoroute53.pl --ignore-origin-ns --ignore-soa --origin domain < sep00 > sep00.zone.xml
Ignore ...(中略)
./bindtoroute53.pl --ignore-origin-ns --ignore-soa --origin domain < sep01 > sep01.zone.xml
./bindtoroute53.pl --ignore-origin-ns --ignore-soa --origin domain < sep02 > sep02.zone.xml
./bindtoroute53.pl --ignore-origin-ns --ignore-soa --origin domain < sep03 > sep03.zone.xml
さてインポートするにはどうしたら…
とりあえずブラウザのコンソールから作成したゾーンのインポートに入れてみます。
XMLを理解してくれるのかと半信半疑で行いますが
Error parsing zone file: File too large
結局大きすぎだそうです。
念の為、XMLがインポートできるか、数レコードのXMLで試してましたがだめでした。
Error parsing zone file: Error in line 1: Invalid type 'version=' (encountered after 0 correct records)
In line:
<?xml version="1.0" encoding="UTF-8"?>
XMLを解釈してくれません。
dnscurl.plで反映
分割しても、どうやってインポートすれば良いか書かれていない…
なんのために作ったXMLだよ!と思いましたが、
検索するとdnscurl.plというスクリプトで反映させられるような記事がいっぱい出てきます。
ですが、肝心のAWS公式dnscurl.plの紹介ページはリンク切れ。
インポートした記事のいくつかにwgetのリンク先が書いてあり、
2018/6/29時点でファイルのリンクは切れていなかったので作業を進められましたが、
公式からあっち行けこっち行けと盥回しにされたら樹海にいた気分です。
というわけで、dnscurl.plスクリプトをDLして使ってみます。
cd /usr/local/src
wget http://awsmedia.s3.amazonaws.com/catalog/attachments/dnscurl.pl
chmod +x dnscurl.pl
アクセスキーの設定
AWS Route53のDNSレコードをコマンドラインから操作する(takeshiyako.blogspot.com)によると、
Route53だけ編集できるポリシーを作って、Route53用のユーザーを作ってそのポリシーを適用し、
そのユーザーでアクセスキーを発行する流れでしたが、
今回は自分のアカウントでアクセスキーを発行します。
アクセスキーを設定して有効化確認します。
# aws configure
AWS Access Key ID [None]: XXXXXXXXXXXXXXXXXXXX
AWS Secret Access Key [None]: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
Default region name [None]:
Default output format [None]:
これを公開してしまうと乗っ取られて大量課金されるフラグが立つんですね。
アクセスキーが設定されているのと、このアクセスキーでAWSコマンドが動作するか確認します。
# cat /root/.aws/credentials
[default]
aws_access_key_id = XXXXXXXXXXXXXXXXXXXX
aws_secret_access_key = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
# aws route53 list-hosted-zones
{
"HostedZones": [
{
"ResourceRecordSetCount": X,
"CallerReference": "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX",
"Config": {
"PrivateZone": false
},
"Id": "/hostedzone/XXXXXXXXXXXXXX",
"Name": "your.domain."
}
}
動作しています。
dnscurl.pl --debug
によれば、接続テストができるようなのでしてみます。
アクセスキーの参照は/root/.aws-secrets
というファイルが標準で使われるようなので、作成します。
# tee /root/.aws-secrets <<EOF
%awsSecretAccessKeys = (
# my personal account
'fred-personal' => {
id => 'XXXXXXXXXXXXXXXXXXXX',
key => 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
},
);
EOF
# chmod 600 /root/.aws-secrets
その後試したところ、/root/.aws/credentials
が使われるようです。
tee /root/.aws/credentials <<EOF > /dev/null
[default]
aws_access_key_id = XXXXXXXXXXXXXXXXXXXX
aws_secret_access_key = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
EOF
chmod 600 /root/.aws/credentials
./dnscurl.pl --keyname fred-personal -- https://route53.amazonaws.com/2010-10-01/hostedzone
(中略)
XML形式でいろいろ返ってきてれば成功です。
dnscurl.plでアップロード
事前にホストゾーンを作成しておく必要があり、ホストゾーンのID(Hosted Zone ID)をメモしておきます。
アップロードします。
./dnscurl.pl --keyname fred-personal -- -H "Content-Type: text/xml; charset=UTF-8" -X POST --upload-file sep00.zone.xml https://route53.amazonaws.com/2010-10-01/hostedzone/XXXXXXXXXXXXX/rrset
./dnscurl.pl --keyname fred-personal -- -H "Content-Type: text/xml; charset=UTF-8" -X POST --upload-file sep01.zone.xml https://route53.amazonaws.com/2010-10-01/hostedzone/XXXXXXXXXXXXX/rrset
./dnscurl.pl --keyname fred-personal -- -H "Content-Type: text/xml; charset=UTF-8" -X POST --upload-file sep02.zone.xml https://route53.amazonaws.com/2010-10-01/hostedzone/XXXXXXXXXXXXX/rrset
./dnscurl.pl --keyname fred-personal -- -H "Content-Type: text/xml; charset=UTF-8" -X POST --upload-file sep03.zone.xml https://route53.amazonaws.com/2010-10-01/hostedzone/XXXXXXXXXXXXX/rrset
以下の様なエラーが出る場合、分割したsep00、sep01…を結合しているために発生します。
XML形式ではない(ヘッダが数箇所出てくる)ので修正する必要があります。
<ErrorResponse xmlns="https://route53.amazonaws.com/doc/2010-10-01/"><Error><Type>Sender</Type><Code>MalformedInput</Code><Message>Could not parse XML</Message></Evim
</Changes>
</ChangeBatch>
</ChangeResourceRecordSetsRequest>
<?xml version="1.0" encoding="UTF-8"?>
<ChangeResourceRecordSetsRequest xmlns="https://route53.amazonaws.com/doc/2010-10-01/">
<ChangeBatch>
<Comment>This change imports a zone file</Comment>
<Changes>
以下のようなエラーが出る場合、XML上でも1,000レコードを超えているようです。
<ErrorResponse xmlns="https://route53.amazonaws.com/doc/2010-10-01/"><Error><Type>Sender</Type><Code>InvalidChangeBatch</Code><Message>Number of records limit of 1000 exceeded.</Message></Error><RequestId>
結局XMLでも1000レコード以上扱えないようなので、上記エラーが出る場合は再度分割をやり直します。
エラーが出た…
<?xml version="1.0"?>
<ErrorResponse xmlns="https://route53.amazonaws.com/doc/2010-10-01/"><Error><Type>Sender</Type><Code>InvalidChangeBatch</Code><Message>Tried to create resource record set [name='your.domain', type='TXT'] but it already exists</Message></Error><RequestId>
これは同じレコードがyour.domainであるよというわかりやすいエラーです。
<?xml version="1.0"?>
<ErrorResponse xmlns="https://route53.amazonaws.com/doc/2010-10-01/"><Error><Type>Sender</Type><Code>InvalidChangeBatch</Code><Message>Duplicate Resource Record: XXX.XXX.XXX.XXX</Message></Error><RequestId>
SNIで同じIP使ってると、どのFQDNかわからないエラーだから困る…
同じIPのAレコードが1つのFQDNに対して2個入っているのが原因でした。
エラーが出たファイルは取り込みされていないので、エラーを修正して再度取り込んだところ、問題なく取り込めました。
実行後、PENDINGと出ていれば正常に処理されているようです。
<?xml version="1.0"?>
<ChangeResourceRecordSetsResponse xmlns="https://route53.amazonaws.com/doc/2010-10-01/"><ChangeInfo><Id>/change/XXXXXXXXXXXXXX</Id><Status>PENDING</Status><SubmittedAt>2018-09-05T04:39:22.554Z</SubmittedAt></ChangeInfo></ChangeResourceRecordSetsResponse>
参考資料
bindtoroute53.plとdnscurl.plを使ってRoute53にBINDのゾーン情報を登録する。(qiita.com/kooohei)
まさに同じことをやっている記事です。
roadworker
さてインポートがうまくいったようなので、今度はroadworkerというツールでRoute53をCLI操作してみたいと思います。
こちらの記事を参考にしています。
AWS Amazon EC2にrbenvでRuby環境を構築する(qiita.com/hytkgami)
RoadWorkerで管理するAWS Route53(qiita.com/SatoHiroyuki)
roadworkerのインストール
roadworkerをインストールしていきます。
gem install roadworker
Building native extensions. This could take a while...
Successfully installed pcaprub-0.12.4
Fetching: packetfu-1.1.13.gem (100%)
ERROR: Error installing roadworker:
packetfu requires Ruby version >= 2.1.0.
rubyのバージョンが古いと言われます。
ruby -v
ruby 2.0.0p648 (2015-12-16) [x86_64-linux]
確かに古いので、rbenvを入れて入れられる最新の2.5.1を上記記事を参考にインストールし、roadworkerを入れます。
gitも入っていないのでgitも入れます。
注意)2.4.0以降は警告が出るので、後述の2.3.7をインストールする手順を参考にしてください。
# yum -y install gcc-c++ glibc-headers openssl-devel readline libyaml-devel readline-devel zlib zlib-devel libffi-devel libxml2 libxslt libxml2-devel libxslt-devel sqlite-devel
# yum -y install libpcap-devel
# tee /etc/profile.d/rbenv.sh <<EOF > /dev/null 2>&1
export RBENV_ROOT="/opt/rbenv"
export PATH="\${RBENV_ROOT}/bin:\${PATH}"
eval "\$(rbenv init -)"
EOF
# yum install -y git
# git clone https://github.com/sstephenson/ruby-build.git /opt/rbenv/plugins/ruby-build
# chmod -R 775 /opt/rbenv
# source /etc/profile.d/rbenv.sh
# rbenv install 2.5.1
# rbenv rehash
# rbenv global 2.5.1
# ruby -v
ruby 2.5.1p57 (2018-03-29 revision 63029) [x86_64-linux]
# gem install roadworker
(中略)
# roadwork -v
roadwork 0.5.8
無事roadworkerが入りました。
現在のroute53の情報を、Routefileにダウンロードします。
# cd
# roadwork -e -o Routefile
# head Routefile
# -*- mode: ruby -*-
# vi: set ft=ruby :
hosted_zone "your.domain." do
rrset "syour.domain.", "A" do
ttl 300
resource_records(
"XXX.XXX.XXX.XXX"
)
end
上手くダウンロードできています。
XXX.XXX.XXX.XXXからYYY.YYY.YYY.YYYに変えて、差分を見て、適用してみます。
# cp -p Routefile Routefile.original
# vim Routefile
# roudwork -t -f Routefile
(中略)
/opt/rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/net-dns2-0.8.7/lib/net/dns/rr/classes.rb:35: warning: constant ::Fixnum is deprecated
./opt/rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/net-dns2-0.8.7/lib/net/dns/rr/types.rb:127: warning: constant ::Fixnum is deprecated
(中略)
と思ったら差分比較ですごくたくさんのエラー。
Fixnumとやらがdeprecatedだそうです。エラーではないけれどもひたすら出まくるので気になります。
検索すると2.4.0から出るらしいです。
rubyを古めの2.3.7にしてテストし直します。
# rbenv install 2.3.7
# rbenv rehash
# rebenv global 2.3.7
# gem install roadworker
(中略)
# roadwork -t -f Routefile
# roadwork -t -f Routefile
.............FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
your.domain. A:
expected=XXX.XXX.XXX.XXX(0)
actual=XXX.XXX.XXX.XXX(60)
XXXX examples, XXXX failures
5分くらいかかります。
Fがエラーなのはなんとなく想像が付きます。
エラーの出ているレコードを見ると、TTLを0にしているレコードがエラーを出しているようです。
https://github.com/codenize-tools/roadworker/blob/master/lib/roadworker/dsl-tester.rb
このソースによると、
179:if is_same
180: unless actual_ttls.all? {|i| i <= expected_ttl }
181: is_same = false
182: end
183:end
このあたりでfalseと判定されてそうです。
85:expected_ttl = fetch_dns_name(record.dns_name) ? 60 : record.ttl
97:actual_ttls = response.answer.map {|i| i.ttl }
結果の画面はexpectedは0が入っているっぽいので、97行目のactualに60が入ってしまうのがおかしいようにも思いますがよくわかりません。
人生初のプルリクを送るチャンスかと思いましたが、rubyはわからないので遠慮しておきます。
diffで変えた所以外に違いがなければOKとします。
diff Routefile.original Routefile
# diff Routefile Routefile3
...c...
< "XXX.XXX.XXX.XXX"
---
> "YYY.YYY.YYY.YYY"
dry-runでチェックします。
# roadwork -a -f Routefile --dry-run
Apply `Routefile` to Route53 (dry-run)
Update ResourceRecordSet: your.domain. A (dry-run)
resource_records:
-[{:value=>"XXX.XXX.XXX.XXX"}]
+[{:value=>"YYY.YYY.YYY.YYY"}] (dry-run)
No change
dry-run問題ないようです。
本適用してみます。
# roadwork -a -f Routefile
Apply `Routefile` to Route53
Update ResourceRecordSet: your.domain. A
resource_records:
-[{:value=>"XXX.XXX.XXX.XXX"}]
+[{:value=>"YYY.YYY.YYY.YYY"}]
# dig +noall +ans your.domain @ns-xxxxx.awsdns-56.co.uk
your.domain. 0 IN A YYY.YYY.YYY.YYY
変更後のDNSで引けているようです。
どうやらテストではなくdry-runでテストしたほうが時間もかからず良さげで早くて非常に便利です。
splitも試してみました。
# mkdir split ; cd split
# roadwork -e --split -o Routefile
# ls
your1.domain.route your2.domain.route Routefile
# cat Routefile
# -*- mode: ruby -*-
# vi: set ft=ruby :
require 'your1.domain.route'
require 'your2.domain.route'
個別になったyour1.domain.routeを編集して、Routefileを対象に編集すれば良いようです。
多くのDNSを一度に更新するとエラーが出ました。
根気よくroadwork -a -f Routefile
を続ければそのうち終わります。
[ERROR] Aws::Route53::Errors::Throttling: Rate exceeded
route53からBINDへ
route53からBINDのゾーンファイルへ変換する方法は、jq
コマンドで整形すれば簡単にいきそうです。
HostedZonesInfo=$(aws route53 list-hosted-zones)
HostedZonesNames=$(echo ${HostedZonesInfo} | jq -r '.HostedZones[].Name')
BINDDIR=bind
mkdir ./${BINDDIR}
echo "${HostedZonesNames}" | while read zonename
do
hostedzoneid=$(echo ${HostedZonesInfo} | jq -r ".HostedZones[] | select (.Name == \"$zonename\") | .Id" | cut -d'/' -f3 )
aws route53 list-resource-record-sets --hosted-zone-id ${hostedzoneid} --output json | \
jq -jr '.ResourceRecordSets[] | "\(.Name) \tIN\t\(.Type) \t\(.ResourceRecords[].Value)\n"' \
> ./${BINDDIR}/named.$${zonename}
done