はじめに
普段はWebアプリケーションエンジニアとして、ほぼインフラはノータッチだった。ただ、Webの根幹であるネットワークの基礎の基礎(TCP/IP、DNSなど)を学ぶ中で、多少なりともネットワーク関連の簡単な仕組みや設定を理解できるようになると面白いのではと思った。
そこで今回はAWS EC2上に立てたWebサーバをオリジンサーバとして、以下のことをやってみようと思う。
- 独自のドメインを取得しDNSの設定する
- CDNを設定し、アクセスをCDN経由にする
AWS EC2のWebサーバについて
EC2上に立てたApacheサーバを利用する。
EC2のインスタンスは、VPC内のパブリックサブネット(インターネットゲートウェイを設定)に所属し、EC2はパブリックIPを持つ(Elastic IPではないが今回のやりたいことには問題ない)。
1. 独自のドメインを取得しDNSの設定する
今回は お名前.com を利用して無料のドメインを取得する。
whoisについて
whois
でドメインの所有者の個人情報が見れるが、お名前.comであればこれを代行してくれるので個人のプライバシーの観点でも安心(Whois情報公開代行を参照)。
ドメインの取得方法は別の記事等に譲るとして、ドメインの取得ができたら管理ページからネームサーバをお名前.comのネームサーバ(01.dnsv.jpなど)に設定を変える(これをしないとDNSレコードの設定ができない)。
あとはnslookup等でIPアドレスがEC2ものになっているか、また実際にブラウザから意図したページが見れるか?などを確認してみればいい。
[ec2-user@ip-10-0-1-10 ~]$ nslookup <登録したドメイン>.com
Server: 10.0.0.2
Address: 10.0.0.2#53
Non-authoritative answer:
Name: <登録したドメイン>.com
Address: <EC2のIPアドレス>
上記の通り、意図したページが見られるようになった。なお、SSLサーバ証明はまだ設定していないのでHTTPで通信している。
※ちなみに、今回はAレコードでIPアドレスを指定したが、CNAMEレコードでも同じように今回やりたいことは実現できる。
$ nslookup www.<登録したドメイン>.com
Non-authoritative answer:
Server: UnKnown
Address: <自分のIP>
Name: ec2-<IPアドレスのハイフン区切り>.ap-northeast-1.compute.amazonaws.com
Address: <IPアドレス>
Aliases: www.<登録したドメイン>.com
ただし、DNSの仕様ではCNAMEレコードをゾーンのAPEX(ルートドメイン、つまり example.com のようなドメインの最上位)に配置することはできないので、wwwなどのサブドメインに対してのみCNAMEレコードを設定することになる。そのため、www.<登録したドメイン>.comという形式になっている。
が、CloudflareであればCNAME flatteningという仕組みがあり、以下のような設定が可能になるらしい。
<登録したドメイン>.com. IN CNAME ec2-<IPアドレスのハイフン区切り>.ap-northeast-1.compute.amazonaws.com
余談として
今回、お名前.comでドメインを取得し、DNSのAレコードを設定することで、ドメインの名前解決が行われてEC2のサーバにリクエストが届くようになったが、この作業の裏のDNS周りの設定がどういう風になっているか自身の備忘録として書き出してみたいと思う。
まず、ドメインの登録で何が起きるかだが、これはドメイン名前空間のcom
ドメインにサブドメインを追加することをしている。
サブドメインの追加は、
-
com
ドメインに登録したいサブドメインのネームサーバを構築し、そのゾーン情報を作る -
com
ドメインのゾーンに、登録ドメインのネームサーバを指し示すNSレコードを登録する(権限委譲してもらう)
の2つを行う。
具体的に見ていくほうがわかりやすいので、com
にexample
というサブドメインを追加するを例に、具体的に話を進める。
まずはexample
に対してオーソリティを持つ(ゾーンを管理する)ネームサーバを構築する。
ネームサーバもサーバでインターネット上でアクセスされるので、グローバルIPを持ち、さらにドメインを設定できる。今回は適当に以下とする。
- グローバルIP:10.20.30.40
- ドメイン:ns(つまり、ネームサーバは
ns.example.com.
というFQDNになる)
普通はexample.com
でアクセスしたサーバがあるのでAレコードなどのゾーン情報を作るが、一旦それはskipする。
続いて、com
ドメインのゾーン情報にexampleネームサーバの情報を登録してもらう。
イメージとしては以下のようなデータになる(BINDフォーマット)。
…
; Delegation for example.com.
example.com. IN NS ns.example.com.
ns.example.com. IN A 10.20.30.40
NSレコードでexample
のネームサーバを指定し、AレコードでネームサーバのIPを設定する(これが権限委譲で、comが持つオーソリティをexampleに委譲している)。
これにより、example.com.
の名前解決をするときには、
-
com.
のネームサーバに問い合わせが来る - example.comは
ns.example.com.
のネームサーバがオーソリティを持つからそのネームサーバに聞いて、という返答 -
ns.example.com.
に問い合わせが来て、Aレコードなどを応答
という流れになり、インターネット上でexample.com
のサーバの場所(=IPアドレス)がわかるようになる。
そして、最後にAレコードを設定して、exampl.com.
が名前解決されるようにするが、example
にサブドメインを追加したり、ルート(example.com.
)にIPアドレスを割り当てるなどの操作は、exampleがオーソリティを持つので自分自身で勝手にできる(DNSは分散型のシステムというのはこういうこと)。
操作としてはオーソリティを持つネームサーバの設定をいじるので、最終的には図示すると以下のような状態になる。
2. CDNを設定し、アクセスをCDN経由にする
続いて、CDN経由でオリジンサーバにアクセスするようにCDNの設定をしていきたいと思う。今回はCloudflareを利用する(無料である程度のことができるので)。
Cloudflareの設定は公式のGet started with Cloudflareを参照(ここでは抜粋してやっていく)。
Cloudflareのダッシュボード上でお名前.comで取得したドメインを「サイトを追加」から登録する。途中でお名前.comの管理ページ上でネームサーバをCloudflareのものに更新する。
ネームサーバをCloudflareのものに更新してしばらく(2分くらいだった)すると、以下のようにアクティブになる。
アクティブになった後でnslookup
をしてみると、以下のようにオリジンサーバーの情報ではなくCloudflareのCDNのIPアドレスが返答されるようになる。
$ nslookup www.<登録したドメイン>.com
Non-authoritative answer:
Server: UnKnown
Address: <自分のIP>
Name: www.<登録したドメイン>.com
Addresses: 172.67.201.235
104.21.76.226
これでブラウザからのリクエストがCloudflareのCDNを経由しオリジンサーバーに到達するようになった。ブラウザからアクセスしてみても、Cloudflareの設定をする前と同じように閲覧できることが確認できる。
※1点、上記の画像を見ればわかるが、Cloudflareを導入することでブラウザとCDNの間はCloudflareのエッジ証明書のおかげでSSL/TLSで暗号化されるのでHTTPSでの通信ができるようになる。詳細は「余談として」で触れる。
- 参考文献
余談として
CloudflareのProxy statusについて
Proxy statusに詳細が書かれているが、以下の画像のようにプロキシステータスが「プロキシ済み」になっていないと、Cloudflare経由でのオリジンサーバーにアクセスする設定にならない。「DNS のみ」にすると、単に名前解決されるだけなのでオリジンサーバーの情報がばれる。
CloudflareのSSL/TLSについて
Cloudflareを利用すると、CDNまでの経路はデフォルトでSSL/TLSで暗号化されるように設定される。
ただ、Cloudflareとオリジンサーバー間はHTTPで通信できる状態になっている。これをCloudflareのオリジン証明書(サーバ証明書)を使用してCloudflareとオリジンサーバー間も暗号化されるようにしてみようと思う。
Cloudflareではオリジン証明書は以下から作成できる。
作成した後は、以下のようなコマンドでオリジン証明書・プライベートキーを設定する。
$ sudo dnf install -y mod_ssl
$ sudo mkdir -p /etc/ssl/certs /etc/ssl/private
$ sudo vim /etc/ssl/certs/selfsigned.crt
$ sudo vim /etc/ssl/private/selfsigned.key
$ sudo vim /etc/httpd/conf.d/ssl.conf
sudo vim /etc/httpd/conf.d/ssl.conf
では、SSLCertificateFile
とSSLCertificateKeyFile
のパスを作成したファイルのパスに上書きする。
それが完了したら、sudo systemctl restart httpd.service
でApacheサーバを再起動する。
httpd.serviceのステータスを確認し、443でlistenしていることを確認できればOK(sudo lsof -i -n -P
でもいいかも)。
$ sudo systemctl status httpd.service
● httpd.service - The Apache HTTP Server
...
May 28 06:06:34 ip-10-0-1-10.ap-northeast-1.compute.internal systemd[1]: Starting httpd.service - The Apache HTTP Server...
May 28 06:06:34 ip-10-0-1-10.ap-northeast-1.compute.internal systemd[1]: Started httpd.service - The Apache HTTP Server.
May 28 06:06:34 ip-10-0-1-10.ap-northeast-1.compute.internal httpd[138473]: Server configured, listening on: port 443, port 80
$ sudo lsof -i -n -P
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
…
httpd 138473 root 4u IPv6 575628 0t0 TCP *:80 (LISTEN)
httpd 138473 root 6u IPv6 575638 0t0 TCP *:443 (LISTEN)
httpd 138476 apache 4u IPv6 575628 0t0 TCP *:80 (LISTEN)
httpd 138476 apache 6u IPv6 575638 0t0 TCP *:443 (LISTEN)
httpd 138477 apache 4u IPv6 575628 0t0 TCP *:80 (LISTEN)
httpd 138477 apache 6u IPv6 575638 0t0 TCP *:443 (LISTEN)
httpd 138478 apache 4u IPv6 575628 0t0 TCP *:80 (LISTEN)
httpd 138478 apache 6u IPv6 575638 0t0 TCP *:443 (LISTEN)
続いてVPCのセキュリティグループの設定で、インバウンドにHTTPS(443)を許可するように設定を更新する。
ここまで設定できると、ブラウザからアクセスできるようになる(curl
などのコマンドでも同じ)。
ちなみに、証明書を設定しないで「フル」モードにすると以下のように522エラーになる。
Cloudflareのオリジン証明書について
通常、SSLサーバ証明書はPKI(公開鍵基盤)のCA(認証局)に発行してもらい、そのレベルによってDV、OV、EVなどの区分けがされる。
が、Cloudflareの場合はCloudflareのCAというものがあり、これを利用することでCloudflareのCDNとオリジンサーバの間のSSL/TLS暗号化ができる。ちなみに、Full (strict) - SSL/TLS encryption modesに以下の通り書かれているので、厳密のモードも利用可能。
Issued by a publicly trusted certificate authority or Cloudflare’s Origin CA.
CloudflareのAuthenticated Origin Pulls (mTLS)について
クライアントとサーバの間にCDNを挟むことで、クライアントからのリクエストがオリジンサーバに届かなくなるのでセキュリティを高めることができるが、悪意のあるユーザが何らかの方法でオリジンサーバのドメイン(サブドメイン)を知った場合、CDNを経由しないで直接リクエストが送られてきてしまう。
それでは意味がないので、CDNからのリクエストであることを識別できるようにして、オリジンサーバはそれ以外を受け付けないという設定をするべき。
そのためのオプションがAuthenticated Origin Pulls (mTLS)。これは、TLSハンドシェイク時にClient Certificate request
をオリジンサーバが送ることでクライアント証明書を要求しその証明書を確認してセッションを確立する、という仕組みを利用している。つまり、オリジンサーバに届くリクエストの内、TLSハンドシェイク時にCloudflareのクライアント証明書がないものは偽物なのではじくということができるようになる。
実際に設定してみる(今回はCloudflareが用意しているクライアント証明書を利用する)。手順の詳細はZone-level authenticated origin pulls を参照。
まずは、Cloudflareの証明書をダウンロードする(pemファイル)。ダウンロードしたらEC2の/etc/ssl/certs/authenticated_origin_pull_ca.pem
に配置する。
続いてApacheなので、以下のように設定を更新する。
$ sudo vim /etc/httpd/conf.d/ssl.conf
$ sudo systemctl restart httpd.service
SSLVerifyDepth 1
SSLCACertificateFile /etc/ssl/certs/authenticated_origin_pull_ca.pem
SSLVerifyClient require
これで設定は完了になる。実際にcurlでクライアント証明書を持っていない状態で直接オリジンサーバにリクエストを送ってみると以下の通りクライアント証明書の検証で失敗していることがわかる(curl: (56) OpenSSL SSL_read: error:0A00045C:SSL routines::tlsv13 alert certificate required, errno 0
の部分)。
$ curl https://ec2-<EC2のIPアドレスのハイフン区切り>.ap-northeast-1.compute.amazonaws.com/ -v -k
* Trying <EC2のIPアドレス>:443...
* Connected to … (#0)
* …
* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384
* …
* Server certificate:
* subject: O=CloudFlare, Inc.; OU=CloudFlare Origin CA; CN=CloudFlare Origin Certificate
* start date: May 28 02:17:00 2024 GMT
* expire date: May 25 02:17:00 2039 GMT
* issuer: C=US; O=CloudFlare, Inc.; OU=CloudFlare Origin SSL Certificate Authority; L=San Francisco; ST=California
* SSL certificate verify result: unable to get local issuer certificate (20), continuing anyway.
* …
* TLSv1.3 (IN), TLS alert, unknown (628):
* OpenSSL SSL_read: error:0A00045C:SSL routines::tlsv13 alert certificate required, errno 0
* Closing connection 0
curl: (56) OpenSSL SSL_read: error:0A00045C:SSL routines::tlsv13 alert certificate required, errno 0
ちなみに、余談だがサーバ証明書の内容も表示されており、CloudFlare Origin CA
であることがわかる。
オリジンサーバ側でのCDNからのリクエストをチェックしてはじく方法について
Cloudflareの場合はTLSのクライアント証明書を利用して、CDNからのリクエストを識別していたが、他のCDNサービスでも同じことができるかというと不明。
Web配信の技術 ―HTTPキャッシュ・リバースプロキシ・CDNを活用するという本には、CDNからのリクエストのヘッダに何かしらの値を入れておいてそれがなければオリジンサーバ側ではじく、という方法が紹介されていた。それなら色々なCDNで対応できるみたい。
また、同本に書かれれていたが、CloudflareのAuthenticated Origin Pulls (mTLS)にしろヘッダに何かしらの値をいれるにしろ、結局オリジンサーバのサブドメインがばれるとリクエスト自体はできるので、最低限推測しにくいサブドメインにするなどの対策は必要と書かれていた(←サブドメインを推測しにくいものにすることは、「セキュリティのための曖昧化」(security through obscurity)と言うらしい)。
なぜCDN経由にするのか?
CDNを利用すると、オリジンサーバの存在を隠すことができ、かつCDNに備わっているセキュリティ機能の利用ができるため(他にもキャッシュの話もあるが今回はそれは考えない)。
具体的にどういうことかというと、まず、CDNがない場合はブラウザからアクセスしてくるユーザー目線で考えると、以下のように直接EC2のサーバにリクエストを送っている状態になる。ということは悪意のあるユーザーであればサーバにDDoS攻撃を仕掛けたりができてしまう。
一方、CDN経由であればEC2のサーバ(オリジンサーバ)のIPアドレスを知ることはできず、またCDNには大抵WAFやDDoS保護などの機能があるのでそれを利用してセキュリティを強化できる。
まとめとして
今回は、ドメインをお名前.comで取得しお名前.comのネームサーバの設定を変更してEC2のWebサーバが名前解決されるようするという事と、CloudflareのCDNを経由してオリジンサーバ(EC2)にアクセスさせるという事の2つをやってみた。
普段Webアプリケーションエンジニアではやらないところだと思うので、基本的な部分だけでも手を動かしてやってみることで理解を深められたような気がした。また、インフラエンジニアの方との会話で理解できることが増えそうな気がした(笑)。