IAM Identity CenterでAWS Client VPNのSAML認証を構成する

目次

はじめに

個別のユーザーがセキュアにAWSVPC環境にインターネット越しに接続するには複数の方法があり、Systems Manager Session ManagerやEC2 Instance Connectなどが挙げられます。 AWSに限らず、近年はゼロトラストソリューションやAzure AD Application Proxy、Google Cloud IAP等のサービスも普及し、インターネット越しでのクラウド環境へのアクセスも選択肢が増えましたが、AWSで例えばVPC環境のAuroraやRedshiftにクライアントPCから直接接続したい場合、依然としてAWS Client VPNは有用かと思います。

AWS Client VPNでの接続には、証明書ベースの認証やADと連携したユーザーベースのパスワード認証方法が用意されていますが、証明書を各ユーザーに配布したり、ADがない環境でADを用意するのは労力がかかります。そうした時にSAMLベースのシングルサインオン(SSO)環境を構成していると、ADよりWebフレンドリーな方法でユーザーベースの認証をスムーズに行うことができます。

AWS Client VPNでSSOを構成する内容については、世の中で設定方法を紹介した記事がいくつかあるため、本記事では、そこから一歩踏み込んで参考情報が出てこない注意事項についてご紹介します。

Client VPN構成

AWS Client VPNでSSO認証を構成する

AWS Client VPNでSSO認証を構成する方法については軽くご説明することに留めますが、ここでは、AWS IAM Identity Center(Identity Center Directory)をID Provider(IdP)とし、AWS Client VPNをService Provider(SP)とする構成とします。

IAM Identity CenterでSSOしたいClient VPNのアプリケーションを作成する

アプリケーションの編集

  1. IAM Identity Centerの管理画面からアプリケーションを作成し、「カスタム SAML 2.0 アプリケーションの追加」を選択します。
  2. 表示名等、適切な設定値を入力し、IAM Identity Center メタデータファイルをダウンロードします。

    アプリケーションを設定

  3. アプリケーションのプロパティは開始URL, リレー状態共に空欄のままで、セッション時間はデフォルトが1時間ですが、1時間だとローカル環境から大きなデータのバルクロードなどをするとロード中に通信が切れてしまって不便があったりするため、少し長めに設定するのも良いです。

  4. アプリケーションメタデータには、「メタデータ値をマニュアルで入力する」を選択して、「アプリケーションACS URL」には「http://127.0.0.1:35001」、「アプリケーション SAML 対象者」には「urn:amazon:webservices:clientvpn」を入力します。
    アプリケーションメタデータ

IAM Identity CenterとAWSアカウントでSSO構成を準備する

IAM Identity CenterでのSSOを構成するためには、IdPとAWS間で信頼関係を結ぶために、AWSアカウントのIAMサービスでIDプロバイダを作成する必要があります。

  1. Client VPNを構成したいAWSアカウントのIAMでIDプロバイダを作成し、プロバイダのタイプはSAMLとしてIAM Identity CenterでClient VPNのアプリケーションを作成した際にダウンロードしたメタデータファイルをアップロードします。
    IDプロバイダを追加

AWS Client VPNのクライアントVPNエンドポイントを作成する

  1. クライアントVPNエンドポイントを作成する際に、認証情報の項目の「認証オプション」で「ユーザーベースの認証を使用」を選択します。その上で、「ユーザーベースの認証オプション」で「フェデレーション認証」を選択します。
  2. SAML プロバイダー ARN」では、先に作成したIDプロバイダをドロップダウンから選択します。
  3. 「セルフサービス SAML プロバイダー ARN」については、後述します。
  4. その他の設定値は、適切な設計を行い構成します。
    クライアントVPNエンドポイント

AWS Client VPNでSSO認証を構成する際の考慮点は、公式ドキュメントに詳細に記述があるため、こちらも参照ください。
シングルサインオン (SAML 2.0 ベースのフェデレーション認証) - AWS クライアント VPN

SSOで認証し、Client VPNで接続する

  1. 作成したクライアントVPNエンドポイントを選択した状態で、「クライアント設定をダウンロード」をクリックし、クライアント VPN エンドポイント設定ファイル(.ovpn)をダウンロードします。
  2. AWS Client VPNクライアントアプリケーションをPCにインストールし、プロファイルの追加でダウンロードした設定ファイルを取り込みます。
  3. 取り込まれた設定をドロップダウンから選択し、接続をクリックすると、ブラウザが開いてIAM Identity Centerのログイン画面が出ます。ログインが完了すると「認証の詳細を受信、詳細を処理中です。このウィンドウをいつでも閉じることができます。」とブラウザで表示され、VPN接続が確立されます。
    AWS VPN Client
    SSOポータルログイン画面
    Client VPN認証

AWS Client VPNでSSO認証を構成する際の留意事項

ここからが本記事で特筆したい点になりますが、IAM Identity CenterでAWS Client VPNのSSOを構成した場合に、留意すべき事項がいくつかあります。

Client VPNエンドポイント作成後のセルフサービス SAML プロバイダー ARNの入力が出来ない

Client VPNセルフサービスポータルを有効化すると、先ほど手動でダウンロードした.ovpnファイルやAWS Client VPNクライアントアプリケーションのインストーラーの入手を一つのウェブページから行うことが出来るようになり、利用者へのサービス展開が容易になります。
セルフサービスポータルにアクセスする - AWS クライアント VPN

便利な機能のため有効化したいところですが、一度Client VPNエンドポイントを作成してしまうと、マネージメントコンソールでエンドポイントの編集を行っても、「セルフサービス SAML プロバイダー ARN」の入力欄がないため、セルフサービスポータルを有効化するための設定値を後から変更できず、エンドポイントの再作成を行う必要があるため注意が必要です。

IAM Identity Centerは複数のACS URLに対応していない

「セルフサービス SAML プロバイダー ARN」とは、セルフサービスポータルを有効化するために指定が必要なIDプロバイダですが、IdPが複数のACS URLに対応していない場合は、構成編で指定したIDプロバイダとは別のIDプロバイダを作成する必要があり、更に、IDプロバイダに対応するIAM Identity Centerのアプリケーションをもう一つ作成する必要があります。
惜しいことに、IAM Identity Centerは一つのアプリケーションで複数のACS URLに対応していないため、セルフサービスポータルを有効化するため専用のSSOアプリケーションを作成して、「アプリケーションACS URL」に「https://self-service.clientvpn.amazonaws.com/api/auth/sso/saml」を入力し、このアプリケーションに対応するIDプロバイダをメタデータのアップロードなどをしつつ作成する必要があります。

複数ACS URL非対応

SSOポータルのアイコンを任意に隠すことはできない

IAM Identity Centerでアプリケーションを作成すると、SSOポータルにユーザーに割り振られているアプリケーションのアイコンが並びますが、Client VPN用のアプリケーションやセルフサービスポータル用のアプリケーションのアイコンをクリックしても、ACS URLはシステムとしての認証結果がSPに返る際に使用されるもののため、ユーザーにとって意味のあるページは表示されません。
アイコンをクリックしたらセルフサービスポータルが開くとか、Client VPNアプリケーションが立ち上がるといったIdP initiated SSO(IdP側からSSOでアプリケーションを利用するフロー)が出来るのであれば、アイコンの意味がありますが、利用者にとって特別な意味を持たないアイコンを任意で非表示にするような機能はIAM Identity Centerにはないため、ポータル画面が少し乱雑になります。

SSOポータルのアプリケーションアイコン

Client VPNアプリケーションの証明書更新には4-8時間かかる

IAM Identity CenterのSSOアプリケーションを作成し、IDプロバイダ作成時にメタデータをアップロードしましたが、このメタデータの中にはSAMLで使用されるURLやIdPとSPの間で接続の真正性を確認するためのSSL証明書が含まれています。
このSSL証明書の有効期間は、新規にアプリケーションを作成したときは5年になっており、有効期限が切れる前後で新しい証明書を発行してローテーションし、新しい証明書を含んだメタデータを再度IDプロバイダにアップロードしてから、IAM Identity Centerで新しい証明書をアクティブにする必要があります。

アプリケーションの証明書管理

この作業を行わないとIdPとSPの間で信頼関係が正常に確認されなくなるため、Client VPNのSSO認証が失敗するようになりますが、AWSの仕様で、Client VPNに利用するIDプロバイダに新しいメタデータをアップロード(証明書更新)してから4-8時間を待たないと新しい証明書に切り替わりません。内部的にキャッシュを持っているような動作ですが、公式ドキュメントに記載はなく、この仕様はAWSサポートに確認しました。
つまり、うっかりSSL証明書の有効期限が切れてから更新作業を行っても、4-8時間はClient VPNの認証が出来ない状態になるため、注意が必要です。(なお、この仕様はClient VPNに特有のもので、その他のSSOアプリケーションでは問題は起こりませんでした。)

まとめ

本記事では、AWS IAM Identity CenterとAWS Client VPNでSSOを構成して、ユーザーベースのフェデレーション認証を行う方法の概要と、その構成の際に留意しないといけない事項について説明しました。
特に、留意事項についてはまとまった形で参考情報が出てこないものになりますので、同様の疑問をお持ちの方のお役に立てば幸いです。

Route 53プライベートホストゾーンに対してサブドメインのフォワードは可能か?

目次

はじめに

AWSで、Direct ConnectやSite-to-Site VPNを利用してオンプレミスとのハイブリッド構成を取るケースは良くありますが、この記事ではそういった構成でのDNSのゾーン管理についてどのようなパターンが考えられるか説明します。

例えば、潜在的な要望を考えた場合に、オンプレミス環境ではcompany.comのDNSゾーンを使用しており、AWSではプライベートホストゾーンとしてaws.company.comという形でオンプレミスのゾーンのサブドメインを委任して管理したいというお話を伺ったことがあります。

この構成は果たして取り得るのか、取れないのであれば代替構成はあるのか、という内容について本記事では説明します。

Route 53プライベートホストゾーンに対してサブドメインフォワードは可能か?

Route 53プライベートホストゾーンに対するサブドメインの委任は可能か?

結論から言うと、上位ドメインからRoute 53プライベートホストゾーンに対してサブドメインを委任することは出来ません。(しかし、後述するようにフォワードは可能です。)

判断が難しいのは、以下のドキュメントには「サブドメインの責任を委任する NS レコードをプライベートホストゾーンに作成することはできません。」とあり、Route 53プライベートホストゾーンの更に下位のサブドメインに委任を作成することは出来ないことは仕様として確認できるのですが、上位のドメインからRoute 53プライベートホストゾーンにサブドメインを委任できるかについては、記載がないためです。

プライベートホストゾーンを使用する場合の考慮事項 - Amazon Route 53

公式のドキュメントに記載がないため、実際に検証してみましたが、「上位ドメインからRoute 53プライベートホストゾーンに対してサブドメインを委任すること」はできず、検証した後に見つけたのですが、re:Postのナレッジセンターの記事に以下の記載があることからも裏取りが出来ました。

The remote DNS server to forward DNS queries for the domain name instead of delegating authority of the domain name to the inbound endpoint. Note: Inbound endpoints support only recursive DNS queries. Iterative DNS queries sent to the inbound endpoint timeout.

Use an Inbound Endpoint to Resolve Records in a Private Hosted Zone From a Remote Network | AWS re:Post

Route 53プライベートホストゾーンに対するサブドメインフォワードは可能か?

上記re:Postの引用にも記載されているのですが、サブドメインの委任が出来なければ希望の構成は取れないのかというと、必ずしもそういうことはなく、上位ドメインの権威サーバーから条件付きフォワードをするとサブドメインをRoute 53プライベートホストゾーンに管理させる構成を取ることは可能です。

ただし、こちらも留意すべき点があり、私が検証した限りBINDやWindows ServerのDNS機能で、自身が権威であるドメインサブドメインフォワードを設定する場合、まずはサブドメインに対して委任を設定しないとフォワードは機能しません。これは当たり前のことで、サブドメインを委任しなければ自身が管理するゾーンのサブドメインは自身が管理するレコードとして保持しているかどうかを確認することになるため、下位の権威サーバーに委任した上でフォワードするというのは正しい動きと思われます。

こうなると問題になるのが、Route 53プライベートホストゾーンに対してサブドメインフォワードしたいが、その前提となる委任は仕様上出来ないというジレンマです。

この問題を解決するワークアラウンドとして、ダミーのIPアドレスサブドメインの委任を作成し、フォワードは正しいIPアドレスでRoute 53 Inbound Endpointに設定するという方法が取れます。

この方法ははたして仕様の範囲なのか、それともソフトウェアの不具合をついたワークアラウンドなのか、というのが論点になりますが、以下のBINDのメーリングリストでのやりとりを見ると、これは想定された仕様のようです。同じことをWindows ServerのDNS機能でも検証しましたが、ダミーのアドレスでサブドメインの委任を作成して、条件付きフォワードを設定すると、Route 53プライベートホストゾーンに対してサブドメインフォワードすることは出来ました。

What you're seeing is the expected behavior.

  • Without the delegation record (the NS record), named thinks it's authoritative for the zone and won't forward recursive queries. Instead, it returns a negative answer.

  • With the delegation but without the conditional forwarding zone, named would try to follow the delegation (and glue) in response to a recursive query.

  • With both delegation and conditional forwarding zone, named forwards recursive queries for the subzone as expected.

subdomain forwarding on a domain-authoritative dns

まとめ

調査と検証の結果、「Route 53プライベートホストゾーンに対してサブドメインフォワードは可能か?」という問いについては、使用しているDNSサーバーの仕様にもよるが、プレースホルダーとして仮の委任を作成した状態であればサブドメインフォワードは可能である、というのが答えとなります。

先述の通り、AWSのドキュメントで上位のドメインからRoute 53プライベートホストゾーンが管理する下位のドメインに対する委任が作成できるか公式の記述がないため確証が持てませんでしたが、回避策を取ればそのような構成は可能であることが分かりました。本記事が同様の疑問をお持ちの方のお役に立てば幸いです。

参考になる関連情報

domain name system - Forward requests for subdomain to another DNS server in Windows 2k3 - Server Fault

Bind Sub-Domain Zone Forward? - Server Fault

DNS Delegation and Conditional Forwarder for the same Domain - Microsoft Community Hub

クラウドデータウェアハウスの選定小考

目次

はじめに

2022年のタイミングで新規にデータ基盤を構築しようとする時、皆さまは実行基盤をどこに置かれるでしょうか。一昔前であればオンプレミスのデータセンターにDWHのパッケージ製品を導入することが主流であったかと思いますが、ハイパースケーラーのクラウドサービスが隆盛を極めるようになり、コンピューティングリソースのスケーラビリティやほぼ無限のストレージ拡張性、マネージドサービスによる運用負荷の低減といったメリットにより、クラウド上でデータ基盤/DWHを構成する選択が多くなったように思います。

本記事では、私がデータ基盤の構築案件に携わる中で得たいくつかの考慮事項を書き留めることで市場にどのような選択肢があるのかの記録とします。ただし、裏取りが不十分な内容もありますので、いくつかの情報を見た上での個人的な考察になります。

ハイパースケーラーのDWHサービス

ここでいうハイパースケーラーとは、AWS, Azure, Google Cloudのことを指していますが、各社それぞれでDWHサービスを持っています。AWSであればAmazon Redshift、AzureはAzure Synapse Analytics、Google CloudはBig Queryになります。 また、ハイパースケーラーのDWHサービスではありませんが、Snowflakeにも注目すべきでしょう。Snowflakeはハイパースケーラーのクラウドサービス上で稼働するクラウドDWHサービスで、市場での勢いから良く話題に上がります。

※ただし、2022年5月以降、Snowflakeの株価は売り上げ鈍化の懸念から最盛期の半分以下まで下がっています。

スノーフレイク株下落、2四半期連続で売り上げ伸び鈍化見込む - Bloomberg

あくまで私の周りの限られた意見になりますが、どうもデータ分析ならGoogleのBig Queryだとか、Redshiftはアーキテクチャーが古いからSnowflakeに乗り換えるといった傾向の言説を聞くことがありますが、実際のところそういった印象と実態は一致しているのでしょうか。本来であれば実際に自分で性能比較して公正な結果を求めることが最善ですが、それだけのリソースを用意できないため、今回は公開情報と自身がサービスを使用した気付きから洞察をまとめてみます。

性能とコストパフォーマンス比較

クラウドベンダーとも自社DWHサービスと競合他社サービスを比較したベンチマーク結果をブログやサービス紹介ページで掲載していますが、いずれも当然ですが自社サービスが一番優位であると公表しています。

自分の備忘と公正のために、各社が公開しているベンチマーク結果を引用します。

Microsoft

Microsoftが独立系調査会社のGigaOmに依頼して2021年3月に実施されたTPC-DS派生のベンチマーク結果です。Big Queryが実行時間で大きく後れを取っていること、TCOはBig Query, Snowflakeが大きくなる傾向があることが分かります。

Microsoftが公開している性能比較 (引用:Microsoft)

Microsoftが公開しているTCO比較 (引用:Microsoft)

Azure Synapse、AWS、Google の比較 – 分析の比較 | Microsoft Azure

https://azure.microsoft.com/mediahandler/files/resourcefiles/cloud-analytics-platform-total-cost-of-ownership/Cloud%20Analytics%20Platform%20TCO%20-%20Gigaom.pdf

AWS

AWSが2022年4月に公表したTPC-DS派生のベンチマーク結果です。各社の名前が伏せられていますが、ショートクエリのスループットAWSが他を大きく引き離していること、コストパフォーマンスは一社だけ後れを取っていることが分かります。

AWSが公開している性能比較 (引用:AWS)

AWSが公開しているコストパフォーマンス比較 (引用:AWS)

Amazon Redshift continues its price-performance leadership | AWS Big Data Blog

Google

Googleの顧客であるVerizon Mediaが2021年3月に寄稿して公表した独自のベンチマーク結果です。他社の名前が伏せられていますが、ショートクエリと複雑なクエリを複合したパフォーマンスはBig Queryが高いこと、TCOはESGという企業の調査の引用ですが、Big Queryが一番低く、その他のクラウドは同程度であることが分かります。

Googleが公開している性能比較 (引用:Google)

Googleが公開しているTCO比較 (引用:Google)

Benchmarking cloud data warehouse BigQuery to scale fast | Google Cloud Blog

Oracle

Snowflakeから公開されているベンチマーク結果は見当たらなかったため、代わりにOracleが2022年9月に発表したMySQL HeatWave on AWSの発表資料に記載されている資料を確認します。

公表されているのは、TPC-H派生のベンチマーク結果です。MySQL HeatWave on AWSの処理が圧倒的に高速で、次点がSynapse Analytics、その他は大きく遅れて同程度とのこと。コストパフォーマンスはMySQL HeatWave on AWS、Synapse Analytics、Redshift、Snowflake、Big Queryの順に悪くなるようです。

Oracleが公開している性能比較 (引用:Oracle)

Oracleが公開しているコストパフォーマンス比較 (引用:Oracle)

オラクル、MySQL HeatWave on AWSを発表

オラクル、MySQL HeatWave on AWS発表 - 価格性能はRedshiftの7倍 | TECH+(テックプラス)

各社の公開する情報を比較すると、以下のような傾向はある程度見えてくるように思います。

  • 他社名を一部隠していることもあり、どのクラウドのDWHサービスが際立って優秀とも劣っているとも断言はできない
  • 各社ベンチマークを公表するにあたり、TPCベンチマークから派生させたテストや独自のテストで結果を得ている(各社公表の結果は当然同じ条件で実施されていない)
  • TCOの算出も、リザーブインスタンスの使用やコスト削減の計算は各社の匙加減による

以上のことから、推察される見解としては、各社とも自社のサービス特性に有利に働くように条件を設定してベンチマークを行っていることが考えられます。しかし、それは決して悪いことではなく、顧客のシステム特性に最適なDWHサービスを選ぶことが出来れば、最良の処理性能とコストパフォーマンスを得られる結果につながるため、最終的にはAWSがブログ内で述べているように、顧客自身が自身のシステム特性に合ったテストを行って、どのサービスを選定するかが重要であることが分かります。その意味では、冒頭で述べたような他社と比べてBig Queryがデータ分析には絶対的に向いているといったことは必ずしも言い切れないではないかと思います。

DWHのシステム特性とは?

では、DWHのシステム特性とは具体的にどのようなものなのでしょうか?各社の主張や解説を見ると、大枠のイメージは掴めてきます。

まず、TPC-DSとTPC-Hのベンチマークの違いですが、厳密な内容は他に譲るとして、以下のようになります。

私が理解した限りだと、TPC-DSは定型のレポーティング処理や長時間の分析クエリ向けのテストで、TPC-HはBIのダッシュボードをアナリストが色々と検討のために試行錯誤するようなショート(アドホック)クエリ用のテストではないかと思います。

サンプルデータ: TPC-DS | Snowflake Documentation

サンプルデータ: TPC-H | Snowflake Documentation

その意味では、以下の結果が導出されます。

  • Synapse AnalyticsはMicrosoft, Oracleの結果によると、定型クエリは他社と同等レベルで、アドホッククエリは良い数字を出している
  • Redshiftは定型クエリはAWSの結果が良いが、アドホッククエリはOracleの結果によるとやや分が悪い
  • Big Queryは、Microsoftのテスト結果を鵜吞みにすると、定型クエリは他に大きく後れを取っており、アドホッククエリはOracleの結果によるとRedshiftよりは少し良い
  • SnowflakeMicrosoft, Oracleの結果によると、定型クエリはRedshiftと同等レベルで、アドホッククエリはBig Query相当で、バランスは良いがどちらも飛び抜けて良いわけではない
  • コストパフォーマンスについては、どの会社も自社サービスが一番と謳うため、条件設定の匙加減次第に思える

このまとめを一つの結論とすると、現在使用しているクラウドサービスによっては他社のサービスの方がよりシステム特性にマッチするというケースはあり得るが、Big Queryがデータ分析には向いているとか、Snowflakeがすごいというのは、顧客の持つイメージ先行の感があります。

処理性能以外の使い勝手

性能やコストパフォーマンスの疑問に一つの回答が出たところで、最後に私が触って感じたサービスとしての使い勝手について記述します。

AWS中心に仕事をしているため、RedshiftとSnowflakeを比べることになりますが、以下のような違いがあります。

Redshift Snowflake
プラットフォーム AWS限定 マルチクラウド
サーバレス Redshift ServerlessがGA 始めからサーバレス(Virtual Warehouse)
過去データ参照 自由に過去データの参照は出来ない Time Travelで過去90日間のデータ参照可能
データ共有 特になし Snowflakeアカウント間でテーブル・ビュー等を共有可能
バージョンアップ メンテナンスウィンドウでクラスター更新(Redshift Serverlessの場合、ソフトウェアバージョンの更新は自動的に適用) 無停止自動バージョンアップ
ネットワーク接続 プライベート接続可能 プライベート接続はビジネスクリティカル以上のエディションが必要

Redshiftは自身でクラスターを作成するという点においてオンプレミスのDWH製品の延長のような作りを感じさせるところがあり、その点を指摘してBig QueryやSnowflakeはサーバーレスで使用した分だけの従量課金であり、クラスターメンテナンスも不要であるということが喧伝されるのかと思われます。しかし、RedshiftもRedshift Serverlessがリリースされた今、同じ土俵に並んでいると言えるでしょうし、これからのクラウドDWHサービスは各社用途特性に応じてクエリ性能は横並びする競争が行われてくるであろうことを考慮すると、どのような機能を持っているかという点での差別化が一つのポイントになってくるかと思われます。

私自身はAWSを利用したシステム開発の案件に多く携わっているため、AWSでデータパイプラインを構築する場合にRedshiftは他のAWSサービスとの機能連携が良く行われていることから頻繁に利用しています。新機能に対する開発も積極的に行われており、世の中で言われるほど一概にBig QueryやSnowflakeがデータ分析には優れているとも言い切れず、Redshiftの価値が発揮される場面も多いのではないかと感じています。

第三の選択肢

最後に、クラスター・ノード型のDWHとは違うアプローチでデータ基盤を構成する製品であるClouderaについて触れます。

Cloudera Data PlatformはHadoopエコシステムをベースに構成されるデータ基盤サービスで、CDP Public Cloudサービスを選択することで、顧客自身のクラウド環境に迅速にデータ基盤を立ち上げることができるそうです。 AWSでデータ基盤を作る場合は、S3, Redshift, Glueといったサービスを自分で組み合わせますが、CDP Public Cloudサービスでは、Apache HBaseやApache NiFi, Apache Atlas, HUEといったオープンソースソフトウェアベースのプラットフォームを包括的にデプロイするとのことでした。

Amazon EMRの対抗になるサービスのようですが、Amazon EMRより最初から必要な機能が揃っているパッケージと理解しています。Hadoopは分散システムのため、メジャーなクラウドサービスのDWHと比べてパフォーマンスはどうなのかと気になるところですが、データ基盤の一つの選択肢になるのではないでしょうか。

Cloudera Data Platform (CDP) | Cloudera

まとめ

本記事では、公開されているベンチマーク結果を元に、メジャーなクラウドデータウェアハウスサービスの比較を行い、自分の周りで醸成されている各サービスへのイメージが妥当なのか理解を深めました。また、そういったサービスとは異なるアプローチを取っている製品も取り上げました。 クラウドサービスは常に進化を続けているため、この記事もすぐに陳腐化しますが、2022年時点でのクラウドデータウェアハウスを取り巻く環境の理解の助けになれば幸いです。

AWSとSAP BTPの接続構成

目次

はじめに

SAP Business Technology Platform(BTP)とオンプレミス環境、AWS環境を接続する構成検討を行ったため、備忘として記録します。

SAP BTPとは、SAP社のクラウドアプリケーション開発基盤のようなもので、S/4HANA等のSAPの業務アプリケーションの標準機能以外の機能開発を行えるプラットフォームです。SAP社と契約すると主要なハイパースケーラーを選択して利用できます。

参考:SAP Business Technology Platform (BTP) のインフラ概要 - Qiita

AWSでは、資格試験としてSAP on AWS(PAS-C01)を新たに用意する程、SAPをAWSで利用するビジネスが昨今伸びており、本記事でご紹介する構成もそういったビジネスのためのソリューション検討の一つとなります。

SAP Cloud Connectorの構成

SAP BTPクラウドサービスとして提供されているため、例えばBTP上にアプリケーションを開発してオンプレミスのシステムと通信をしようとすると、通信がインターネットに出ることになります。仮にBTPのプラットフォームにAWSを選んだとしても、オンプレミスからBTPまで直接Direct Connectを敷設したり、Site-to-Site VPNを張ることはできません。 また、SAP PrivareLinkサービスというものも開発されているようで、AzureはGAしているようですが、AWS用のサービスはまだ開発中とのことです。

SAP Private Link Service (BETA) is Available | SAP Blogs

この条件下で、BTPからオンプレミス環境にプライベート接続が必要であるとの要件がある場合、オンプレ側の環境にSAP Cloud Connector(SCC)を構築して、リバースプロキシの代替として永続的なTLSトンネルをBTPと構成する必要があります。

今回は、オンプレからAWSまではDirect Connectの閉域網で地続きになっており、AWS上でSCCを構成してBTPと接続する構成を検討します。

SCCの冗長構成

SCCは製品機能でマスタ/シャドウのHA構成を取ることが出来るようです。AWSのブログではシングル構成でオートスケーリンググループの自動復旧の選択肢も提示されていますが、HA構成で待ち受けている方が障害発生時の切り替え時間が早そうな気がする、かつベンダーサポートが得られるマスタ/シャドウ構成とします。

How to connect SAP solutions running on AWS with AWS accounts and services | AWS for SAP

The SAP Cloud Connector offers a software-based HA implementation to protect against failures, or you implement the connector in an Amazon EC2 autoscaling group

SCCの災害対策

前述のマスタ/シャドウ構成はあくまで近距離間のHAソリューションであり、リージョンを跨いだDR構成用ではないと理解しました。

SAPのブログにもSCCはロケーションごとにデプロイしてLocation IDを持たせるとあるので、SCCの災対構成を考えるのであれば、例えば東京リージョンでマルチAZでマスタ/シャドウを構成し、大阪リージョンにもマルチAZでマスタ/シャドウを構成して、それぞれに異なるロケーションIDを持たせるのが良さそうです。災害発生でSCCを稼働しているメインリージョンがダウンした時は、BTP上のアプリケーション側から災対環境SCCのロケーションIDを指定して通信を行うようです。

How to crash your iflows and watch them failover beautifully | SAP Blogs

Connecting multiple Cloud Connectors to an account in SAP Cloud Platform | SAP Blogs

なお、SAPのドキュメントに気になる記載があり、シャドウインスタンスはメインインスタンスと同じネットワークセグメント上に配置するよう記載があります。AZが分かれれば当然サブネットは異なりますので、同じネットワークセグメント上にはなりません。HA構成を組むのにマルチAZで組めない=ゾーン障害に対応できないという古めかしい仕様が現在も有効なものでしょうか?

記憶が定かではありませんが、Db2クラスター構成を組む際もTSAの仕様が古く、ネットワークセグメントを跨いだクラスター構成では切り替えをサポートしないという記述がありました。オンプレミスを前提に設計されているミドルウェアクラスター機能はマルチAZと相性が悪いのかもしれません。

SAP Help Portal

メインインスタンスと同じネットワークセグメント上にシャドウインスタンスをインストールします。

マルチAZの話は一度置くとして、これでSCCの災対構成が出来ました。ただし、SCCの災対構成を考えるのであれば、本質的にはAWSの災対構成だけではなく、オンプレミスのデータセンターの災対構成やBTPの災対構成も検討しなければ、完全な業務継続は出来ません。

なお、BTPの災対構成はマルチデータセンタの設定で自動フェールオーバーの構成を行えるようです。

SAP Help Portal

マルチデータセンタの設定を使用し、自動フェイルオーバーを実装することで、データセンタが停止した場合のアプリケーションの高可用性を確実にします。

SAP Help Portal

アプリケーションを 2 つのデータセンタに並列にデプロイし、問題が発生した場合に相互に切り替えることができるようにします。

SCCの拡張性

SCCの拡張性を考えてみます。SCCはスケールアウトが出来る製品ではないようなので、基本的にはスケールアップが前提になります。ただし、複数SCCをデプロイし、それぞれに異なるロケーションIDを指定してBTP側から接続を分散すれば、疑似的なロードバランスは出来るとのこと。災対構成との関係もあるので、無理にスケールアウトを考えず、スケールアップ前提の構成が良さそうです。

SAP Help Portal

これまでのところ、水平スケールを直接実行することはできません。ただし、関連するすべてのサブアカウントについて異なるロケーション ID を使用して複数の Cloud コネクタ インストールを操作することにより、負荷を静的に分散できます。

SCCのサイジング

SCCをインストールするインスタンスサイズについては、SAPのドキュメントにガイドがあるため、こちらを参照すれば間違いありません。

SAP Help Portal

SCCを導入するOS選定

SAPのガイドで製品出荷マトリクスが公開されているため、この中から検討しましょう。

SAP Help Portal

ここまでの検討を構成図に起こしたものが以下になります。ただし、マスタ/シャドウ構成をマルチAZで組めるかは未確認です。

SCC on AWSの構成

BTPに向けたオンプレミスからの接続

SCCを構成することで、BTPからオンプレミス向けの通信については解決しました。しかし、ここに落とし穴がありますが、SCCはあくまでBTPからオンプレミス環境へHTTP(S), RFC, TCPで通信するものであるということです。

オンプレミスのシステムからBTPのアプリケーションに向けて接続する場合は、SCCのサービスチャネルを使用して限られた通信のみ可能です。それ以外の接続は別の仕組みで通信する必要があります。

SAP Help Portal

Direct ConnectのパブリックVIFで接続

オンプレミスのシステムからBTPのアプリケーションに向けて接続するベストな構成は、Direct ConnectのパブリックVIFを使用することでしょう。例えば、SAP BTPAPI Gatewayを開発したとして、オンプレミスのシステムがBTP上のAPI Gatewayを介して他のシステムとやり取りする場合は、この構成が必要です。

AWSで稼働するSAPソリューションをAWSアカウントおよびサービスと接続する方法 | Amazon Web Services ブログ

BTPサービスに接続するには、インターネット経由でパブリックエンドポイントにアクセスできます。より安定したネットワーク環境を必要とする場合は、AWS Direct Connectを使用してBTPプラットフォームに接続することもできます。ただし、このユーズケースでは、AWS Direct ConnectはオンプレミスネットワークとパブリックなAWSエンドポイントの間で接続を確立する必要があります。

Accessing SAP Cloud Platform via AWS Direct Connect | SAP Blogs

リバースプロキシでの接続

通常であれば、Direct ConnectのPublic VIFで接続すれば、オンプレミスからBTPへの閉じられた接続を行うことが出来ます。ただし、オンプレミスのルーターからPublic VIFへの通信をNAPTする上でNAPTのポート上限(65535)があるため、大量の通信(ポート)が発生するシステムではこの方法を選択できないことがあります。(オンプレミスのルーター側で1:Nではなく、N:NのNAPTができればポート上限を気にする必要はなくなりますが、オンプレミスルーターの拡張性に課題がある場合を想定しています。)

AWS Direct Connect パブリック VIF を使用して、プライベートネットワークを AWS パブリックサービスに接続する | AWS re:Post

その場合、代替案として取り得る選択肢は少ないですが、SCCを構成しているAWS環境に、オンプレミスからBTPまでのリバースプロキシを構成すれば、オンプレミス->Direct Connect->AWSまでの閉じられた通信を作ることはできます。

もっとも、結局その先はAWSからBTPまでインターネットでの通信になるため、システム要件としてインターネットに通信が出ない事、という要件があった場合は満たせません。インターネットに通信が出ない事、ではなく物理的な閉域網を使用すること、という意味であれば、AWSAWSのサービス間での通信は、例えパブリックIPアドレスを使用するものであってもAWSのプライベートネットワークに閉じると説明しているため、BTPの実行環境にAWSを選択して、リバースプロキシもAWSにデプロイすれば、通信はAWSの閉域網に閉じていると言えます。

よくある質問 - Amazon VPC | AWS

Q:2 つのインスタンスがパブリック IP アドレスを使用して通信する場合、またはインスタンスがパブリックな AWS のサービスエンドポイントと通信する場合、トラフィックはインターネットを経由しますか?

いいえ。パブリック IP アドレスを使用する場合、AWS でホストされているインスタンスとサービス間のすべての通信は AWS のプライベートネットワークを使用します。AWS ネットワークから発信され、AWS ネットワーク上の送信先を持つパケットは、AWS 中国リージョンとの間のトラフィックを除いて、AWS グローバルネットワークにとどまります。

なお、先述したSAP PrivateLinkサービスですが、これはあくまでBTPからオンプレミス向けの通信の話であり、通常のPrivateLinkのようにオンプレミスからBTP向けの通信を受けられるわけではないようなので、用途が異なります。

オンプレミスからBTPまでのリバースプロキシ接続

まとめ

本記事では、オンプレミス環境/AWSとSAP BTP環境のFrom/Toの接続についてどのような構成が実現できるか説明しました。昨今伸びているSAP on AWSのビジネスで検討されることがあるSAP BTP環境との接続について、要点をまとめることが出来たと思います。SCCのマルチAZ構成の箇所については内容の正確性の保証は出来ませんが、どなたかの参考になれば幸いです。

S3バケットに保存されたログをCloudWatch Logsに転送する

目次

はじめに

AWS上でデータ基盤を構築するために、データウェアハウスの選択肢としてAmazon Redshiftクラスターを選択することは一般的であると思います。

Redshiftは下記3種類の監査ログをS3バケットに出力することが出来ますが、S3バケットに保存されたログを閲覧しようとすると、Amazon Athenaを使用してあらかじめ作成した外部テーブルに対してSQLクエリを実行する必要があります。あるいは、Amazon OpenSearch Serviceを使用してログ分析基盤を構築し、そこに取り込むというのも手かもしれません。

  • 接続ログ – 認証試行、接続、切断をログに記録します。
  • ユーザーログ – データベースのユーザー定義の変更に関する情報をログに記録します。
  • ユーザーアクティビティログ – 各クエリをデータベースで実行される前にログに記録します。

システムでログ分析基盤をしっかり持っていたり、AthenaやSQLクエリを自由に扱えるのであれば問題はありませんが、例えばそういったスキルセットを持たないシステム運用者がRedshiftの監査ログをもっとカジュアルに確認したい、という場合は、S3バケットに保存されたログをCloudWatch Logsに取り込んでマネージメントコンソール上でGUI操作で閲覧できると便利です。

また、Redshiftの監査ログをCloudWatch Logsに取り込むと、メトリクスフィルターを作成することでログ監視を行うこともできるようになり、より多くのシステム要件に対応することもできます。

本記事では、S3バケットに保存されたログをLambda関数でCloudWatch Logsに取り込むという流れを試してみます。

※やはり需要があったのか2022年4月にAWS公式でRedshiftからCloudWatch Logsへログ出力が出来るようになりました。本記事ではRedshiftのログ取り込みを例に挙げていますが、記事の内容は依然他のS3ログの取り込みに対しても有効な手法です。

Amazon Redshift が監査ログの新たな機能強化を発表

S3バケットのログをCloudWatch Logsに取り込む

CloudWatch Logsに取り込まれたログをS3バケットに保存するという流れであれば、サブスクリプションフィルターを使用してKinesis Data Firehoseへ流し込み、ログ整形用のLambda関数(Kinesis Data Firehose Cloudwatch Logs Processor)を使用する手法が良く用いられますが、その逆を行うことはAWS標準の機能では完結しません。

S3バケットに保存されたログをCloudWatch Logsに取り込むには、S3イベント通知と専用のLambda関数を使用します。 本記事では例としてS3バケットに保存されたRedshiftの監査ログを取り込み対象としますが、原理的には他のログにも応用可能です。

S3に保存されたログをCloudWatch Logsに取り込む流れ

S3イベント通知の設定

S3イベント通知を設定する詳細なステップは省略しますが、まずは、Redshiftが監査ログを出力するS3バケットの以下プレフィックスにファイルが保管されたら、Lambda関数が実行されるようイベント通知を設定します。

AWSLogs/AccountID/ServiceName/Region/Year/Month/Day/AccountID_ServiceName_Region_ClusterName_LogType_Timestamp.gz

S3バケットに保管されたファイルを判別する関数

def lambda_handler(event, context):
    bucket = event['Records'][0]['s3']['bucket']['name']
    key = urllib.parse.unquote_plus(event['Records'][0]['s3']['object']['key'])
    print("Input Bucket : " + bucket)
    print("Input File Full Path : " + key)
    
    #get the file name from the key
    file_pattern1='redshift_useractivitylog_'
    file_pattern2='redshift_connectionlog_'
    file_pattern3='redshift_userlog_'
    file_name = key.split('/')[8]
    key_path=key.replace(file_name,'')

    if file_pattern1 in key:
    
        loggroup='useractivitylogs'
        logstream='useractivitylogstream'
    
        logtransfer(loggroup, logstream, bucket, key, file_name, key_path)
                    
    if file_pattern2 in key:

        loggroup='connectionlogs'
        logstream='connectionlogstream'
        
        logtransfer(loggroup, logstream, bucket, key, file_name, key_path)

    if file_pattern3 in key:

        loggroup='userlogs'
        logstream='userlogstream'
        
        logtransfer(loggroup, logstream, bucket, key, file_name, key_path)

    else:
        print("Skipping")

最初に実行されるlambda_handler関数で、S3イベント通知で連携されたログのファイル名を判別し、Redshiftの3種類の監査ログのどれであるか判別します。 判別したログは、それぞれのログ種別用にあらかじめ作成しておくCloudWatch Logsグループとストリームに転送するよう、ファイル名、S3パスと合わせてlogtransfer関数に渡されます。

ログの内容を展開してCloudWatch Logsに流し込む準備をする関数

def logtransfer(loggroup, logstream, bucket, key, file_name, key_path):
    print("Input File : " + file_name)
    print("Input File Path : " + key_path)

    res = logs_client.describe_log_streams(
        logGroupName=loggroup,
        logStreamNamePrefix=logstream,
    )
    seq_token = res['logStreams'][0].get('uploadSequenceToken', None)
    print("SequenceToken : " + str(seq_token))
    if (seq_token == None):

        res = logs_client.put_log_events(
            logGroupName=loggroup,
            logStreamName=logstream,
            logEvents=[
                {
                    'timestamp': int(time.time()) * 1000,
                    'message': "first attempt"
                },
            ]
        )
            
        res = logs_client.describe_log_streams(
            logGroupName=loggroup,
            logStreamNamePrefix=logstream,
        )
        seq_token = res['logStreams'][0]['uploadSequenceToken']
        print("SequenceToken : " + str(seq_token))

    else:

        res = logs_client.describe_log_streams(
            logGroupName=loggroup,
            logStreamNamePrefix=logstream,
        )
        seq_token = res['logStreams'][0]['uploadSequenceToken']
        print("SequenceToken : " + str(seq_token))

    with open('/tmp/'+file_name, 'wb') as f:
    
        #download the file
        s3.download_fileobj(bucket, key, f)
            
    #extract the content from gzip
    events = []
    i = 0
    with gzip.open('/tmp/'+file_name, 'rt', "utf_8") as fi:
        for line in fi:
                        
            i = i + 1

            ev = {'timestamp' : int(time.time()) * 1000, 'message' : line}
            events.append(ev)
            if (i == 1):
                print("First event : ")
                print(line)
            if (i % 500 == 0): # a higher value may result of a buffer overflow from Boto3 because you cant send more than 1MB of events in one call
                print("Send events " + str(i))
                res = streamevents(events, seq_token, loggroup, logstream)
                seq_token = res['nextSequenceToken']
                events = []
        streamevents(events, seq_token, loggroup, logstream)
                
        print("Last event : ")
        print(line)
        
        print(str(i) + " events sent to CloudWatch Logs")

CloudWatch Logsにログを転送する際は、PUTリクエストに1つ前のリクエストで表示されたSequenceTokenを含める必要があるため、SequenceTokenを取得する処理を行います。なぜSequenceTokenが必要なのか疑問があったためWebを調べると、真偽は不明ですが、AWS内部でログ取り込みを並行処理する際に、前回取り込まれたログと正しい順番で後続ログを取り込むことを保証するためではないかと推測されている方がいました。

普段コードを書かないため初回SequenceToken取得の流れが上手く作れていませんが、上記のコードでも用は足ります。

その後、同じ関数内で渡されたS3バケットの所定プレフィックスに保存されたログをgzip展開し、streamevents関数にログイベントとSequenceTokenをまとめて渡します。

CloudWatch Logsへログを転送する関数

def streamevents(events, sequenceToken, loggroup, logstream):
    kwargs = {
        'logGroupName':loggroup,
        'logStreamName':logstream,
        'logEvents':events,
    }
    if (sequenceToken != None):
        kwargs.update({'sequenceToken': sequenceToken})
    return logs_client.put_log_events(**kwargs)

ここまで出来れば、後はlogs_client.put_log_eventsで渡された情報を元にCloudWatch Logsにログを書き込むだけです。 なお、Lambda関数を実行するために必要なIAMポリシー・ロールや、出力先のCloudWatch Logsグループ・ストリームは別途作成しておきます。

IAMポリシーは、S3に対するGet, ListとCloudWatch LogsのPutLogEvents, DescribeLogStreams, CreateLogGroup, CreateLogStreamがあれば動作しました。

Lambda関数のコード全体

import json
import urllib
import boto3
import re
import gzip
import time

#s3 client
s3 = boto3.client('s3')

#log client
logs_client = boto3.client('logs')

def streamevents(events, sequenceToken, loggroup, logstream):
    kwargs = {
        'logGroupName':loggroup,
        'logStreamName':logstream,
        'logEvents':events,
    }
    if (sequenceToken != None):
        kwargs.update({'sequenceToken': sequenceToken})
    return logs_client.put_log_events(**kwargs)

def logtransfer(loggroup, logstream, bucket, key, file_name, key_path):
    print("Input File : " + file_name)
    print("Input File Path : " + key_path)

    res = logs_client.describe_log_streams(
        logGroupName=loggroup,
        logStreamNamePrefix=logstream,
    )
    seq_token = res['logStreams'][0].get('uploadSequenceToken', None)
    print("SequenceToken : " + str(seq_token))
    if (seq_token == None):

        res = logs_client.put_log_events(
            logGroupName=loggroup,
            logStreamName=logstream,
            logEvents=[
                {
                    'timestamp': int(time.time()) * 1000,
                    'message': "first attempt"
                },
            ]
        )
            
        res = logs_client.describe_log_streams(
            logGroupName=loggroup,
            logStreamNamePrefix=logstream,
        )
        seq_token = res['logStreams'][0]['uploadSequenceToken']
        print("SequenceToken : " + str(seq_token))

    else:

        res = logs_client.describe_log_streams(
            logGroupName=loggroup,
            logStreamNamePrefix=logstream,
        )
        seq_token = res['logStreams'][0]['uploadSequenceToken']
        print("SequenceToken : " + str(seq_token))

    with open('/tmp/'+file_name, 'wb') as f:
    
        #download the file
        s3.download_fileobj(bucket, key, f)
            
    #extract the content from gzip
    events = []
    i = 0
    with gzip.open('/tmp/'+file_name, 'rt', "utf_8") as fi:
        for line in fi:
                        
            i = i + 1

            ev = {'timestamp' : int(time.time()) * 1000, 'message' : line}
            events.append(ev)
            if (i == 1):
                print("First event : ")
                print(line)
            if (i % 500 == 0): # a higher value may result of a buffer overflow from Boto3 because you cant send more than 1MB of events in one call
                print("Send events " + str(i))
                res = streamevents(events, seq_token, loggroup, logstream)
                seq_token = res['nextSequenceToken']
                events = []
        streamevents(events, seq_token, loggroup, logstream)
                
        print("Last event : ")
        print(line)
        
        print(str(i) + " events sent to CloudWatch Logs")

def lambda_handler(event, context):
    bucket = event['Records'][0]['s3']['bucket']['name']
    key = urllib.parse.unquote_plus(event['Records'][0]['s3']['object']['key'])
    print("Input Bucket : " + bucket)
    print("Input File Full Path : " + key)
    
    #get the file name from the key
    file_pattern1='redshift_useractivitylog_'
    file_pattern2='redshift_connectionlog_'
    file_pattern3='redshift_userlog_'
    file_name = key.split('/')[8]
    key_path=key.replace(file_name,'')

    if file_pattern1 in key:
    
        loggroup='useractivitylogs'
        logstream='useractivitylogstream'
    
        logtransfer(loggroup, logstream, bucket, key, file_name, key_path)
                    
    if file_pattern2 in key:

        loggroup='connectionlogs'
        logstream='connectionlogstream'
        
        logtransfer(loggroup, logstream, bucket, key, file_name, key_path)

    if file_pattern3 in key:

        loggroup='userlogs'
        logstream='userlogstream'
        
        logtransfer(loggroup, logstream, bucket, key, file_name, key_path)

    else:
        print("Skipping")

CloudWatch Logsに取り込んだログをCloudWatch Logs Insightsで閲覧

それでは、CloudWatch Logsに取り込んだログをCloudWatch Logs Insightsで閲覧しましょう。

Lambda関数の中できれいに監査ログをフィールドごとに整理出来ればLogs Insightsを使用する必要はありませんが、今回は平文のログをそのままCloudWatch Logsに転送しているため、閲覧時に専用のクエリ言語でログをフィールドごとに整形する必要があります。監査ログのフィールドの詳細はAWSのドキュメントに記載があります。

UseractivityLogs

parse @message "'* [ db=* user=* pid=* userid=* xid=* ]' LOG: *" as recordtime, db, user, pid, userid, xid, query
| sort recordtime asc
| filter user like /user01/

ConnectionLogs

parse @message "* |*|* |* |*|* |* |* |*|* |* |*|*|*|* |*|* |* |*|*" as event, recordtime, remotehost, remoteport, pid, dbname, username, authmethod, duration, sslversion, sslcipher, mtu, sslcompression, sslexpansion, iamauthguid, application_name, driver_version, os_version, plugin_name
| sort recordtime asc

Userlogs

parse @message "*|* |*|* |*|*|*|*|*|*|*" as userid, username, oldusername, action, usecreatedb, usesuper, usecatupd, valuntil, pid, xid, recordtime
| sort recordtime asc

結果

S3に保存されたRedshiftの監査ログをCloudWatch Logs Insightsで閲覧することが出来ました!

S3バケットに保存されたgzip圧縮のテキストログ(可読性が低い)

S3バケットに保存されたテキストログ

CloudWatch Logs Insightで整形されたログ

CloudWatch Logs Insightで整形されたログ

システム設計の仕方により、色々な方法でログ閲覧を行えますが、一つの手法として本記事が参考になれば幸いです。

元情報 / 参考にさせて頂いた記事

データベース監査ログ作成 - Amazon Redshift

A lambda function to stream Application Load Balancer logs dropped in S3 to CloudWatch Logs · GitHub

Lambda (Python) から特定のログストリームにログを書こうとして苦戦した2つのポイント - Qiita

CloudFormation StackSetsとSSMパラメーターストア

目次

はじめに

AWS環境を構築していると、例えば本番環境と検証環境で環境固有の変数は異なるが後は同一構成で作成する、ということがあると思います。そういったときにAWSリソースをCloudFormationテンプレートでデプロイし、環境固有の変数はSSMパラメーターストアに登録すると、複数の環境を作るために最小限の労力で手作業によるミスを最小化して対応できます。今回はそうした構成をどのように実現するか説明します。

CloudFormation StackSetsを使用する

AWSでマルチアカウント管理をしており、例えば共通機能をまとめた共通サービスアカウントのCloudFormation StackSetsから、本番環境用のAWSアカウントと検証環境用のAWSアカウントにクロスアカウントでスタックをデプロイすれば、一つのスタックセットで環境固有の変数のみ共通サービスアカウントのSSMパラメーターストアから取得し、複数の環境を一度にセットアップできて美しい構成になるのではないかと考えたのですが、これは記事執筆時点では出来ない構成の様です。

動的な参照を使用してテンプレート値を指定する - AWS CloudFormation

ssm の動的な参照パターンを使用するときには以下の追加の考慮事項に注意してください。 現在、CloudFormation はクロスアカウント SSM パラメータアクセスをサポートしていません。

実現したかった構成

その代わり、SSMパラメーターストアを共通サービスアカウントに持つのではなく、各環境側に持つようにして、SSMパラメーターストアの値自体は共通サービスアカウントからCloudFormation StackSetsで配るということで妥協しました。この形であれば、StackSetsから各環境にStackが作成されて動的参照が行われるときに、各環境のSSMパラメーターストアが参照されるようです。なお、SecureStringはCloudFormationテンプレートで配ることが出来ないため、結局一部の作業は最終的に各環境で個別の手作業が必要になります。

AWS::SSM::Parameter - Type-SecureString · Issue #82 · aws-cloudformation/cloudformation-coverage-roadmap · GitHub

StackSetsをクロスアカウントで配る際に、配る元のアカウントのSSMパラメーターストアを参照できない事については、AWSサポートに機能追加するよう要望を出しました。Product Feature Requestに登録頂けることを期待しています。

妥協案

SSMパラメーターストアの階層設計

SSMパラメーターストアの階層設計について、公開情報がほとんど見当たらなかったため、こちらの記録も残します。

参考: 階層とタグによるパラメータの組織化及びAmazon EC2 Systems Manager パラメータ ストアのAmazon CloudWatchイベント | Amazon Web Services ブログ

結論としては、マルチアカウント管理の環境でパラメーターを登録する場合は、以下のように階層を作成するのが良いかと思います。

規則:/システム名/アカウントID/サービス名/パラメーター 例:/TestSystem/123123123123/VPC/VpcName

この形にすることで、どのシステムの、どのアカウント(環境)の、どのサービスの、どのパラメーターを指しているかが明確になりました。厳密には、自アカウントの中でパラメーターストアを呼び出すため、規則としてアカウントIDの階層を作る必要はありませんが、将来的に先述のクロスアカウントでStackSetsを配る場合の対応がされることがあれば、パラメーターストアを統合することが可能なようにするためです。

テンプレートから動的参照する際には以下のような形で呼び出します。

VpcName: !Sub '{{resolve:ssm:/TestSystem/${AWS::AccountId}/VPC/VPCName}}

CloudFormation StackSetsのデプロイモデル

余談になりますが、CloudFormation StackSetsにはサービスマネージド型とセルフマネージド型があります。

サービスマネージド型の方が、AWS Organizationsの機能をそのまま使う形でStackSetsをクロスアカウントでデプロイできるのですが、なぜかデプロイ先の指定がOUレベルまでしか出来ない制約がありました。

そのため、これまではわざわざセルフマネージド型を採用して、クロスアカウントでStackSetsをデプロイできるようにIAMロールを各アカウントに配っていたのですが、どこかのタイミングでサービスマネージド型でもOU内のアカウントを指定する形でデプロイできるようになったようです。

Account level targets for service-managed Stack Sets - AWS CloudFormation

サービスマネージド型でアカウントIDを指定してデプロイ

まとめ

本記事では、AWSをマルチアカウント管理していて、共通サービスアカウントのCloudFormation StackSetsからクロスアカウントで各AWSアカウントにスタックをデプロイし、環境固有の変数はSSMパラメーターストアから取得する構成をご紹介しました。どなたかのお役に立てば幸いです。

(もっと)IAM Identity CenterとAzure ADでシングルサインオン

目次

はじめに

※2022/07 AWS SSOがIAM Identity Centerに名称変更されたため、記事を加筆・修正

こちらの記事AWS IAM Identity CenterとAzure ADでシングルサインオン構成を行う方法について、細かな補足を入れつつ説明しました。本記事はその中では書ききれなかったより複雑な条件での留意事項について説明します。

IAM Identity Centerで必須の要求(クレーム)は何か

SAMLを構成する場合、SAML認証時にIDプロバイダー(Azure AD)がサービスプロバイダー(IAM Identity Center)にユーザーの何の属性を渡してユーザー情報を確認させるかをクレームと呼びます。

Azure ADのエンタープライズアプリケーションを作成すると、デフォルトではシングルサインオンの[属性とクレーム]には、以下の情報を渡すようセットされています。

必要な要求:一意のユーザー識別子(nameidentifier)

追加の要求:name, givenname, surname, emailaddress

属性とクレーム

IAM Identity Centerでは、SAML認証時にユーザー情報の識別に使用するのは、[必要な要求]である一意のユーザー識別子のみです。Azure ADから渡されるユーザーのnameidentifierであるusernameが、IAM Identity Centerに登録されたユーザー名と一致すればSAML認証が行われます。

多くの場合、Azure ADではnameidentifierにはuserprincipalname=UPNが使用されますので、usernameにUPNを指定して自動プロビジョニングすれば一致するはずです。

なお、Azure AD側でログインのためにUPN以外の属性を使用している場合は、nameidentifierにUPN以外を指定するよう変更ください。後述しますが、Azure ADのログイン画面でUPNではなくメールアドレスを打ち込むケースだとUPNではなくメールアドレス(user.mail)を必要な要求に指定する必要があるかもしれません。(UPNがメールアドレスと同一の場合はUPNの指定で良い)

Troubleshooting IAM Identity Center issues - AWS IAM Identity Center (successor to AWS Single Sign-On)

The nameID value must exactly match the user name of an existing user in IAM Identity Center (it doesn’t matter if the email address in IAM Identity Center matches or not – the inbound match is based on username)

AWSサポートにも確認しましたが、IAM Identity Centerでは、追加の要求は不要のため、実施する必然性はありませんが、追加の要求をクレームから削除してもSAML認証は動作します。

また、この設定はあくまでSAMLのクレームに関わる設定であるため、自動プロビジョニングのSCIMとは関係がありません。こちらの設定で追加の要求であるemailaddress等を削除しても、自動プロビジョニングの方の属性でメールアドレスを同期するよう指定しておけば、問題なくユーザーはプロビジョニングされます。

必要な要求

Azure ADゲストユーザーの扱いとSAMLクレームの複数の条件

具体的なシステム現場での状況を想定しますが、IAM Identity CenterとAzure ADでシングルサインオンを構成して、ユーザー認証を一本化できたぞとなったときに、一つ問題が発生します。それは、外部ユーザーの存在です。

自社組織の人間であれば当然自社のAzure ADにユーザーIDを持っているでしょうが、外部ベンダーと委託契約をした場合などで、外部ベンダーの作業者は自社のAzure ADのユーザーIDを持っていません。

この状況では2通りの対応方法が考えられますが、一つはシングルサインオンのコンセプトを崩して、外部ベンダーの作業者用にIAMスイッチロールの仕組みを別途作成し、外部ベンダーの作業者はIAMの世界で作業してくださいと決めてしまうパターンです。(Azure ADに障害が発生してAWSアカウントにログインできなくなる可能性もあるため、どのみち緊急避難用のIAMスイッチロールの仕組みを準備しておく必要はいずれにしてもあるかもしれません。)

しかし、これでは夢がありませんので、今回はもう一つの方法を説明します。Azure ADはゲストユーザーとして外部ユーザーを招待することが出来ますので、外部ベンダーの作業者はゲストユーザーとして認証し、IAM Identity Centerにログインさせましょう。

SAMLクレームの複数の条件

ゲストユーザーをSAML認証させる際に問題になるのが、必要な要求の指定です。ゲストユーザーは自社ユーザーではありませんので、仮に自社ユーザーがUPNでログインしている場合、必要な要求はnameidentifierにUPNを指定すれば良いですが、外部ユーザーは一般的にメールアドレス情報で認証しますので、nameidentifierにUPNを指定していたらSAML認証が失敗します。nameidentifierにuser.mailを指定すればゲストユーザーは認証できるようになりますが、今度は自社ユーザーが認証できなくなります。

この問題を解決するには、必要な要求の編集を行い、一意のユーザー識別子の要求条件に複数の条件を持たせる設定を行います。メンバー(自社ユーザー)の場合はUPN、ゲストの場合はuser.mailをSAML認証時にIAM Identity Centerに渡してユーザー名と突合させるという条件を付けることが出来るため、どちらのユーザにも対処できるようになります。

要求の管理

SAMLクレームの複数の条件

ユーザーの自動プロビジョニングの手動実行

以上のようなゲストユーザーのSAMLの構成など、色々と設定変更を試行していると、既定の40分間隔ではなく、即座にユーザーをIAM Identity Centerに同期して認証テストをしてみたいということがあります。そのような場合は、Azure ADのプロビジョニングの画面で、[要求時にプロビジョニングする]を実行します。自動プロビジョニングしたいユーザー名を指定すると、対象ユーザーが即座に同期され、ユーザー属性の何が変更されたか確認できます。

これは、テスト目的でも有用ですし、例えば会社のVIPが新しくAzure ADにオンボードしてきて、ユーザー登録後すぐにシングルサインオン先のアプリケーションを使用したいので40分も待てない、というようなケースにも使用できます。

要求時にプロビジョニング

Identity Centerディレクトリの利用

ここまで外部IDプロバイダーはAzure ADである前提で話をしてきました。しかし、必ずしもすべての企業でAzure ADやOkta等のSaaS対応のIDプロバイダーは使用していないかもしれません。もちろん、オンプレミスでActive Directoryを運用しているなら、Azure Active Directory Connect(AADC)サービスを使用して、Azure ADにユーザー情報を同期しても良いかもしれませんが、ここでは前編の記事でご紹介したように、AWSでユーザー管理が完結するようローカルIDストアとしてIdentity Centerディレクトリにユーザーを一から登録して運用するパターンを考えてみます。

パスワードポリシーが設定できない

Identity Centerディレクトリは無償で使えるディレクトリサービスにはなりますが、企業で使用する際にネックになるのが、ユーザーのパスワードポリシーを指定できない点です。企業では業界のセキュリティ基準などで何文字以上で何回パスワード入力を間違えたらロックする、といった指定を細かにされることが多いですが、Identity Centerディレクトリは以下の製品要件以外のポリシーを指定できず、何回入力失敗したらロックするということもできません。

  • Passwords are case-sensitive.
  • Passwords must be between 8 and 64 characters in length.
  • Passwords must contain at least one character from each of the following four categories: Lowercase letters (a-z), Uppercase letters (A-Z), Numbers (0-9), Non-alphanumeric characters (~!@#$%^&*_-+=`|(){}[]:;"'<>,.?/)
  • The last three passwords cannot be reused.

Password requirements when managing identities in IAM Identity Center - AWS IAM Identity Center (successor to AWS Single Sign-On)

そういったきめ細かいポリシーを設定する必要がある場合は、専用機能を作りこんでいるAzure ADのようなサービスを利用するか、代替措置を何かしら行うことを約束するかという判断が必要です。

AWSとして、AWS SSOをIAM Identity Centerに改称してより推進していくのであろうと考えられますので、パスワードポリシーのきめ細かな設定については実装されることを期待しています。

MFAの設定が可能

一方で、Identity CenterディレクトリはMFA(多要素認証・二段階認証)の構成を行うことが出来ますので、これは有用です。設定により、メールでVerification Codeを送付する二段階認証や、認証アプリ等での二要素認証を設定できるのは柔軟性があります。

Identity CenterディレクトリのMFA設定

SAML証明書とSCIMアクセストークンの更新

最後に、SAMLでのシングルサインオンを構成した後に、運用の中で発生する作業を挙げます。SAMLを構成した際に交換したメタデータには証明書が含まれていましたが、この証明書にも有効期限は存在します。また、SCIMの設定をした際にアクセストークンをIAM Identity Center側で作成しましたが、アクセストークンにも有効期限が存在します。

一度シングルサインオンを構成したら後は安心と思っていると、数年後に突然シングルサインオンが出来なくなって利用者から障害問い合わせが発生するかもしれません。SAMLを構成するための証明書やSCIM用のアクセストークンは、有効期限が迫ってきたら新しい証明書やアクセストークンを生成して、更新作業を行う必要があります。

詳細は下記記事にてまとめられていますので、参照ください。

Azure AD における SAML 署名証明書を更新する | Japan Azure Identity Support Blog

Automatic provisioning - AWS IAM Identity Center (successor to AWS Single Sign-On)

まとめ

本記事では、前回の記事で取り上げられなかったより詳細なSAMLの仕様や動作について説明しました。この情報がどなたかのお役に立ちますと幸いです。