フリーのダイナミック DNS サービスとしては 古参のdyndns や No-IP などの多数のサービスがありますが、フリー版のアカウントだと定期的にログインしないと登録したレコードが expire されてしまいます。「もうちょっとでレコードが expire されますよー」という英文のメールが来るたびにログインするのが面倒になってきたので、有料版にアップグレードしようかと思いましたが、AWS の DNS サービスである Route53 で一手間かけると同様の DDNS が格安で運用できるようなので、ちょっとチャレンジしてみました。
AWS を使ったことがあれば、比較的簡単に設定できると思います。
今回の構築にあたっては下記のサイトが非常に参考になりました。
以下、構築メモ。
† 下準備
今回はRoute53 を外部からアップデートするスクリプトとして dnscurl.pl というファイルを使うので、これを動かすために必要となる各種のソフトウェアを予めインストールしておきます。今回の作業は CentOS 6.x で行いました。
yum install -y perl perl-Digest-HMAC perl-XML-XPath
† スクリプト配置ディレクトリの作成
スクリプト類を配置するディレクトリを作成し、必要となるファイルをダウンロードした後、パーミッションを変更しておきます。
mkdir -p /path/to/route53ddns
cd /path/to/route53ddns
wget http://awsmedia.s3.amazonaws.com/catalog/attachments/dnscurl.pl
wget https://gist.github.com/kkosuge/1960895/raw/3428523159465cc4f591b3ae3e706dbf3e991205/route53DynDNS.bash
chmod 755 dnscurl.pl route53DynDNS.bash
† Route 53 の設定
AWS にログインし Route 53 Management Consoleを開いて DDNS に使うゾーンを作成します。今回は DDNS 専用のサブドメインを作って登録したので、使うゾーンは下記のような SOA と NS レコードしかないシンプルなものになりました。設定が完了するとドメイン名の隣に作成したゾーンの Hosted Zone ID というものが表示されているはずなので、これをメモしておきます。

† IAM の設定
セキュリティを考えると、このゾーンをアップデートするのに必要な最小限の権限を持った専用の ID を作成することが好ましいので、IAM Management Consoleを開いて、適当な新しいアカウントを作成し、Permissions の User Policies で下記のポリシーだけを割り当てておきます。文中の(ここにメモしたゾーンIDを書く)の部分については先ほどメモした、Hosted Zone IDの値を書き込んでください。
Route53DDNS Policy
{
"Statement":[
{
"Action":[
"route53:ChangeResourceRecordSets",
"route53:GetHostedZone",
"route53:ListResourceRecordSets"
],
"Effect":"Allow",
"Resource":[
"arn:aws:route53:::hostedzone/(ここにメモしたゾーンIDを書く)"
]
},
{
"Action":[
"route53:ListHostedZones"
],
"Effect":"Allow",
"Resource":[
"*"
]
},
{
"Effect":"Allow",
"Action":[
"route53:GetChange"
],
"Resource":"arn:aws:route53:::change/*"
}
]
}
このポリシーに作成にあたっては 「Tate Eskew » Automating Dynamic DNS updating with AWS Instances and Route53」を参考にしました。
† Access Key ID と Secret Access Key
ポリシーの割り当てが済んだら IAM の Security Credentials から Access Key ID と Secret Access Key を取得しておきます。
このあたりは Route 53 以外の AWS のサービス利用時と同様です。
取得が済んだらサーバーのコンソールに戻り、取得した Access Key ID と Secret Access Key が格納されたファイルを作ります。
他人に見られないようにパーミッションが 600 しておきましょう。
touch .aws-secrets
chmod 600 .aws-secrets
vi .aws-secrets # 下記の内容を挿入
.aws-secrets
%awsSecretAccessKeys = (
"my-aws-account" => {
id => "XXXXXXXXXXXXXXXXXXXX",
key => "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
}
)
† dnscurl.pl のテスト
Route 53 への疎通が問題ないかを確認するために下記のコマンドを打ち込んでみます。
問題がなければ HostedZones というゾーンの一覧が表示されるはずです。
./dnscurl.pl --keyname my-aws-account -- -H "Content-Type: text/xml; charset=UTF-8" https://route53.amazonaws.com/2010-10-01/hostedzone
0.0%
<?xml version="1.0"?>
<ListHostedZonesResponse xmlns="https://route53.amazonaws.com/doc/2010-10-01/"><HostedZones><HostedZone><Id>/hostedzone/XXXXXXXXXXXXXX</Id><Name>hoge.example.jp.</Name><CallerReference>XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX</CallerReference><Config><Comment>hoge ddns</Comment></Config></HostedZone></HostedZones><IsTruncated>false</IsTruncated><MaxItems>100</MaxItems></ListHostedZonesResponse>
† route53DynDNS.bash の設定
環境に合わせて route53DynDNS.bash を編集します。
ここでは example.jp というゾーンに対して hoge というレコードを追加する設定例になります。
また、CentOS の xpath では -e オプションが使えないようなので、この部分については削除しました。
diff -u route53DynDNS.bash{.org,}
--- route53DynDNS.bash.org 2013-10-25 18:35:51.686855629 +0900
+++ route53DynDNS.bash 2013-10-25 19:23:00.255294392 +0900
@@ -2,18 +2,21 @@
### User Settings (things you must set)
+SCRIPT_DIR=`dirname $0`
+cd "$SCRIPT_DIR"
+
## Location of the dnscurl.pl script
-DNSCurl="/path/to/route53DynDNS/dnscurl.pl"
+DNSCurl="./dnscurl.pl"
## The host name you wish to update/create
-myHostName="*"
+myHostName="hoge"
## Zone/domain in which host (will) reside(s)
-myDomainName="example.com"
+myDomainName="example.jp"
##########################################
### Things you may want to set
## set the TTL for the new/updated A record
-myTTL="600"
+myTTL="300"
route53API="https://route53.amazonaws.com/2011-05-05"
route53APIDoc="https://route53.amazonaws.com/doc/2011-05-05/"
@@ -33,7 +36,7 @@
## Get the Hosted Zone ID from Route 53
hostedZoneIDSearch="ListHostedZonesResponse/HostedZones/HostedZone[Name=\"$myDomainName.\"]/Id"
-route53HostedZoneID=`$DNSCurl --keyname my-aws-account -- -s -H "Content-Type: text/xml; charset=UTF-8" -X GET $route53API/hostedzone 2>/dev/null | xpath -e $hostedZoneIDSearch 2>/dev/null |awk -F'[<|>]' '/Id/{print $3}' | cut -d/ -f3`
+route53HostedZoneID=`$DNSCurl --keyname my-aws-account -- -s -H "Content-Type: text/xml; charset=UTF-8" -X GET $route53API/hostedzone 2>/dev/null | xpath $hostedZoneIDSearch 2>/dev/null |awk -F'[<|>]' '/Id/{print $3}' | cut -d/ -f3`
if [ -z $route53HostedZoneID ]; then
echo "Could not find zone '$myDomainName' in route 53"
@@ -47,7 +50,7 @@
currentARecordValueSearch="ListResourceRecordSetsResponse/ResourceRecordSets/ResourceRecordSet[Name=\"\\052.$myDomainName.\"]/ResourceRecords/ResourceRecord/Value"
fi
-currentARecordValue=`$DNSCurl --keyname my-aws-account -- -s -H "Content-Type: text/xml; charset=UTF-8" -X GET $route53API/hostedzone/$route53HostedZoneID/rrset 2>/dev/null | xpath -e $currentARecordValueSearch 2>/dev/null | awk -F'[<|>]' '/Value/{print $3}' | cut -d/ -f3`
+currentARecordValue=`$DNSCurl --keyname my-aws-account -- -s -H "Content-Type: text/xml; charset=UTF-8" -X GET $route53API/hostedzone/$route53HostedZoneID/rrset 2>/dev/null | xpath $currentARecordValueSearch 2>/dev/null | awk -F'[<|>]' '/Value/{print $3}' | cut -d/ -f3`
## And if not, set a flag to create the A record
if [ -z $currentARecordValue ]; then
@@ -64,7 +67,7 @@
currentARecordTTLSearch="ListResourceRecordSetsResponse/ResourceRecordSets/ResourceRecordSet[Name=\"\\052.$myDomainName.\"]/TTL"
fi
-currentARecordTTL=`$DNSCurl --keyname my-aws-account -- -s -H "Content-Type: text/xml; charset=UTF-8" -X GET $route53API/hostedzone/$route53HostedZoneID/rrset 2>/dev/null | xpath -e $currentARecordTTLSearch 2>/dev/null | awk -F'[<|>]' '/TTL/{print $3}' | cut -d/ -f3`
+currentARecordTTL=`$DNSCurl --keyname my-aws-account -- -s -H "Content-Type: text/xml; charset=UTF-8" -X GET $route53API/hostedzone/$route53HostedZoneID/rrset 2>/dev/null | xpath $currentARecordTTLSearch 2>/dev/null | awk -F'[<|>]' '/TTL/{print $3}' | cut -d/ -f3`
if [ -z $currentARecordTTL ]; then
CREATE_INITIAL=true
@@ -165,7 +168,7 @@
## Do the actual create/update
updateResponse=`$DNSCurl --keyname my-aws-account -- -s -H "Content-Type: text/xml; charset=UTF-8" -X POST --upload-file $tmpxml $route53API/hostedzone/$route53HostedZoneID/rrset 2>/dev/null`
## And record the response
-updateResponseStatus=`echo $updateResponse | xpath -e 'ChangeResourceRecordSetsResponse/ChangeInfo/Status' 2>/dev/null |awk -F'[<|>]' '/Status/{print $3}' | cut -d/ -f3`
+updateResponseStatus=`echo $updateResponse | xpath 'ChangeResourceRecordSetsResponse/ChangeInfo/Status' 2>/dev/null |awk -F'[<|>]' '/Status/{print $3}' | cut -d/ -f3`
## Make sure the response is "PENDING"
## Otherwise, error
@@ -177,7 +180,7 @@
fi
## Get the transaction ID
-updateResponseID=`echo $updateResponse | xpath -e 'ChangeResourceRecordSetsResponse/ChangeInfo/Id' 2>/dev/null | awk -F'[<|>]' '/Id/{print $3}' | cut -d/ -f3`
+updateResponseID=`echo $updateResponse | xpath 'ChangeResourceRecordSetsResponse/ChangeInfo/Id' 2>/dev/null | awk -F'[<|>]' '/Id/{print $3}' | cut -d/ -f3`
echo "Got update status ID $updateResponseID with status $updateResponseStatus"
echo "Pausing $sleepDelay seconds to allow for sync"
@@ -186,7 +189,7 @@
## Check for status
statusCheckResponse=`$DNSCurl --keyname my-aws-account -- -s -H "Content-Type: text/xml; charset=UTF-8" -X GET $route53API/change/$updateResponseID 2>/dev/null`
-statusCheckResponseStatus=`echo $statusCheckResponse | xpath -e 'GetChangeResponse/ChangeInfo/Status' 2>/dev/null | awk -F'[<|>]' '/Status/{print $3}' | cut -d/ -f3`
+statusCheckResponseStatus=`echo $statusCheckResponse | xpath 'GetChangeResponse/ChangeInfo/Status' 2>/dev/null | awk -F'[<|>]' '/Status/{print $3}' | cut -d/ -f3`
if [[ "$statusCheckResponseStatus" != "INSYNC" ]]; then
echo "update failed!"
† レコードのアップデートと定期実行
設定が完了したら、route53DynDNS.bash を起動してレコードが挿入もしくはアップデートされることを確認します。
ポーズの秒数が短いと Status が PENDING から INSYNC に変わらずに失敗したように見えることがあるので、Route 53 のウェブ画面で確認したほうが確実です。
LANG=C ./route53DynDNS.bash
Could not find A RR for hoge.example.jp.
Creating initial record
Public IP address is: ###.###.###.###
Creating A record for hoge.example.jp. to be ###.###.###.###
Got update status ID XXXXXXXXXXXXX with status PENDING
Pausing 20 seconds to allow for sync
Success!
最後に cron などで定期実行するようにすれば OK。
crontab -e
*/30 * * * * LANG=C /path/to/route53ddns/route53DynDNS.bash 2>&1 | logger -i -t route53ddns -p local0.info