EC2 Image Builder で作成した AMI が OU 単位で共有できるようになった話
これは エキサイトホールディングス Advent Calendar 2021 の25日目の記事です。
みなさん,AWS Organizations と EC2 Image Builder は使っていますでしょうか.AWS Organizations はマルチアカウントを一元的に管理し統制できるサービスです.そして,EC2 Image Builder は Amazon マシンイメージ(AMI)の構築フローを自動化するサービスです.AWS Organizations は AWS のさまざまなサービスと連携することでどんどん便利になってきており,最近のアップデートで EC2 Image Builder と連携して AWS Organizations が管理する各 AWS アカウントへの AMI の共有が容易になったので,今回はその紹介をしたいと思います.アップデートが発表された 2021/11/24 からつい最近までは AWS CLI でしか設定ができなかったのですが,マネージメントコンソールからも設定できるようになっていたのでマネージメントコンソール上での設定を紹介します.
AWS Organizations とは
AWS Organizations は組織のマルチアカウント運用に利用されるサービスで,アカウントの一元管理や各アカウントの統制などに役立ちます.
一般的に,管理するアカウントとなる親アカウントと管理されるアカウントとなる子アカウントで分けて,アカウント管理チームは親アカウントを利用してアカウントを一元管理する構成が多いかと思います.AWS Organizations は他の数多くの AWS のサービスと連携しているため,子アカウントの AWS サービスの設定を親アカウントで一括設定する使い方ができます.また,AWS Organizations では Organization Unit(OU) と呼ばれるアカウントをグループ化する機能を利用してアカウントを管理することができ,ガバナンス目的でそれらの OU に対して「S3 のパブリック公開を禁止する」のようなポリシーを適用することもよくある使い方です.
EC2 Image Builder とは
EC2 Image Builder は AMI の構築フローを自動化するサービスです.
AMI は事前構成された OS イメージで,EC2 インスタンスを作成するために必要な OS とソフトウェアを含んでいます.サービスに必要なパッケージやミドルウェアの設定などをカスタムして AMI を作成し,カスタムした AMI から EC2 インスタンスを起動することでサービス提供可能な EC2 インスタンスを迅速に用意できます.このような目的でカスタムされた AMI をゴールデンイメージと呼ぶこともあります.
ゴールデンイメージを利用する場合,OS のバージョンアップやパッケージの脆弱性対応などさまざまな理由でゴールデンイメージを作り直すことがあり,その都度新しいイメージを構築・テストして必要な環境へイメージを共有しなければなりません.その一連のプロセスをサービス化したのが EC2 Image Builder です.
ここでは EC2 Image Builder の詳細は割愛しますが,用語のざっくりとした解説と AMI の構築フローを図にしておきます.
- イメージレシピ:ミドルウェアや各種サーバの設定を定義した AMI を構築する手順書
- インフラストラクチャー:AMI を構築する環境
- ディストリビューション:AMI の配布先の設定
- ビルドスケジュール:AMI をビルドするスケジュール設定
- パイプライン:上記をまとめた AMI を構築する一連の手順のこと
アップデートの概要
今回のアップデートにより AWS アカウントにゴールデンイメージを配布することが容易になりました.従来の機能で実現できていた手法と今回のアップデートでできるようになったことを説明します.
従来可能であった運用
従来までは以下 2 つの方法で AMI を他アカウントに共有できていました.
- AMI の許可設定を変えて共有する
- EC2 Image Builder 上で設定するパイプラインを AWS Resource Access Manager と AWS Organizations を利用して共有する
前者の場合は,作成されたカスタム AMI の許可設定に 特定の AWS アカウント ID を指定することで他の AWS アカウントに AMI を共有する仕組みです.これは組織が管理しているアカウントが少なければよいですが,数が多くなってくるとアカウントの数だけ許可設定を追加しないといけないので別途 AWS Lambda を使ってプログラムを書いて自動化していくことになるかと思います.現状の運用はこの方法なのですが,AWS Lambda を書いてなんとか頑張るのは他の方法がなかったときの最終手段にしたい気持ちがあります.
後者の場合は,EC2 Image Builder 上で設定するパイプライン自体を共有することを意味しています.AWS のリソース共有を管理する AWS Resource Access Manager というサービスと AWS Organizations を使って OU 単位で共有が可能なのですが,子アカウントでは共有されたパイプラインを EC2 Image Builder で実行して AMI を作成しなければならず,単純に AMI を共有するよりステップが 1 つ増えてしまいます.可能であれば子アカウントの利用者に運用負荷を与えたくないので採用しませんでした.
新しく可能となった運用
今回のアップデートでは EC2 Image Builder と AWS Organizations が連携することにより,パイプラインの流れの中で指定した OU に AMI を共有できる機能が実装されました.具体的には,下図のようにパイプラインの中のディストリビューションフェーズで AMI をどこの OU に配布するか設定できます.
これにより,OU 配下のアカウントに対して AWS Lambda を使わず AMI を共有できるようになりました!
また,OU を使って AMI を共有できるようになるので,OU にアカウントを「入れる・外す」のアクションが AMI を共有「する・しない」の状態に紐づきます.注意点としては,ディストリビューションで OU を設定したパイプラインを一度実行して AMI を作成しないと AMI の共有は機能しないので,ディストリビューションを設定しただけで終わらないようにお気をつけください.
まとめ
NameSpace 機能を使ったネットワークからインターネットに出れなかった話
コンテナのネットワーク周りを理解するためにカーネルの NameSpace 機能を使って Ubuntu20.04 サーバ上に仮想的なネットワークを構築してみた.その際,作成したネットワークからインターネットに出るのに四苦八苦したのでメモとして残しておく.この記事では,ネットワークの構築の詳細は話さず構築後のトラブルシュートをメインとする.
概要
NameSpace 機能を使ったコンテナネットワークの実験をした.同じような実験についてはインターネットにいくつか記事があるが,今回は以下の記事を参考にした.
構築した環境は図のようになっている.
上記ネットワークを構築するコマンドをまとめておいた.もしよければどうぞ.
クリックすると展開されます
#!/bin/bash ip netns add host1 ip netns add host2 ip netns exec host1 ip link set lo up ip netns exec host2 ip link set lo up ip link add name veth1 type veth peer name br-veth1 ip link add name host1 type veth peer name br-host1 ip link add name host2 type veth peer name br-host2 ip link set host1 netns host1 ip link set host2 netns host2 ip link add br0 type bridge ip link set dev br-veth1 master br0 ip link set dev br-host1 master br0 ip link set dev br-host2 master br0 ip addr add 10.0.0.100/24 dev veth1 ip netns exec host1 ip addr add 10.0.0.1/24 dev host1 ip netns exec host2 ip addr add 10.0.0.2/24 dev host2 ip netns exec host1 ip link set host1 up ip netns exec host2 ip link set host2 up ip link set veth1 up ip link set br-veth1 up ip link set br-host1 up ip link set br-host2 up ip link set br0 up echo 1 > /proc/sys/net/ipv4/ip_forward ip netns exec host1 ip route add default via 10.0.0.100 ip netns exec host2 ip route add default via 10.0.0.100
また,環境としては EC2 の Ubuntu20.04 を使ったので環境構築として以下の記事も参考にさせていただいた.また,EC2 の設定としてインターネットと通信できる状態にしておくことが前提となるのでご注意ください.
こちらも Ubuntu で実行できるようコマンドをまとめておいた.
クリックすると展開されます
#!/bin/bash apt-get update apt-get install -y apt-transport-https ca-certificates curl software-properties-common jq curl -fsSL https://download.docker.com/linux/ubuntu/gpg | apt-key add - add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" apt-get install -y docker-ce cgdb cgroup-tools uuid-runtime tree iputils-ping make gcc git clone git://git.kernel.org/pub/scm/linux/kernel/git/morgan/libcap.git /usr/src/libcap cd /usr/src/libcap make make install
NameSpace を使って隔離したネットワークからインターネットへ出ていけない
先に紹介した記事 にもある通り,NameSpace 機能で作成した Host1 や Host2 からインターネットに出ていくためには iptables による Nat を設定しなければならないのでホスト Linux 上で以下のように設定した.
iptables -t nat -I POSTROUTING -s 10.0.0.0/24 -o eth0 -j MASQUERADE
この状態で Host1 から ping を打ってみるが,インターネットへ疎通できなかった.
ip netns exec host1 ping 8.8.8.8 PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data. ...
試しに eth0,veth1,br-veth1,br-host1 のそれぞれで tcpdump をしてみてどこまでパケットが届いているか確認すると br-host1 のキャプチャでしかパケットが表示されなかった.
tcpdump -i eth0 -p icmp -n ... tcpdump -i veth1 -p icmp -n ... tcpdump -i br-veth1 -p icmp -n ... tcpdump -i br-host1 -p icmp -n 01:49:58.420877 IP 10.0.0.1 > 8.8.8.8: ICMP echo request, id 8735, seq 203, length 64 01:49:59.444910 IP 10.0.0.1 > 8.8.8.8: ICMP echo request, id 8735, seq 204, length 64
どうやら bridge 内の通信がまずうまくいっていないので以下の記事を参考にして bridge 内通信を修正.今回は Netfilter で iptables を呼ばない設定をすることにした.記事の最後にこのカーネルパラメータを 1 の状態で解決できなかった理由も書いておく.
sysctl -w net.bridge.bridge-nf-call-iptables = 0
再度各 NIC に対して tcpdump をしてみたところ veth1 までパケットが届いていることがわかった.
tcpdump -i veth1 -p icmp -n 03:06:17.104353 IP 10.0.0.2 > 8.8.8.8: ICMP echo request, id 9766, seq 1, length 64
tcpdump より veth1 から eth0 へのパケットが落ちていることが分かるので,iptables で veth1 から eth0 への FORWARD を設定した.
iptables -t filter -I FORWARD -s 10.0.0.0/24 -d 0.0.0.0/0 -j ACCEPT
eth0 の NIC を tcpdump すると Nat の設定も効いていて,インターネットから eth0 まで通信が返ってきていることを確認できた.
tcpdump -i eth0 -p icmp -n 03:12:11.326420 IP 172.30.20.98 > 8.8.8.8: ICMP echo request, id 9779, seq 1, length 64 03:12:11.328700 IP 8.8.8.8 > 172.30.20.98: ICMP echo reply, id 9779, seq 1, length 64
最後に eht0 から veth1 への戻りの FORWARD 設定をする.セキュリティも考慮してソース IP は 8.8.8.8 だけにしておく.
iptables -t filter -I FORWARD -d 10.0.0.0/24 -s 8.8.8.8/32 -j ACCEPT
そうすると Host1 からインターネットへの接続を確立できる.
ip netns exec host2 ping 8.8.8.8 PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data. 64 bytes from 8.8.8.8: icmp_seq=1 ttl=104 time=2.51 ms 64 bytes from 8.8.8.8: icmp_seq=2 ttl=104 time=2.36 ms
余談
今回は bridge ネットワーク通信を正常にするために net.bridge.bridge-nf-call-iptables
を 0 に設定した対応を取ったが,先の記事 にある iptables -I FORWARD -m physdev --physdev-is-bridged -j ACCEPT
を iptables に設定する方法で進めてみた.
うまくいかない箇所は同じで,まずは veth1 から eth0 への通信が失敗するので iptables に iptables -t filter -I FORWARD -s 10.0.0.0/24 -d 0.0.0.0/0 -j ACCEPT
を設定する.そうすると eth0 にパケットが到達するようになるが,なぜか Nat が効かない状態となる.この Nat をどうにか効かせようと色々調べて設定も変えてみたがうまくいかなかった.結果として net.bridge.bridge-nf-call-iptables=0
にする方法でうまくいったのでそちらをまとめたのだが,もし誰かこの原因をご存知の方がいたらコメント欄に書いていただけると助かります.
まとめ
- トラブルシュートしながらコンテナネットワークからインターネットへ出ていく時の設定を見つけた
- 改めてネットワークトラブルは tcpdump などでパケットを取り続けるしかないことを学んだ
net.bridge.bridge-nf-call-iptables
の設定をそのままにした状態でうまくいかない原因は明確となっていない
サーチリストを使った名前解決はやめよう
DNS を運用していると名前衝突問題に気を付ける必要がある.名前衝突問題とは,組織が内部的に使う Top Level Domain(TLD)と インターネットで利用できる TLD が重複してしまうことによりDNSの動作が期待するものとは違った動作になることを指す.名前衝突問題で具体的に問題となるのは,例えばインターネット上のドメイン名を検索するつもりが,ローカルネットワークで独自に付けたTLDに対して名前解決を行ってしまうことや,またその反対に,ローカルネットワークのドメイン名を検索するつもりがインターネット上の TLD に対して名前解決してしまうことである.JPNIC のページが詳しいのでリンクを置いておく.
今回は後者の問題である「ローカルネットワークのドメイン名を検索するつもりがインターネット上の TLD に対して名前解決してしまう」という事象を再現してみようと思う.上記の記事で紹介されているサーチリストを使ったケースを想定する.
概要
環境として使うのは AWS とし,AWS の各サービスの説明は本質的な部分とは異なるので今回は割愛する.
dig コマンドで名前解決の確認を行うために OS を Ubuntu18.04
とした EC2 を立てておく.EC2 から利用する内部的に使うドメインは Route53 で管理し, example.com
という名前でプライベートホストゾーンを作成する.プライベートホストゾーンにインターネット上に存在するサブドメインを登録し,サーチリストを設定した EC2 から名前解決を行うと「ローカルネットワークのドメイン名を検索するつもりがインターネット上の TLD に対して名前解決してしまう」が再現することを確認する.
Route53 の設定
Route53 では example.com
というプライベートホストゾーンを作成し,適当なプライベート IP アドレス(172.30.20.86)を指定した 2 つの A レコードを登録しておく.
aws
という TLD は AWS が取得しているドメインであり, dns1.nic.aws
を名前解決すると既に A レコードを正引きすることができる.
Route53 の設定を以下の図に示す.
サーチリストの設定
EC2 でサーチリストを設定する.
サーチリストは、DNS検索サフィックス、 DNS suffix search listなどとも呼ばれる、 ユーザーがドメイン名を入力する手間を減らせるようにするためのリストです。 具体的にはDNSにおいて、 名前解決の際にドメイン名を最後まで入力しなくても、 サーバやクライアントで補完がされるように、 補完候補となる文字列を順番に並べたものです。 www.nic.ad.jp
Ubuntu18.04
の場合 EC2 上で /etc/netplan/99-manual.yaml
というファイル名を以下の内容で作成し再起動すればよい.
network: ethernets: eth0: nameservers: search: - example.com version: 2
再起動した EC2 にログインし,サーチリストを使うようにオプションをつけた dig を実行すると example.com
が補完され instance-dst.example.com
を名前解決できていることが確認できる.
root@ip-172-30-20-203:/etc# dig instance-dst +search ; <<>> DiG 9.11.3-1ubuntu1.16-Ubuntu <<>> instance-dst +search ;; global options: +cmd ;; Got answer: ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 41197 ;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1 ;; OPT PSEUDOSECTION: ; EDNS: version: 0, flags:; udp: 65494 ;; QUESTION SECTION: ;instance-dst.example.com. IN A ;; ANSWER SECTION: instance-dst.example.com. 300 IN A 172.30.20.86 ;; Query time: 2 msec ;; SERVER: 127.0.0.53#53(127.0.0.53) ;; WHEN: Sun Oct 31 08:55:09 UTC 2021 ;; MSG SIZE rcvd: 69
では,プライベートホストゾーンに登録してあるもう 1 つのサブドメインの dns1.nic.aws
を名前解決してみる.
root@ip-172-30-20-203:/etc# dig dns1.nic.aws +search ; <<>> DiG 9.11.3-1ubuntu1.16-Ubuntu <<>> dns1.nic.aws +search ;; global options: +cmd ;; Got answer: ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 8193 ;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1 ;; OPT PSEUDOSECTION: ; EDNS: version: 0, flags:; udp: 65494 ;; QUESTION SECTION: ;dns1.nic.aws. IN A ;; ANSWER SECTION: dns1.nic.aws. 300 IN A 213.248.218.53 ;; Query time: 2 msec ;; SERVER: 127.0.0.53#53(127.0.0.53) ;; WHEN: Sun Oct 31 08:58:35 UTC 2021 ;; MSG SIZE rcvd: 57
名前解決の結果 172.30.20.86
ではなく 213.248.218.53
が返ってきている.これより,プライベートホストゾーンではなくインターネットで公開されているドメインが正引きされていることが分かる.これは,サーチリストの補完が行われる前にまずは dig コマンドで指定されたドメインが名前解決されるため, dns1.nic.aws
の正引き結果が返ってきてしまっている状態である.この事象は,プライベートホストゾーンのサブドメインを運用中にそのサブドメインとインターネットに存在するドメインが重複した場合,サーチリストを設定したサーバからの名前解決が失敗する可能性があることを示している.
対策
JPNIC が紹介している対策はシンプルでサーチリストを使わないことである.
名前衝突の問題への根本的な対策は、TLDの重複を避けることです。つまり、原因となっている、内部向けのTLDやサーチリストの使用を止めることです。 www.nic.ad.jp
サーチリストを使わなければ名前解決する際に必ずプライベートホストゾーンの FQDN 指定するので,今回の事象は発生しなくなる.
まとめ
別AWSアカウントへIAMロールを提供する際は外部IDを思い出す
IAMロールでスイッチロール(マネージメントコンソールで他のAWSアカウントのロールにスイッチすること)を実現する際に「外部ID」というオプションがあったので,何のためにあるのかまとめてみた.先に言っておくとスイッチロールを使うときに外部IDを気にする必要はない.
外部IDとは
外部IDとは,第三者用のIAMロールを作成する際にセキュリティ対策用途で設定する値のことである.このセキュリティ対策は「混乱した代理」と呼ばれる問題を解決するために実施される.「混乱した代理」の概要は以下に解説されているが,この記事を通して理解できるかと思う.
以下は実際のIAMロールを作成する画面であり,外部IDを設定できることが分かる.スイッチロールを使うときに外部IDを指定する必要はない旨もかかれている.
コンソールでは、ロールの切り替え機能を使用する外部 ID の使用はサポートされていません。 画像にも書いてある通り,スイッチロールさせたいだけなら外部IDは無視して構わない.
ユースケース
スイッチロールで使わないのであれば,どんなユースケースがあるのかを考えてみる.また「混乱した代理」とその対策である外部IDがどのように機能するのか理解する.
異なる AWS アカウントで複数の顧客をサポートするマルチテナント環境では、AWS アカウントごとに 1 つの外部 ID を使用することをお勧めします。
「混乱した代理」の概要より,複数のAWSアカウントをお客様に持つサービスを提供している会社を例にとると理解しやすい.ここでは「a-mochan」という監視系のSaaSサービスがあると仮定してユースケースを説明する.「a-mochan」もAWS環境でサービス提供をしている.
「a-mochan」では,お客様のS3バケットにあるログへ定期的にアクセスし,お客様ごとにログを可視化するダッシュボードを用意している.「a-mochan」のお客様であるClient Aは自身のAWSアカウントのS3に「a-mochan」からのアクセスを許可するためにIAMロールを作成した.その際外部IDは設定しなかった.Client A は作成したIAMロールのAmazon Resource Name(arn)を「a-mochan」上で設定し,S3のデータを「a-mochan」に取り込めるようにしたことでダッシュボードを確認することができた.
ここで,悪意のあるユーザが「a-mochan」を利用し始めた.悪意のあるユーザは,IAMロールのarnが arn:aws:iam::[AWS AccountID]:role/[Role Name]
のような形で推測が容易なこともあり,Client A が作成したIAMロールのarnを推測し「a-mochan」上で設定することができた.そうすると悪意のあるユーザのダッシュボードにも Client A のS3の情報が表示され,悪意のあるユーザは Client A のダッシュボードを閲覧可能な状態になる.この状態のことを「混乱した代理」と呼ぶ.
では「混乱した代理」対策のために外部IDを利用する流れをみていく.「a-mochan」はお客様ごとに一意のIDを発行し,お客様は作成するIAMロールの外部IDとして「a-mochan」が発行したIDを指定する.そうするとClient AのAWSアカウントでは「a-mochan」からのアクセス時に外部IDが一致しているかどうかチェックするようになるので,「a-mochan」ではClient AのAWSアカウントへのリクエスト時に外部IDを付与する仕様に変更する.外部IDは「a-mochan」のシステムで発行された利用者ごとに一意なIDで,利用者はコントロール不可能な値である.そのため,悪意のあるユーザが Client A のIAMロールのarnを推測できたとしても,「a-mochan」から発行されたClient A用のID(948833852)と悪意のあるユーザ用のID(749678483) が違うので「混乱した代理」は起こらない.
このように外部IDは「混乱した代理」を引き起こさないために設定する値のことであることがわかった.
感想
AWSアカウントへIAMロールを用いてアクセスさせるようなサービスを利用する時は,外部IDの発行が実装されているか確認すべきだと感じた.外部IDを考慮していないサービスはどのような方法で「混乱した代理」を回避しているのか確認して利用するサービスを選定すべきなのかなと思う.また逆も然りで,そのようなサービスを提供する時は外部IDを考慮した実装にすべきである.
まとめ
- 外部IDの意図を理解した
- 外部IDが必要となる「混乱した代理」についてユースケースを用いて理解した
A10のリバースプロキシで外部サーバへ転送する設定
A10の調べ物で色々ググるがあまり出てこないので,今回はA10でのリバースプロキシの設定をまとめておこうと思う.
A10のハードウェアアプライアンスはA10 Thunder 4430
である.
現状確認と要件
オンプレ環境にLBとしてA10を設置しており,A10ではインターネット(エンドユーザ)からのトラフィックを配下のLANに流している.仮にhoge.co.jp
というサービスをオンプレ環境にホストしているとして,https://hoge.co.jp/fuga.txt
というファイルにアクセスされたらそのトラフィックを外部のサーバに流し,それ以外のパスについては従来通り配下のLANに流すような要求が発生したとする.外部サーバにはhttps
で通信し,外部サーバのFQDNはpiyo.co.jp
のようにhoge.co.jp
とは異なることを想定している.今回の外部サーバはAWSにあるものとし,443番ポートを受け付けているALB(Application Load Balancer)とそこに80番ポートを受け付けているEC2がぶら下がっている構成にする.イメージを下図に示す.
上記の要件を実現するためにA10で実施すべきことの詳細を説明する.
SNATの必要性
上記の通信を実現するには,A10で外部からのリクエストを受け取ったとき,ネットワーク層のソースIPをNAT(SNAT)させる必要がある.なぜなら,SNATをしないと外部サーバへたどり着くリクエストのソースIPはクライアントのものになり,外部サーバからのレスポンスがA10を経由せずクライアントに直接返されてしまうからである.クライアントからするとリクエストはA10に送信したのに,レスポンスは外部サーバから受け取ってしまう形となり通信ができない状態となる.したがってA10ではSNATも考慮する.
復号化と再暗号化
A10では受け取ったリクエストのパスで振り分け先を判断するので,一度復号しないとパスを確認することができない.また,A10・外部サーバ間はhttps
の通信を想定している.したがってA10では,受け取ったリクエストのアプリケーション層の情報(主にパス)を確認するために一度復号化し,再度暗号化して外部サーバへ通信する必要がある.
ホストの書き換え
A10配下のLANにホストしてあるFQDNはhoge.co.jp
で,外部サーバにホストしてあるFQDNはpiyo.co.jp
を想定しているので,A10では外部サーバへ転送する際にホストを書き換える必要がある.
実現方法
上記で説明してきた要件をA10では以下の手順で設定していく.
1. ALBをサーバとして登録
外部サーバとしてA10にALBを登録する.外部サーバの登録方法としてIPかFQDNを選べるので今回はALBから払い出されるDNS名を指定する.ヘルスチェックは外部サーバによって決めればよいが,今回は443ポートチェックで行う.作成したサーバをサービスグループとして登録する.
slb server piyo-alb piyo-alb-1111111111.ap-northeast-1.elb.amazonaws.com port 443 tcp health-check tcp_443 slb service-group piyo-sg tcp member piyo-alb 443
2. IP Source Pool作成
SNATのためのIPプール作成.
ip nat pool global_ip_pool 33.44.55.1 33.44.55.10 netmask /24 ip-rr
3. Client SSL,Server SSLテンプレート作成
復号化・暗号化するためのClient SSL,Server SSLテンプレート作成.秘密鍵や証明書の登録は割愛する.
slb template client-ssl any.co.jp ca-cert CA cert any.co.jp key any.co.jp slb template server-ssl reverse-proxy-re-encrypt
4. aFlex作成
A10のリバースプロキシを実現するキモとなるのがこのaFlexである.1~3で設定した内容をaFlexのスクリプトで定義する.また,ホストの書き換えもaFlexで行う.reverse-proxy-aflex
という名前で作成する.
when HTTP_REQUEST { if { [HTTP::path] matches_regex "^/fuga.txt$"} { HTTP::header insert X-Forwarded-For [IP::client_addr] # クライアントのIPを外部サーバのアプリケーションログに出したければこの設定を入れる HTTP::header Host "piyo.co.jp" pool piyo-sg snatpool global_ip_pool SSL::template serverside reverse-proxy-re-encrypt } }
5. バーチャルサーバ作成
/fuga.txtの場合はaFlexで処理し,それ以外の場合はLANに流すサービスグループを指定したバーチャルサーバを作成.
slb virtual-server reverse-proxy-vs 111.222.11.22 port 443 https aflex reverse-proxy-aflex service-group hoge-sg # `/fuga.txt`ではないパスの場合はLANに流すようにサービスグループを設定 template client-ssl any.co.jp #復号化
以上の設定でhttps://hoge.co.jp/fuga.txt
にアクセスするとALBに転送され,その他のパスについてはLANに流れるようになる.
まとめ
CloudFormation StackSetsを使う上で覚えておきたいこと
業務でCloudFormationのStackSets(以降StackSets)という機能を使ってマルチアカウント・マルチリージョンデプロイを試してみて、いくつか気をつけるべき点があったのでメモがてらまとめておこうと思う.
StackSetsについて
StackSetsを簡単に説明する.StackSetsとは,複数のAWSアカウントや複数のリージョンに跨ってAWSサービスをデプロイする時に使用するサービスである.単一のアカウントにデプロイするサービスとしてCloudFormation Stackがあるが,その拡張版といってよい.StackSetsでは,StackSetsを実行するアカウントとStackSetsによってサービスがデプロイされるアカウントが存在する.前者を親アカウント,後者を子アカウントと呼ぶことにする.親アカウントでStackSetsを実行すると,子アカウントにスタックが作成され,そのスタックのテンプレートに記載されている内容が子アカウント上で設定される.
StackSetsを実行すると親アカウントにスタックセットと呼ばれる子アカウントのスタックを管理するリソースが作成され,そのスタックセット配下には各アカウントの各リージョン毎に一意なスタックインスタンス(子アカウントのスタックへのリファレンス)が作成される.詳しくは公式ページを参照あれ.
StackSetsを使う際の注意点
AWS CLIのバージョンの違いによるaws cloudformation describe-stack-set
の表示項目の違い
aws cloudformation describe-stack-set
は作成したスタックセットの詳細を取得するコマンドである.AWS OrganizationsのOrganization Unit(以降OU)を使ったデプロイを使っている場合,aws cloudformation describe-stack-set
を実行することで,OrganizationalUnitIds
の項目に対象のスタックセットがどのOUにデプロイされているかが表示される.しかし,古いAWSコマンドのバージョンによってはOrganizationalUnitIds
という項目が表示されない事象がある.どのバージョンまでが表示されなくて,どのバージョンから表示されるかは分からないが,自分の環境のバージョンで試した結果は少なくともaws-cli/1.16.250
では表示されず,aws-cli/1.18.125
では表示された.OUを使ったデプロイが今年に入ってからの機能なので,AWSコマンドの古いバージョンを使っている場合は注意する.
親アカウントが属しているOUにデプロイするとき,親アカウント自身にはデプロイされない
OUを使うデプロイを考えた時,子アカウントだけでなく親アカウントもOUに含めることで親アカウントにもデプロイするよう試してみたが,親アカウントにはデプロイできなかった. これは公式ページにも記載されている.親アカウントにもデプロイしたい場合は別途StackやStackSetsを使ってデプロイしなければならない.
StackSets は、マスターアカウントが組織内または組織の OU 内にあっても、スタックインスタンスを組織のマスターアカウントにデプロイしません。
このページの「サービスマネージド型のアクセス許可を持つスタックセットを作成する際の考慮事項」はOUを使ったデプロイを想定している方はよく読んでおくとよいかもしれない. docs.aws.amazon.com
マルチリージョンにデプロイする際,グローバルリージョンにしか存在しないリソースの設定には気をつける
マルチリージョンにサービスをデプロイする前提で,グローバルリージョンにしか存在しないサービス(例えばIAM)をStackSetsで設定する場合は気をつけなければならない.例えば,ap-northeast-1
のリージョンでIAMロールを作成し,us-east-1
のリージョンでもIAMロールを作成した時,作成した2つのIAMロール名が同じ場合はエラーになる.この対応としては「どこか1つのリージョンで設定する」もしくは「IAMロール名をリージョン毎に変える」等で対応できる.これは当たり前といえば当たり前だが,意外とグローバルリージョンにしか存在しないサービスはあるので頭の片隅に置いておくとよいと思う.
サービスによってはクロスリージョンの通信ができないことによってStackSetsの実行が失敗する場合がある
サービスによってはクロスリージョンの通信ができないことによりStackSetsの実行が失敗するものがあるのでそれらを考慮する必要がある.
例えば,ap-northeast-1
に作成したAWS Configで通知をAmazon Simple Notification Service(以降SNS)に送信したい時,SNSはap-northeast-1
に作成されている必要がある.これを例えばus-east-1
に作成したSNSに通知しようとするとエラーとなる.他にも例えば,LambdaをStackSetsでデプロイする際,ap-northeast-1
に作成されたLambdaのアーティファクトS3はap-northeast-1
に作成されている必要がある.これを例えばus-east-1
に作成したアーティファクトS3からコードを取得しようとするとエラーとなる.
このように,サービスによってはクロスリージョンアクセス不可能な設定があるので,それぞれのリージョンで必要なサービスを配置しておかないとStackSetsを流した時にエラーになることがある.
個人的な見解になるが,サービスによってクロスリージョンアクセスする・しないを分けるのはややこしいので,特に強い理由がなければ連携するサービスは1つのリージョンにおさめるのが分かりやすくて良いと思っている.
まとめ
- StackSetsを使う上での注意点を列挙した
- OUと連携できるようになってからStackSetsは余計な設定をすることなく使えるようになったので重宝しそう
JumpCloudの鍵認証で使用されるauthorized_keys.jcorigを理解する
業務上AWSのEC2のユーザ管理をJumpCloudで統一して管理している.JumpCloud上で管理対象のEC2に鍵認証を許可することができるのだが,公開鍵を設定する方法として「JumpCloudのコンソール上で設定する方法」と「実際にサーバに手動で設定する方法」がある.後者で設定しようとした際に~/.ssh/authorized_keys
の他に~/.ssh/authorized_keys.jcorig
というファイルがあることに気づいた(以降authorized_keys
とauthorized_keys.jcorig
と表記する).今回はこれがなんのためにあるものなのか調べてみた.
authorized_keys.jcorigとは
公式には詳細については書いていなかった. support.jumpcloud.com
実際にauthorized_keys.jcorig
の中身を確認したところ
# Keys added to this file will be automatically # copied to your authorized_keys file by the JumpCloud agent.
と書いてあり,authorized_keys.jcorig
に書くとJumpCloudエージェントがauthorized_keys
にコピーしてくれるようだ.
さらにauthorized_keys
を確認すると
# Keys added to this file will be automatically # copied to your authorized_keys file by the JumpCloud agent. # Constructed by the JumpCloud agent, you can add keys manually in /home/hoge/.ssh/authorized_keys.jcorig
と書いてあり,サーバ上で手動で設定するならauthorized_keys.jcorig
に書き込めという旨が書いてある.
検証
公式にそこまで情報はないがauthorized_keys.jcorig
に書き込めばauthorized_keys
にJumpCloudエージェントが内容を反映してくれるのだろうという予想はできたので,実際に色々と試して検証してみることにする.JumpCloudエージェントがインストールされたEC2を用意し以下を確認していく.
- サーバ上に手動で設定するパターン
authorized_keys.jcorig
に公開鍵を追加authorized_keys
に公開鍵を追加
- JumpCloud上で設定するパターン
- 公開鍵を追加
- 公開鍵を削除
サーバ上でauthorized_keys.jcorig
に手動で公開鍵を追加
まずは正当な方法であろうauthorized_keys.jcorig
に手動で公開鍵を追加する.しかし,しばらくしてもauthorized_keys.jcorig
の内容がauthorized_keys
にコピーされずログインはできなかった.そこで以下3パターンでログインの可否を確認する.
- EC2を再起動すると
authorized_keys.jcorig
の内容がauthorized_keys
にコピーされログインできた authorized_keys
にも公開鍵を記述するとログインできた(その後EC2を再起動しても特に問題なし)- JumpCloudエージェントを再起動する方法では
authorized_keys
に内容がコピーされることはなくログインできなかった
サーバ上でauthorized_keys
に手動で追加
次にauthorized_keys
に公開鍵を追加してみた.するとログインできるようになったが,EC2を再起動をするとauthorized_keys
から公開鍵が消えてしまいログインできなくなった.
ここまでの検証で以下のことがわかる.
- 基本的には
authorized_keys
に公開鍵が登録されているかどうかで認証の機能が動作する一般的なLinuxの仕組みと同じ動きをする authorized_keys.jcorig
に公開鍵を記述してもauthorized_keys
に記述されないと鍵認証されないので,即時でログインしたい場合はEC2を再起動するかauthorized_keys
にも同じ内容を書くことが必須- 手動で公開鍵を追加するには
authorized_keys.jcorig
に書かないと永続化されない - 公開鍵を削除する際は少なくとも
authorized_keys.jcorig
の公開鍵を削除しないと,EC2を再起動した際に再びauthorized_keys
に書き込まれてしまう - 公開鍵を削除する際,即時にログインできないようにするには
authorized_keys.jcorig
の公開鍵を削除してEC2を再起動するかauthorized_keys.jcorig
・authorized_keys
両ファイルから公開鍵を削除する必要がある authorized_keys.jcorig
の内容をauthorized_keys
にコピーする方法として,JumpCloudエージェントの再起動は使えない- 公開鍵の追加・削除共に
authorized_keys.jcorig
が正として振る舞っている
JumpCloudコンソールから公開鍵を追加
JumpCloudコンソールを使って公開鍵を追加すると,EC2を再起動しなくともしばらくするとauthorized_keys
に公開鍵が追加される.一方authorized_keys.jcorig
には何も追加されない.また公開鍵を手動で追加した場合とJumpCloudコンソールから追加した場合とでauthorized_keys
に記述される公開鍵の位置が以下のように異なる.
$ cat authorized_keys # Keys added to this file will be automatically # copied to your authorized_keys file by the JumpCloud agent. [authorized_keys.jcorigからコピーされた手動で設定した公開鍵] # Constructed by the JumpCloud agent, you can add keys manually in /home/yuichiro.takarada/.ssh/authorized_keys.jcorig [JumpCloudコンソールで設定した公開鍵]
JumpCloudコンソールから公開鍵を削除
JumpCloudコンソールを使って公開鍵を削除すると,EC2を再起動しなくともしばらくするとauthorized_keys
から公開鍵が削除される.ちなみに,JumpCloudコンソールから設定されたauthorized_keys
に記述してある公開鍵を手動で削除するともちろんログインはできなくなるがJumpCloudのコンソールからは削除されない.そこからEC2を再起動するとauthorized_keys
にJumpCloudコンソールで設定した公開鍵が再び設定される.
ここまでの検証で以下のことが分かる.
- JumpCloudコンソールを使って追加された公開鍵は
authorized_keys.jcorig
に書き込まれず,authorized_keys
にのみ書き込まれる - JumpCloudコンソールを使って追加・削除された公開鍵は,EC2を再起動せずとも自動で
authorized_keys
に追加・削除が行われる - JumpCloudコンソールを使って追加された公開鍵が
authorized_keys
からサーバ上で手動により削除された場合でも,JumpCloudコンソールから削除されていなければ,EC2の再起動後再びauthorized_keys
に書き込まれる - JumpCloudコンソールの状態を正として振舞う
検証結果からの学び
検証結果から,JumpCloudエージェントが入ったインスタンスでは「サーバ上で手動による公開鍵を設定する」・「JumpCloudで公開鍵を設定する」という2つの公開鍵設定方法がある中で,前者の方法ではauthorized_keys.jcorig
を正としてauthorized_keys
に公開鍵をコピーし,後者の方法ではJumpCloudコンソールを正としてauthorized_keys
に公開鍵をコピーしていることが分かった.運用においては基本JumpCloudコンソール上から設定する方法でなんら問題ないと思うのでそうするつもりだが,どうしても手動で設定する必要がある場合だけ今回の調査を生かしてauthorized_keys.jcorig
に公開鍵を記述する運用にしたい.とにかく,authorized_keys.jcorig
が「サーバ上で手動による公開鍵を設定する」ためのファイルだということは理解した.
まとめ
- JumpCloudエージェントが入ったインスタンスで
authorized_keys.jcorig
というファイルがなんのためにあるのかということを調べた authorized_keys.jcorig
は「サーバ上で公開鍵を設定する」場合の公開鍵を管理するファイルであることが分かった