
Windows Server
イベント
マガジン
該当するコンテンツが見つかりませんでした
技術ブログ
みなさんこんにちは、イノベーションセンターの福田・村田です。 我々は、クラウドとオンプレミスそれぞれの検証環境を所有しており、オンプレミス製品やそれらをクラウドと組み合わせたハイブリッドクラウドの検証をおこなっています。 チームでの活動を続ける中で検証環境が拡大し、セキュリティ強化やコンプライアンス対応、DevOps 環境の整備がますます重要になってきました。 その一環でサーバーやネットワーク機器へのログイン認証を Entra ID に一元化する取り組みを行いました。 本記事では、その背景や技術選定の判断、運用して得られた知見を共有します。 具体的には以下のような内容を扱います。 SSH 公開鍵の手動配布をやめて、Entra ID 認証ベースの一時鍵(opkssh)に移行した話 ネットワーク機器のログインを FreeRADIUS + privacyIDEA で Entra ID に寄せた話 実際に運用してみて分かったハマりどころ(24時間で鍵が切れる、パスワード+OTP 連結入力、など) これまでの構成と課題 全体の構成と採用した方式 サーバーログイン — opkssh 導入前の課題 opkssh を選んだ経緯 ログインの流れ ネットワーク機器ログイン — FreeRADIUS + privacyIDEA RADIUS (FreeRADIUS) 採用の背景 RADIUS の MFA 提供 (privacyIDEA) FreeRADIUS + privacyIDEA 連携による MFA + RADIUS 認証 やってみての所感 よかったこと 「誰がログインしたか」が追えるようになった サーバー・ネットワーク機器追加時の作業が減った 注意が必要だったこと・ハマりどころ opkssh の一時鍵は24時間で失効する パスワード + OTP の連結入力は初見で戸惑う まとめ これまでの構成と課題 これまで我々の環境では、サーバーやネットワーク機器ごとに異なるログイン方式が採用されていました。 例えば、サーバーには SSH 公開鍵認証をしている一方、ネットワーク機器では SSH パスワード認証やコンソール接続をしている、という状態です。 その結果、以下のような課題が顕在化していました。 SSH 鍵ペア・パスワード・アカウントなどの認証情報の管理が煩雑になり、属人化する 機器ごとに運用手順が異なり、運用コストが増加する 誰が・いつ・どの機器にログインしたかを把握しづらく、トラブルシューティングにかかる時間が長期化する こうした課題を解決すべく、原因であったログイン方式の改修に取り組みました。 具体的には、次の2点です。 サーバーおよびネットワーク機器のログイン認証を一元化する 誰が・いつ・どの機器にログインしたかを追跡可能にする また、多要素認証(Multi-Factor Authentication, MFA)の導入による認証の強化も合わせて目指しました。 我々の環境ではすでに、Web サービスのログインを Microsoft Entra ID (Entra ID, 旧 Azure Active Directory)に一元化し、サインインログも Entra ID で追跡可能にした実績がありました。 また、Entra ID には MFA の機能が備わっています。 そこで、機器へのログインも Entra ID に寄せることで、Web サービスと同様に認証の一元化、ログイン履歴の追跡、そして MFA の導入をまとめて実現できると判断しました。 全体の構成と採用した方式 今回、すべての機器で認証の起点を Entra ID にしました。 最終的に SSH でログインする点は共通していますが、サーバーとネットワーク機器で採用した方式が異なるため、それぞれ解説します。 サーバーログイン — opkssh 導入前の課題 もともとサーバーへのログインには SSH の公開鍵認証を利用していました。利用者ごとに SSH 鍵ペアを発行し、各サーバーに SSH 公開鍵を配置する運用です。 検証環境の拡大に伴いサーバー台数が増えるにつれて鍵管理において以下の運用負荷が課題になっていきました。 SSH 公開鍵を配布する手間が増える どのサーバーにどの鍵が残っているか把握しづらくなる opkssh を選んだ経緯 この課題に対して導入したのが OpenPubkey SSH(opkssh) です。 2025年3月に Cloudflare がオープンソース化を発表し、Linux Foundation の OpenPubkey プロジェクト傘下で開発されている OSS です。 OpenID Connect(OIDC)対応の IdP と連携して SSH ログインを提供する仕組みです。 opkssh の導入にあたって、SSH のプロトコル自体に手を入れる必要はありません。 サーバー側の sshd 設定ファイルと opkssh 用の設定ファイルを追加・変更することで導入できます(下記)。 この設定内容はサーバー固有のものではないため、サーバーが増えた場合でも容易に展開が可能です。 また、opkssh は認証結果を IdP と連携して利用するため、MFA についても SSH 側で個別に実装する必要はありません。 このような理由から、opkssh を採用しました。 # /etc/ssh/sshd_config ## 受信した SSH 公開鍵を opkssh が検証する AuthorizedKeysCommand /usr/local/bin/opkssh verify %u %k %t AuthorizedKeysCommandUser root # /etc/opk/providers ## IdP として Entra ID を指定 ### tenant-id は Entra ID のテナントID ### clinet-id は Azure のクライアントID https://login.microsoftonline.com/{{ tenant-id }}/v2.0 {{ client-id }} # /etc/opk/auth_id ## IdP が引き継げるサーバ内のユーザを指定 ### user-name は、サーバ内のユーザ名 ### email-address は、Entra ID アカウントのメールアドレス ### tenant-id は Entra ID のテナントID {{ user-name }} {{ email-address }} https://login.microsoftonline.com/{{ tenant-id }}/v2.0 ログインの流れ opkssh 導入後のサーバーログインは、以下の流れになります。 利用者が opkssh login を実行すると、ブラウザで Entra ID のログイン画面が開きます Entra ID での認証(MFA 含む)が成功すると、一時的な SSH 鍵ペアが配布されます。SSH 公開鍵には、鍵の有効期限や Entra ID のユーザ情報を含む PK Token が埋め込まれています 通常の SSH ログインと同様にサーバーにログインします サーバー側では sshd_config に設定した検証ツールが PK Token を検証し、問題なければログインが許可されます この構成により、SSH ログインの認証が Entra ID に集約され、MFA を含む認証ポリシーを IdP 側で統一的に管理できるようになりました。 一次的な SSH 鍵ペアは短期間で失効するため、従来の、各サーバーに SSH 公開鍵を配置する運用も解消されました。 ネットワーク機器ログイン — FreeRADIUS + privacyIDEA 続いて、ネットワーク機器における Entra ID ログインおよび MFA の提供について解説します。 RADIUS (FreeRADIUS) 採用の背景 ネットワーク機器の認証一元化には代表的な選択肢として Terminal Access Controller Access-Control System Plus(TACACS+) や Remote Authentication Dial-In User Service(RADIUS) といった認証方法があります。 TACACS+ はコマンド単位の認可制御まで柔軟に設計できますが、その分、構築・運用の設計項目が多くなります。 さらに TACACS+ は構成・実装の自由度が高いのですが、設定や運用に関する事例が相対的に少なく、判断材料の収集に時間を要する印象でした。 一方 RADIUS は利用実績も豊富であり、運用ノウハウや設定例等も豊富に公開されていました。 さらに導入もシンプルにおこなえて幅広い機器がサポートしています。 そこで RADIUS を採用してログインを提供することにしました。 Entra ID による RADIUS の提供自体は Microsoft 公式が Windows Server の Network Policy Server を提供しています( 参考文献 )。 さらの Network Policy Server には Microsoft 公式として Azure MFA という MFA を導入する方式も提供していました( 参考文献 ) 。 最初はこの方法で RADIUS を提供できないか検証していたました。 しかし検証途中で以下の点が判明し、今回は採用を見送りました。 Windows Server の保守運用をしなければならないこと Network Policy Server とネットワーク機器による RADIUS ログインとの相性が悪いこと そこで他に RADIUS を提供する方法を検討しました。 その中でも特に柔軟な設定ができ、まとまった情報を得やすい FreeRADIUS に着目しました。 FreeRADIUS はプラグイン形式で RADIUS 認証を拡張する仕組みが存在し、 RADIUS 認証を実質的にプラグインにバイパスできます。 我々はこの点に着目し、 FreeRADIUS にさらに MFA を提供するシステムをプラグインを通して提供することで MFA + RADIUS の環境を実現できないかを検討しました。 RADIUS の MFA 提供 (privacyIDEA) RADIUS 認証は ID/パスワード認証を前提としたプロトコルです。 そのため MFA を導入するには追加の属性をいれたり、外部連携が必要になるという課題があります。 ですが FreeRADIUS であれば RADIUS 認証を拡張できるため、 MFA 認証も入れることができるだろうと予想しさまざまな MFA 認証システムを検討しました。 その中でも privacyIDEA は開発が活発であり、しかも FreeRADIUS と連携するためのプラグインを公式で提供していました。 コミュニティ規模が小さいためまとまった情報は少ないのですが、導入自体は公式自身で FreeRADIUS と連携するためのドキュメント を整備してくれているため、導入を簡単におこなえました。 privacyIDEA 自体は TOTP、 SMS、 E メール等豊富な MFA に対応しており、 REST API 経由でアカウントに MFA を提供できます。 その上 LDAP / Entra ID / RADIUS 等のさまざまなアカウントサービスとも連携する方法が用意されており、それらと連携して MFA を提供できます。 そこで今回はこの Entra ID、 FreeRADIUS との連携の容易さから privacyIDEA による RADIUS 認証 + MFA システムの提供をすすめました。 FreeRADIUS + privacyIDEA 連携による MFA + RADIUS 認証 具体的には以下のような流れで RADIUS 認証に MFA を提供しています。 機器官理者が FreeRADIUS に RADIUS クライアントとしてネットワーク機器を登録 ユーザーは事前に privacyIDEA の Web UI で TOTP トークンを登録(Authenticator アプリケーション等で QR コードを読み取り) ネットワーク機器に SSH ログインすると RADIUS 認証リクエストがFreeRADIUS に送られる FreeRADIUS は privacyIDEA に認証を委譲。privacyIDEA は Entra Domain Services(LDAPS)経由でパスワードを検証し、TOTP は自身に登録されたトークンと照合する パスワード・TOTP ともに正しければ認証成功 なお、ログイン時にユーザーが入力するのはパスワードと TOTP を連結した文字列です。 たとえばパスワードが mypassword で TOTP が 123456 なら、 mypassword123456 と入力します。 RADIUS プロトコルのパスワードフィールドが1つしかないため、privacyIDEA 側で末尾6桁を OTP として分離し、それぞれを検証する仕組みです。 これによって RADIUS 認証をするネットワーク機器からみるとパスワード認証にみえつつ実際には privacyIDEA 側でパスワード + MFA の検証をおこなうというシステムを構築できました。 FreeRADIUS のプラグイン拡張による柔軟性と privacyIDEA の MFA 提供によって、本来であれば ID/パスワード認証が基本となる RADIUS に対して Entra ID アカウントを提供しつつ MFA も提供でき、実現したかったネットワーク機器への MFA + Entra ID ログインを達成できました。 やってみての所感 構成や方式の話が続いたので、ここからは実際に運用して感じたことを共有します。 よかったこと 「誰がログインしたか」が追えるようになった 以前は、誰が・いつ・どの機器にログインしたかを特定できない状況でした。 Entra ID に認証を一元化したことですべてのログインが個人のアイデンティティへと紐づくようになり、トラブル時の調査や監査対応も楽になりました。 サーバー・ネットワーク機器追加時の作業が減った 以前はサーバー・ネットワーク機器を1台追加するたびに、その機器に対してメンバー個々がログインできるよう、ユーザや認証情報の設定する必要がありました。 しかし、opkssh や FreeRADIUS + privacyIDEA の導入後は、メンバー個々の設定は不要になり、サーバー・ネットワーク機器側に初期設定を一度だけすれば十分になりました。 これにより、機器追加時の作業量は人数に依存せず、機器台数にのみ依存する形となりました。 検証環境が頻繁に拡大する状況であっても、運用負荷の増加を抑えられるようになりました。 注意が必要だったこと・ハマりどころ opkssh の一時鍵は24時間で失効する opkssh が生成する SSH 鍵ペアはデフォルト24時間の有効期限があります。長時間の作業や翌日にまたがるメンテナンスでは途中で鍵が失効し、opkssh login の再実行が必要です。 「急にログインできなくなった」という問い合わせを防ぐため、利用者への事前周知は必須でした。 パスワード + OTP の連結入力は初見で戸惑う RADIUS + privacyIDEA の構成では、ログイン時にパスワードと TOTP を連結して入力します(例: mypassword123456 )。 慣れれば問題ありませんが、初めて使うメンバーからは「パスワード欄に何を入れればいいのか分からない」という声が上がりました。導入前にログイン手順書を用意してチームに共有してから展開したのは正解でした。 まとめ 本記事では、サーバーに opkssh、ネットワーク機器に FreeRADIUS + privacyIDEA を採用し、ログイン認証の起点を Entra ID に寄せた取り組みを紹介しました。 鍵配布や個別アカウントの棚卸を削減し、ログインの追跡性を向上させることができました。 今後も拡大が見込まれる検証環境において、セキュリティや運用を考慮した体制整備を進めていきます。
こんにちは、SCSKでAWSの内製化支援『 テクニカルエスコートサービス 』を担当している貝塚です。 先日、顧客内製開発中のWebシステムの認証について、こんなご相談をいただきました。 社内のAD(Active Directory)で管理しているユーザーIDとパスワードで、クラウド上のWebアプリケーションにログインさせたい ただし、社員IDはメールアドレスではなくsAMAccountName(Active Directoryでユーザーを一意に識別するログイン名属性。例: testuser01)を使用している 本記事では、AWS上にADFS(Active Directory Federation Services)を構築し、Amazon Cognitoと連携してSAML 2.0ベースのSSO認証を実現する構成について説明します。さらに、ADFSをインターネットに安全に公開するため、Web Application Proxy(WAP)を導入した最終的なアーキテクチャも紹介します。 CognitoとADを連携させるには Cognito User Poolは外部のSAML 2.0 Identity Provider(IdP)と連携することで、SSOを実現できます。 しかし、AD自体はLDAP/Kerberosベースのディレクトリサービスであり、SAML IdPの機能を持ちません。そのため、CognitoとADを連携させるには、ADの認証情報をSAML Assertionに変換して発行できるIdPを別途用意する必要があります。 AWS Directory Service AD Connectorは使えるか このケースでAWS Directory Service AD Connectorは使えるでしょうか?AD ConnectorはオンプレミスADへのプロキシとして動作し、AWSマネジメントコンソールへのSSO、Amazon WorkSpaces、RDSのWindows認証などに利用できます。 しかし、AD ConnectorはSAML IdPではありません。Cognito User PoolのSAMLフェデレーションに必要なSAMLメタデータやSAML Assertionを発行する機能を持たないため、CognitoとADの連携には使用できないということが分かります。 結論: AWS上にADFSを構築する AD Connectorの検討を経て、AWS上にADFSサーバーを構築する方式を選択しました。 ADFSはWindows Serverの役割(ロール)の一つで、ADの認証情報をSAML 2.0やWS-Federationといったフェデレーションプロトコルで外部に提供します。つまり、ADをSAML IdPとして機能させるのがADFSの中核的な役割です。 今回のケースでは、このADFSをSAML IdPとしてCognitoと連携させます。 全体アーキテクチャの検討 ADFSとCognitoを連携させるアーキテクチャを検討します。 当初案: ALB → ADFS → AD 当初は、Internet → ALB → ADFS → AD という構成を検討していました。ADFSサーバはSSL証明書を持つので、ALBでHTTPSを終端した後、再びHTTPSでADFSサーバと通信する想定です。ALBにAWS WAFをアタッチすればセキュリティ面も安心です。 ALBからADFSへの通信 ALBはHTTPSターゲットグループを使えばバックエンドへの再暗号化自体は可能です。 しかし、ADFSはSNI(Server Name Indication)の処理が特殊で、ALBのようなL7ロードバランサーの背後に配置すると、ヘルスチェックやプロキシ動作が正しく機能しないことが分かりました。 修正案: NLB → ADFS → AD そこで、NLBを使い Internet → NLB → ADFS → AD という 構成に変更しました。 NLBはL4(TCP)パススルーで動作するため、TLS終端はADFSが行います。SNIの問題も発生しません。ただし、NLBにはAWS WAFを直接アタッチできないという制約があります。 暫定アーキテクチャ インターネットからADFSへの通信のセキュリティ確保は別途考えることにして、まずは以下の構成としました。 こちらは完全に検証用の構成であることにご注意ください。実際はウェブサイトと同じところ(またはその「奥」)に認証サイトがあるべきですが、Cognito、ADFS、ウェブサーバ間の通信要件が明確になるようにあえてVPCを分けました。また実際には可用性要件も踏まえて構成する必要があります。 認証データフロー 認証データの流れとしては クライアントPC → Cognito → ADFS → AD → ADFS → Cognito → Client ですが、実際の通信はクライアントPCのブラウザがCognitoとADFSの間をHTTPリダイレクトで仲介する形になります。CognitoとADFSの間に直接の通信は発生しません。 通信フロー 具体的な通信フローは以下のとおりです。 Client → ALB(Webアプリ): Webアプリにアクセス ALB → Client: Cognitoの認証エンドポイントへリダイレクト(HTTP 302) Client → Cognito: 認証リクエスト Cognito → Client: ADFS(SAML IdP)へリダイレクト(HTTP 302、SAMLRequestを含む) Client → ADFS: SAMLRequestを送信(ADFSのログイン画面が表示される) ADFS ←→ AD: ADFSがADに対してユーザー認証を実行(サーバー間通信) ADFS → Client: SAMLResponseを返却(認証成功時) Client → Cognito: SAMLResponseをCognitoのACSエンドポイントにPOST Cognito → Client: 認証トークンを発行し、ALBのコールバックURLへリダイレクト Client → ALB(Webアプリ): 認証済みとしてWebアプリにアクセス 重要なのは、CognitoとADFSの間に直接の通信は発生しないという点です。唯一の例外は、CognitoがADFSのメタデータXMLを取得する際のみ、Cognito → ADFSの直接通信が発生します。ただしメタデータXML取得は後述の通り構築時にADFSで出力されたメタデータをCognitoにアップロードすることで代替が可能です。 自己署名証明書とCognitoのメタデータ取得 今回の検証環境ではADFSに信頼できる第三者機関認証局(CA)の証明書は準備できず自己署名証明書を使用しました。 CognitoがADFSのメタデータURLにアクセスする方式では自己署名証明書のSSL検証に失敗するためエラーが発生し連携ができません。この問題を回避するため、メタデータのファイルをCognitoにアップロードする方式を採用しました。ADFSのメタデータXMLをS3バケットに格納し登録するようにしています。 sAMAccountNameでの認証 今回の要件では、メールアドレスではなくsAMAccountName(Active Directoryでユーザーを一意に識別するログイン名属性)でログインする必要がありました。 ADFSの Claim Rulesで sAMAccountNameを Name IDとして発行するよう設定し、Cognito側でこの値をユーザー識別子として使用しています。ウェブサイト側では認証済みリクエストの x-amzn-oidc-data ヘッダー(JWT形式)をデコードすることで、sAMAccountName を取得できます。 アーキテクチャ修正:Web Application Proxy (WAP)の導入 上記構成で一旦動作を確認した後、インターネットにさらされることになったADFSのセキュリティ強化を検討することになりました。 Web Application Proxy (WAP)はMicrosoft推奨のADFS公開方式で、ADFSのSNI処理を正しくハンドリングするリバースプロキシです。WAPはドメイン非参加で運用するため、万が一WAPが侵害されてもADドメインへの影響を限定できます。WAPが侵害された場合は、ADFS側で「プロキシ信頼の取り消し」を実行することで、侵害されたプロキシからのリクエストを即座に拒否できます。 本案件はクライアントPCのグローバルIPアドレスが限定できるためセキュリティグループに頼る選択肢もありましたが、ネットワーク層以外の防御を組み合わせるという視点からWAPを導入する方向で検討を進めました。とはいえ顧客運用対象のEC2サーバが増えるため手放しでは喜べない結果となりました。 まとめ 本記事では、CognitoとオンプレミスADをSAML連携するために、AWS上にADFSを構築し、WAPを経由して安全にインターネットに公開する構成を紹介しました。 ADFSの前にALBを置けないことからNLBを採用し、さらにWAPを導入することで、セキュリティと可用性を両立しています。自己署名証明書環境でのCognitoメタデータ取得の回避策や、sAMAccountNameによる認証の実現方法など、実装時に直面した課題とその解決策も共有しました。これらのノウハウが皆様のお役に立てば幸いです。 実装手順 本検証構成をデプロイするためのCloudFormationテンプレートとコマンドを以下に掲載します。 デプロイは5つのCloudFormationテンプレートと、テンプレート以外で必要な対応手順から構成されます。 [Step 1] CloudFormation: スタック1 (擬似オンプレミスVPC) デプロイ [Step 2] CloudFormation: スタック2 (AD/ADFSサーバ) デプロイ [自動実行] UserDataスクリプト(ADドメイン作成、ADFSドメイン参加) [Step 3] CloudFormation: スタック3 (WAP) デプロイ [Step 4] SSM Run Command: ADテストユーザー作成 [Step 5] 手動: ADFSファーム作成(Fleet Manager 経由) [Step 6] ADFSメタデータXMLのS3アップロード [Step 7] CloudFormation: スタック4 (ウェブアプリ用VPC) デプロイ [Step 8] CloudFormation: スタック5 (Cognito/ウェブアプリ) デプロイ [Step 9] WAP構成(Fleet Manager 経由) Step 1: スタック1(擬似オンプレミスVPC)のデプロイ コマンド aws cloudformation create-stack \ --stack-name adfs-pseudo-vpc \ --template-body file://cfn-pseudo-onprem-vpc.yaml \ --parameters file://cfn-pseudo-onprem-vpc_adfs-pseudo-vpc.json \ --region ap-northeast-1 cfn-pseudo-onprem-vpc.yaml AWSTemplateFormatVersion: '2010-09-09' Description: 'Stack 1: Pseudo On-Premises VPC with NLB for ADFS access' Parameters: VpcCidr: Type: String Description: 'CIDR block for the VPC' Default: '10.0.0.0/16' PublicSubnet1aCidr: Type: String Description: 'CIDR block for the public subnet in AZ 1a' Default: '10.0.10.0/24' PublicSubnet1cCidr: Type: String Description: 'CIDR block for the public subnet in AZ 1c' Default: '10.0.11.0/24' WapSubnet1aCidr: Type: String Description: 'CIDR block for the WAP subnet in AZ 1a' Default: '10.0.20.0/24' WapSubnet1cCidr: Type: String Description: 'CIDR block for the WAP subnet in AZ 1c' Default: '10.0.21.0/24' InternalNlbSubnet1aCidr: Type: String Description: 'CIDR block for the Internal NLB subnet in AZ 1a' Default: '10.0.30.0/24' InternalNlbSubnet1cCidr: Type: String Description: 'CIDR block for the Internal NLB subnet in AZ 1c' Default: '10.0.31.0/24' PrivateSubnet1aCidr: Type: String Description: 'CIDR block for the private subnet in AZ 1a' Default: '10.0.40.0/24' PrivateSubnet1cCidr: Type: String Description: 'CIDR block for the private subnet in AZ 1c' Default: '10.0.41.0/24' Resources: # VPC PseudoOnPremVpc: Type: AWS::EC2::VPC Properties: CidrBlock: !Ref VpcCidr EnableDnsSupport: true EnableDnsHostnames: true Tags: - Key: Cost Value: cognitoadfs - Key: Name Value: !Sub '${AWS::StackName}-vpc' # Internet Gateway InternetGateway: Type: AWS::EC2::InternetGateway Properties: Tags: - Key: Cost Value: cognitoadfs - Key: Name Value: !Sub '${AWS::StackName}-igw' InternetGatewayAttachment: Type: AWS::EC2::VPCGatewayAttachment Properties: VpcId: !Ref PseudoOnPremVpc InternetGatewayId: !Ref InternetGateway # Public Subnet 1a PublicSubnet1a: Type: AWS::EC2::Subnet Properties: VpcId: !Ref PseudoOnPremVpc CidrBlock: !Ref PublicSubnet1aCidr AvailabilityZone: 'ap-northeast-1a' MapPublicIpOnLaunch: true Tags: - Key: Cost Value: cognitoadfs - Key: Name Value: !Sub '${AWS::StackName}-public-subnet-1a' # Public Subnet 1c PublicSubnet1c: Type: AWS::EC2::Subnet Properties: VpcId: !Ref PseudoOnPremVpc CidrBlock: !Ref PublicSubnet1cCidr AvailabilityZone: 'ap-northeast-1c' MapPublicIpOnLaunch: true Tags: - Key: Cost Value: cognitoadfs - Key: Name Value: !Sub '${AWS::StackName}-public-subnet-1c' # WAP Subnet 1a WapSubnet1a: Type: AWS::EC2::Subnet Properties: VpcId: !Ref PseudoOnPremVpc CidrBlock: !Ref WapSubnet1aCidr AvailabilityZone: 'ap-northeast-1a' MapPublicIpOnLaunch: false Tags: - Key: Cost Value: cognitoadfs - Key: Name Value: !Sub '${AWS::StackName}-wap-subnet-1a' # WAP Subnet 1c WapSubnet1c: Type: AWS::EC2::Subnet Properties: VpcId: !Ref PseudoOnPremVpc CidrBlock: !Ref WapSubnet1cCidr AvailabilityZone: 'ap-northeast-1c' MapPublicIpOnLaunch: false Tags: - Key: Cost Value: cognitoadfs - Key: Name Value: !Sub '${AWS::StackName}-wap-subnet-1c' # Internal NLB Subnet 1a InternalNlbSubnet1a: Type: AWS::EC2::Subnet Properties: VpcId: !Ref PseudoOnPremVpc CidrBlock: !Ref InternalNlbSubnet1aCidr AvailabilityZone: 'ap-northeast-1a' MapPublicIpOnLaunch: false Tags: - Key: Cost Value: cognitoadfs - Key: Name Value: !Sub '${AWS::StackName}-internal-nlb-subnet-1a' # Internal NLB Subnet 1c InternalNlbSubnet1c: Type: AWS::EC2::Subnet Properties: VpcId: !Ref PseudoOnPremVpc CidrBlock: !Ref InternalNlbSubnet1cCidr AvailabilityZone: 'ap-northeast-1c' MapPublicIpOnLaunch: false Tags: - Key: Cost Value: cognitoadfs - Key: Name Value: !Sub '${AWS::StackName}-internal-nlb-subnet-1c' # Private Subnet 1a PrivateSubnet1a: Type: AWS::EC2::Subnet Properties: VpcId: !Ref PseudoOnPremVpc CidrBlock: !Ref PrivateSubnet1aCidr AvailabilityZone: 'ap-northeast-1a' MapPublicIpOnLaunch: false Tags: - Key: Cost Value: cognitoadfs - Key: Name Value: !Sub '${AWS::StackName}-private-subnet-1a' # Private Subnet 1c PrivateSubnet1c: Type: AWS::EC2::Subnet Properties: VpcId: !Ref PseudoOnPremVpc CidrBlock: !Ref PrivateSubnet1cCidr AvailabilityZone: 'ap-northeast-1c' MapPublicIpOnLaunch: false Tags: - Key: Cost Value: cognitoadfs - Key: Name Value: !Sub '${AWS::StackName}-private-subnet-1c' # Elastic IP for NAT Gateway NatGatewayEIP: Type: AWS::EC2::EIP DependsOn: InternetGatewayAttachment Properties: Domain: vpc Tags: - Key: Cost Value: cognitoadfs - Key: Name Value: !Sub '${AWS::StackName}-nat-eip' # NAT Gateway NatGateway: Type: AWS::EC2::NatGateway Properties: AllocationId: !GetAtt NatGatewayEIP.AllocationId SubnetId: !Ref PublicSubnet1a Tags: - Key: Cost Value: cognitoadfs - Key: Name Value: !Sub '${AWS::StackName}-nat-gateway' # Public Route Table PublicRouteTable: Type: AWS::EC2::RouteTable Properties: VpcId: !Ref PseudoOnPremVpc Tags: - Key: Cost Value: cognitoadfs - Key: Name Value: !Sub '${AWS::StackName}-public-rt' PublicRoute: Type: AWS::EC2::Route DependsOn: InternetGatewayAttachment Properties: RouteTableId: !Ref PublicRouteTable DestinationCidrBlock: '0.0.0.0/0' GatewayId: !Ref InternetGateway PublicSubnet1aRouteTableAssociation: Type: AWS::EC2::SubnetRouteTableAssociation Properties: SubnetId: !Ref PublicSubnet1a RouteTableId: !Ref PublicRouteTable PublicSubnet1cRouteTableAssociation: Type: AWS::EC2::SubnetRouteTableAssociation Properties: SubnetId: !Ref PublicSubnet1c RouteTableId: !Ref PublicRouteTable # Private Route Table PrivateRouteTable: Type: AWS::EC2::RouteTable Properties: VpcId: !Ref PseudoOnPremVpc Tags: - Key: Cost Value: cognitoadfs - Key: Name Value: !Sub '${AWS::StackName}-private-rt' PrivateRoute: Type: AWS::EC2::Route Properties: RouteTableId: !Ref PrivateRouteTable DestinationCidrBlock: '0.0.0.0/0' NatGatewayId: !Ref NatGateway PrivateSubnet1aRouteTableAssociation: Type: AWS::EC2::SubnetRouteTableAssociation Properties: SubnetId: !Ref PrivateSubnet1a RouteTableId: !Ref PrivateRouteTable PrivateSubnet1cRouteTableAssociation: Type: AWS::EC2::SubnetRouteTableAssociation Properties: SubnetId: !Ref PrivateSubnet1c RouteTableId: !Ref PrivateRouteTable # WAP Route Table WapRouteTable: Type: AWS::EC2::RouteTable Properties: VpcId: !Ref PseudoOnPremVpc Tags: - Key: Cost Value: cognitoadfs - Key: Name Value: !Sub '${AWS::StackName}-wap-rt' WapRoute: Type: AWS::EC2::Route Properties: RouteTableId: !Ref WapRouteTable DestinationCidrBlock: '0.0.0.0/0' NatGatewayId: !Ref NatGateway WapSubnet1aRouteTableAssociation: Type: AWS::EC2::SubnetRouteTableAssociation Properties: SubnetId: !Ref WapSubnet1a RouteTableId: !Ref WapRouteTable WapSubnet1cRouteTableAssociation: Type: AWS::EC2::SubnetRouteTableAssociation Properties: SubnetId: !Ref WapSubnet1c RouteTableId: !Ref WapRouteTable # Internal NLB Route Table (VPC local only, no internet route) InternalNlbRouteTable: Type: AWS::EC2::RouteTable Properties: VpcId: !Ref PseudoOnPremVpc Tags: - Key: Cost Value: cognitoadfs - Key: Name Value: !Sub '${AWS::StackName}-internal-nlb-rt' InternalNlbSubnet1aRouteTableAssociation: Type: AWS::EC2::SubnetRouteTableAssociation Properties: SubnetId: !Ref InternalNlbSubnet1a RouteTableId: !Ref InternalNlbRouteTable InternalNlbSubnet1cRouteTableAssociation: Type: AWS::EC2::SubnetRouteTableAssociation Properties: SubnetId: !Ref InternalNlbSubnet1c RouteTableId: !Ref InternalNlbRouteTable # Security Group for AD ADSecurityGroup: Type: AWS::EC2::SecurityGroup Properties: GroupDescription: 'Security group for Active Directory server' VpcId: !Ref PseudoOnPremVpc SecurityGroupEgress: - IpProtocol: -1 CidrIp: '0.0.0.0/0' Tags: - Key: Cost Value: cognitoadfs - Key: Name Value: !Sub '${AWS::StackName}-ad-sg' # Security Group for ADFS ADFSSecurityGroup: Type: AWS::EC2::SecurityGroup Properties: GroupDescription: 'Security group for ADFS server' VpcId: !Ref PseudoOnPremVpc SecurityGroupEgress: - IpProtocol: -1 CidrIp: '0.0.0.0/0' Tags: - Key: Cost Value: cognitoadfs - Key: Name Value: !Sub '${AWS::StackName}-adfs-sg' # Security Group Ingress Rules (defined separately to avoid circular dependency) ADSecurityGroupIngressFromADFS: Type: AWS::EC2::SecurityGroupIngress Properties: GroupId: !Ref ADSecurityGroup IpProtocol: -1 SourceSecurityGroupId: !Ref ADFSSecurityGroup Description: 'Allow all traffic from ADFS Security Group' ADFSSecurityGroupIngressFromAD: Type: AWS::EC2::SecurityGroupIngress Properties: GroupId: !Ref ADFSSecurityGroup IpProtocol: -1 SourceSecurityGroupId: !Ref ADSecurityGroup Description: 'Allow all traffic from AD Security Group' ADFSSecurityGroupIngressAllTraffic: Type: AWS::EC2::SecurityGroupIngress Properties: GroupId: !Ref ADFSSecurityGroup IpProtocol: -1 CidrIp: '0.0.0.0/0' Description: 'Allow all traffic (test environment)' # Security Group for WAP WapSecurityGroup: Type: AWS::EC2::SecurityGroup Properties: GroupDescription: 'Security group for WAP server' VpcId: !Ref PseudoOnPremVpc SecurityGroupIngress: - IpProtocol: -1 CidrIp: '0.0.0.0/0' Description: 'Allow all traffic (test environment)' SecurityGroupEgress: - IpProtocol: -1 CidrIp: '0.0.0.0/0' Tags: - Key: Cost Value: cognitoadfs - Key: Name Value: !Sub '${AWS::StackName}-wap-sg' # External Network Load Balancer ExternalNetworkLoadBalancer: Type: AWS::ElasticLoadBalancingV2::LoadBalancer Properties: Name: !Sub '${AWS::StackName}-ext-nlb' Type: network Scheme: internet-facing Subnets: - !Ref PublicSubnet1a - !Ref PublicSubnet1c Tags: - Key: Cost Value: cognitoadfs - Key: Name Value: !Sub '${AWS::StackName}-ext-nlb' # External NLB Target Group (WAP Target) ExternalNlbTargetGroup: Type: AWS::ElasticLoadBalancingV2::TargetGroup Properties: Name: !Sub '${AWS::StackName}-wap-tg' VpcId: !Ref PseudoOnPremVpc Port: 443 Protocol: TCP TargetType: instance HealthCheckProtocol: TCP HealthCheckPort: '443' TargetGroupAttributes: - Key: preserve_client_ip.enabled Value: 'false' Tags: - Key: Cost Value: cognitoadfs - Key: Name Value: !Sub '${AWS::StackName}-wap-tg' # External NLB Listener ExternalNlbListener: Type: AWS::ElasticLoadBalancingV2::Listener Properties: LoadBalancerArn: !Ref ExternalNetworkLoadBalancer Port: 443 Protocol: TCP DefaultActions: - Type: forward TargetGroupArn: !Ref ExternalNlbTargetGroup # Internal Network Load Balancer InternalNetworkLoadBalancer: Type: AWS::ElasticLoadBalancingV2::LoadBalancer Properties: Name: !Sub '${AWS::StackName}-int-nlb' Type: network Scheme: internal Subnets: - !Ref InternalNlbSubnet1a - !Ref InternalNlbSubnet1c Tags: - Key: Cost Value: cognitoadfs - Key: Name Value: !Sub '${AWS::StackName}-int-nlb' # Internal NLB Target Group (ADFS Target) InternalNlbTargetGroup: Type: AWS::ElasticLoadBalancingV2::TargetGroup Properties: Name: !Sub '${AWS::StackName}-adfs-tg' VpcId: !Ref PseudoOnPremVpc Port: 443 Protocol: TCP TargetType: instance HealthCheckProtocol: TCP HealthCheckPort: '443' TargetGroupAttributes: - Key: preserve_client_ip.enabled Value: 'false' Tags: - Key: Cost Value: cognitoadfs - Key: Name Value: !Sub '${AWS::StackName}-adfs-tg' # Internal NLB Listener InternalNlbListener: Type: AWS::ElasticLoadBalancingV2::Listener Properties: LoadBalancerArn: !Ref InternalNetworkLoadBalancer Port: 443 Protocol: TCP DefaultActions: - Type: forward TargetGroupArn: !Ref InternalNlbTargetGroup Outputs: VpcId: Description: 'VPC ID' Value: !Ref PseudoOnPremVpc Export: Name: !Sub '${AWS::StackName}-VpcId' PublicSubnet1aId: Description: 'Public Subnet 1a ID' Value: !Ref PublicSubnet1a Export: Name: !Sub '${AWS::StackName}-PublicSubnet1aId' PrivateSubnet1aId: Description: 'Private Subnet 1a ID' Value: !Ref PrivateSubnet1a Export: Name: !Sub '${AWS::StackName}-PrivateSubnet1aId' WapSubnet1aId: Description: 'WAP Subnet 1a ID' Value: !Ref WapSubnet1a Export: Name: !Sub '${AWS::StackName}-WapSubnet1aId' ADSecurityGroupId: Description: 'AD Security Group ID' Value: !Ref ADSecurityGroup Export: Name: !Sub '${AWS::StackName}-ADSecurityGroupId' ADFSSecurityGroupId: Description: 'ADFS Security Group ID' Value: !Ref ADFSSecurityGroup Export: Name: !Sub '${AWS::StackName}-ADFSSecurityGroupId' WapSecurityGroupId: Description: 'WAP Security Group ID' Value: !Ref WapSecurityGroup Export: Name: !Sub '${AWS::StackName}-WapSecurityGroupId' ExternalNlbTargetGroupArn: Description: 'External NLB Target Group ARN' Value: !Ref ExternalNlbTargetGroup Export: Name: !Sub '${AWS::StackName}-ExternalNlbTargetGroupArn' InternalNlbTargetGroupArn: Description: 'Internal NLB Target Group ARN' Value: !Ref InternalNlbTargetGroup Export: Name: !Sub '${AWS::StackName}-InternalNlbTargetGroupArn' ExternalNlbDnsName: Description: 'External NLB DNS Name' Value: !GetAtt ExternalNetworkLoadBalancer.DNSName Export: Name: !Sub '${AWS::StackName}-ExternalNlbDnsName' cfn-pseudo-onprem-vpc_adfs-pseudo-vpc.json [ { "ParameterKey": "VpcCidr", "ParameterValue": "10.0.0.0/16" }, { "ParameterKey": "PublicSubnet1aCidr", "ParameterValue": "10.0.10.0/24" }, { "ParameterKey": "PublicSubnet1cCidr", "ParameterValue": "10.0.11.0/24" }, { "ParameterKey": "WapSubnet1aCidr", "ParameterValue": "10.0.20.0/24" }, { "ParameterKey": "WapSubnet1cCidr", "ParameterValue": "10.0.21.0/24" }, { "ParameterKey": "InternalNlbSubnet1aCidr", "ParameterValue": "10.0.30.0/24" }, { "ParameterKey": "InternalNlbSubnet1cCidr", "ParameterValue": "10.0.31.0/24" }, { "ParameterKey": "PrivateSubnet1aCidr", "ParameterValue": "10.0.40.0/24" }, { "ParameterKey": "PrivateSubnet1cCidr", "ParameterValue": "10.0.41.0/24" } ] Step 2: スタック2(AD/ADFS)のデプロイ ADサーバ、ADFSサーバを作成します。ADFSDomainNameに、ADFSを外部公開するためのFQDNを指定し、Route 53でそのDNS名をStep1で作成される外側NLBのFQDNに向けるレコードを作成しておいてください。 コマンド aws cloudformation create-stack \ --stack-name adfs-ad \ --template-body file://cfn-ad-adfs.yaml \ --parameters file://cfn-ad-adfs_adfs-ad.json \ --capabilities CAPABILITY_IAM \ --region ap-northeast-1 cfn-ad-adfs.yaml AWSTemplateFormatVersion: '2010-09-09' Description: 'Stack 2: AD/ADFS Servers for Cognito ADFS SSO Integration' Parameters: PseudoOnPremVpcStackName: Type: String Description: 'Name of the Pseudo On-Premises VPC stack' Default: 'adfs-pseudo-vpc' ADInstanceType: Type: String Description: 'Instance type for AD server' Default: 't3.medium' AllowedValues: - t3.medium - t3.large - t3.xlarge ADFSInstanceType: Type: String Description: 'Instance type for ADFS server' Default: 't3.medium' AllowedValues: - t3.medium - t3.large - t3.xlarge ADAdminPassword: Type: String Description: 'Administrator password for AD domain' NoEcho: true MinLength: 8 ADDomainName: Type: String Description: 'AD domain name (e.g., corp.local)' Default: 'corp.local' ADFSDomainName: Type: String Description: 'ADFS federation service name (e.g., adfs.example.com)' Default: 'adfs.example.com' WindowsAMI: Type: AWS::SSM::Parameter::Value Description: 'Latest Windows Server 2022 AMI from SSM Parameter Store' Default: '/aws/service/ami-windows-latest/Windows_Server-2022-English-Full-Base' Resources: # IAM Role for SSM SSMInstanceRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: ec2.amazonaws.com Action: 'sts:AssumeRole' ManagedPolicyArns: - 'arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore' Tags: - Key: Name Value: !Sub '${AWS::StackName}-ssm-role' - Key: Cost Value: 'cognitoadfs' # Instance Profile for SSM SSMInstanceProfile: Type: AWS::IAM::InstanceProfile Properties: Roles: - !Ref SSMInstanceRole # AD Server Instance ADInstance: Type: AWS::EC2::Instance Properties: ImageId: !Ref WindowsAMI InstanceType: !Ref ADInstanceType IamInstanceProfile: !Ref SSMInstanceProfile SubnetId: Fn::ImportValue: !Sub '${PseudoOnPremVpcStackName}-PrivateSubnet1aId' SecurityGroupIds: - Fn::ImportValue: !Sub '${PseudoOnPremVpcStackName}-ADSecurityGroupId' Tags: - Key: Name Value: !Sub '${AWS::StackName}-ad-server' - Key: Cost Value: 'cognitoadfs' PropagateTagsToVolumeOnCreation: true UserData: Fn::Base64: !Sub | # Get instance metadata $instanceId = Invoke-RestMethod -uri http://169.254.169.254/latest/meta-data/instance-id # Install AD DS role Install-WindowsFeature -Name AD-Domain-Services -IncludeManagementTools # Disable Network Level Authentication for RDP (for Fleet Manager access) Set-ItemProperty -Path 'HKLM:\System\CurrentControlSet\Control\Terminal Server\WinStations\RDP-Tcp' -Name 'UserAuthentication' -Value 0 # Set local Administrator password before creating domain # This password will become the domain Administrator password after domain creation $AdminUser = [ADSI]"WinNT://./Administrator,user" $AdminUser.SetPassword("${ADAdminPassword}") # Create AD domain (computer name will be the default AWS name) $SafeModePassword = ConvertTo-SecureString "${ADAdminPassword}" -AsPlainText -Force Install-ADDSForest -DomainName "${ADDomainName}" -DomainNetbiosName "CORP" -SafeModeAdministratorPassword $SafeModePassword -InstallDns -Force -NoRebootOnCompletion:$false # Server will restart automatically after domain creation # ADFS Server Instance ADFSInstance: Type: AWS::EC2::Instance DependsOn: ADInstance Properties: ImageId: !Ref WindowsAMI InstanceType: !Ref ADFSInstanceType IamInstanceProfile: !Ref SSMInstanceProfile SubnetId: Fn::ImportValue: !Sub '${PseudoOnPremVpcStackName}-PrivateSubnet1aId' SecurityGroupIds: - Fn::ImportValue: !Sub '${PseudoOnPremVpcStackName}-ADFSSecurityGroupId' Tags: - Key: Name Value: !Sub '${AWS::StackName}-adfs-server' - Key: Cost Value: 'cognitoadfs' PropagateTagsToVolumeOnCreation: true UserData: Fn::Base64: Fn::Sub: - | # Get instance metadata $instanceId = Invoke-RestMethod -uri http://169.254.169.254/latest/meta-data/instance-id # Install ADFS role Install-WindowsFeature -Name ADFS-Federation -IncludeManagementTools # Disable Network Level Authentication for RDP (for Fleet Manager access) Set-ItemProperty -Path 'HKLM:\System\CurrentControlSet\Control\Terminal Server\WinStations\RDP-Tcp' -Name 'UserAuthentication' -Value 0 # Generate self-signed certificate $cert = New-SelfSignedCertificate -DnsName "${ADFSDomainName}" -CertStoreLocation "cert:\LocalMachine\My" -KeySpec KeyExchange # Wait for AD server to be ready (20 minutes for domain creation and restart) Start-Sleep -Seconds 1200 # Get AD server IP $ADServerIP = "${ADInstancePrivateIp}" # Set DNS server to AD server # Get the first active network adapter (dynamically) $adapter = Get-NetAdapter | Where-Object {$_.Status -eq 'Up'} | Select-Object -First 1 Set-DnsClientServerAddress -InterfaceIndex $adapter.ifIndex -ServerAddresses $ADServerIP # Clear DNS cache after changing DNS server Clear-DnsClientCache # Wait for DNS resolution with retry (max 10 attempts, 10 seconds interval) $maxRetries = 10 $retryCount = 0 do { Start-Sleep -Seconds 10 $result = Resolve-DnsName ${ADDomainName} -ErrorAction SilentlyContinue $retryCount++ Write-Output "DNS resolution attempt $retryCount : $($result.IPAddress)" } while (-not $result -and $retryCount -lt $maxRetries) if (-not $result) { Write-Error "DNS resolution for ${ADDomainName} failed after $maxRetries attempts" exit 1 } # Join domain with error handling $password = ConvertTo-SecureString "${ADAdminPassword}" -AsPlainText -Force $credential = New-Object System.Management.Automation.PSCredential("CORP\Administrator", $password) try { Add-Computer -DomainName "${ADDomainName}" -Credential $credential -Force -ErrorAction Stop Write-Output "Domain join successful" } catch { Write-Error "Domain join failed: $_" exit 1 } Restart-Computer -Force - ADInstancePrivateIp: !GetAtt ADInstance.PrivateIp # Lambda execution role for NLB target registration NLBTargetRegistrationRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: lambda.amazonaws.com Action: 'sts:AssumeRole' ManagedPolicyArns: - 'arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole' Policies: - PolicyName: 'ELBv2TargetRegistration' PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - 'elasticloadbalancing:RegisterTargets' - 'elasticloadbalancing:DeregisterTargets' Resource: '*' Tags: - Key: Name Value: !Sub '${AWS::StackName}-nlb-target-reg-role' - Key: Cost Value: 'cognitoadfs' # Lambda function for NLB target registration NLBTargetRegistrationFunction: Type: AWS::Lambda::Function Properties: FunctionName: !Sub '${AWS::StackName}-nlb-target-reg' Runtime: python3.12 Handler: index.handler Role: !GetAtt NLBTargetRegistrationRole.Arn Timeout: 60 Code: ZipFile: | import json import boto3 import cfnresponse def handler(event, context): try: target_group_arn = event['ResourceProperties']['TargetGroupArn'] instance_id = event['ResourceProperties']['InstanceId'] client = boto3.client('elbv2') if event['RequestType'] in ['Create', 'Update']: client.register_targets( TargetGroupArn=target_group_arn, Targets=[{'Id': instance_id}] ) cfnresponse.send(event, context, cfnresponse.SUCCESS, {'InstanceId': instance_id}) elif event['RequestType'] == 'Delete': try: client.deregister_targets( TargetGroupArn=target_group_arn, Targets=[{'Id': instance_id}] ) except Exception: pass cfnresponse.send(event, context, cfnresponse.SUCCESS, {}) except Exception as e: print(f'Error: {e}') cfnresponse.send(event, context, cfnresponse.FAILED, {'Error': str(e)}) Tags: - Key: Name Value: !Sub '${AWS::StackName}-nlb-target-reg-func' - Key: Cost Value: 'cognitoadfs' # Custom resource to register ADFS instance to Internal NLB target group NLBTargetRegistration: Type: Custom::NLBTargetRegistration DependsOn: ADFSInstance Properties: ServiceToken: !GetAtt NLBTargetRegistrationFunction.Arn TargetGroupArn: Fn::ImportValue: !Sub '${PseudoOnPremVpcStackName}-InternalNlbTargetGroupArn' InstanceId: !Ref ADFSInstance Outputs: ADInstanceId: Description: 'AD Server Instance ID' Value: !Ref ADInstance Export: Name: !Sub '${AWS::StackName}-ADInstanceId' ADFSInstanceId: Description: 'ADFS Server Instance ID' Value: !Ref ADFSInstance Export: Name: !Sub '${AWS::StackName}-ADFSInstanceId' ADInstancePrivateIp: Description: 'AD Server Private IP Address' Value: !GetAtt ADInstance.PrivateIp Export: Name: !Sub '${AWS::StackName}-ADInstancePrivateIp' ADFSInstancePrivateIp: Description: 'ADFS Server Private IP Address' Value: !GetAtt ADFSInstance.PrivateIp Export: Name: !Sub '${AWS::StackName}-ADFSInstancePrivateIp' cfn-ad-adfs_adfs-ad.json [ { "ParameterKey": "PseudoOnPremVpcStackName", "ParameterValue": "adfs-pseudo-vpc" }, { "ParameterKey": "ADInstanceType", "ParameterValue": "t3.medium" }, { "ParameterKey": "ADFSInstanceType", "ParameterValue": "t3.medium" }, { "ParameterKey": "ADAdminPassword", "ParameterValue": "ChangeMe123!" }, { "ParameterKey": "ADDomainName", "ParameterValue": "corp.local" }, { "ParameterKey": "ADFSDomainName", "ParameterValue": "adfs.example.com" }, { "ParameterKey": "WindowsAMI", "ParameterValue": "/aws/service/ami-windows-latest/Windows_Server-2022-English-Full-Base" } ] スタック2のデプロイ完了後、UserDataスクリプトの実行完了まで約25-30分の待機が必要です。UserDataではADドメインの作成とADFSサーバーのドメイン参加が自動実行されます。 Step 3: スタック3(WAP)のデプロイ コマンド aws cloudformation create-stack \ --stack-name adfs-wap \ --template-body file://cfn-wap.yaml \ --parameters file://cfn-wap_adfs-wap.json \ --capabilities CAPABILITY_IAM \ --region ap-northeast-1 cfn-wap.yaml AWSTemplateFormatVersion: '2010-09-09' Description: 'Stack 3: WAP Server for ADFS Proxy Security Enhancement' Parameters: VpcStackName: Type: String Description: 'Name of the Pseudo On-Premises VPC stack' Default: 'adfs-pseudo-vpc' InstanceType: Type: String Description: 'Instance type for WAP server' Default: 't3.medium' AllowedValues: - t3.medium - t3.large - t3.xlarge LatestWindowsAmiId: Type: AWS::SSM::Parameter::Value Description: 'Latest Windows Server 2019 Japanese AMI from SSM Parameter Store' Default: '/aws/service/ami-windows-latest/Windows_Server-2019-Japanese-Full-Base' KeyPairName: Type: String Description: 'EC2 Key Pair name for WAP server (optional)' Default: '' Conditions: HasKeyPair: !Not [!Equals [!Ref KeyPairName, '']] Resources: # IAM Role for SSM (Fleet Manager) WapSSMRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: ec2.amazonaws.com Action: 'sts:AssumeRole' ManagedPolicyArns: - 'arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore' Tags: - Key: Name Value: !Sub '${AWS::StackName}-ssm-role' - Key: Cost Value: 'cognitoadfs' # Instance Profile for SSM WapSSMInstanceProfile: Type: AWS::IAM::InstanceProfile Properties: Roles: - !Ref WapSSMRole # WAP Server Instance WapInstance: Type: AWS::EC2::Instance Properties: ImageId: !Ref LatestWindowsAmiId InstanceType: !Ref InstanceType KeyName: !If [HasKeyPair, !Ref KeyPairName, !Ref 'AWS::NoValue'] IamInstanceProfile: !Ref WapSSMInstanceProfile SubnetId: Fn::ImportValue: !Sub '${VpcStackName}-WapSubnet1aId' SecurityGroupIds: - Fn::ImportValue: !Sub '${VpcStackName}-WapSecurityGroupId' Tags: - Key: Name Value: !Sub '${AWS::StackName}-wap-server' - Key: Cost Value: 'cognitoadfs' PropagateTagsToVolumeOnCreation: true UserData: Fn::Base64: | # Install WAP role Install-WindowsFeature Web-Application-Proxy -IncludeManagementTools # Disable Network Level Authentication for RDP (for Fleet Manager access) Set-ItemProperty -Path 'HKLM:\System\CurrentControlSet\Control\Terminal Server\WinStations\RDP-Tcp' -Name 'UserAuthentication' -Value 0 # Lambda execution role for NLB target registration WapNLBTargetRegistrationRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: lambda.amazonaws.com Action: 'sts:AssumeRole' ManagedPolicyArns: - 'arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole' Policies: - PolicyName: 'ELBv2TargetRegistration' PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - 'elasticloadbalancing:RegisterTargets' - 'elasticloadbalancing:DeregisterTargets' Resource: '*' Tags: - Key: Name Value: !Sub '${AWS::StackName}-nlb-target-reg-role' - Key: Cost Value: 'cognitoadfs' # Lambda function for NLB target registration WapNLBTargetRegistrationFunction: Type: AWS::Lambda::Function Properties: FunctionName: !Sub '${AWS::StackName}-nlb-target-reg' Runtime: python3.12 Handler: index.handler Role: !GetAtt WapNLBTargetRegistrationRole.Arn Timeout: 60 Code: ZipFile: | import json import boto3 import cfnresponse def handler(event, context): try: target_group_arn = event['ResourceProperties']['TargetGroupArn'] instance_id = event['ResourceProperties']['InstanceId'] client = boto3.client('elbv2') if event['RequestType'] in ['Create', 'Update']: client.register_targets( TargetGroupArn=target_group_arn, Targets=[{'Id': instance_id}] ) cfnresponse.send(event, context, cfnresponse.SUCCESS, {'InstanceId': instance_id}) elif event['RequestType'] == 'Delete': try: client.deregister_targets( TargetGroupArn=target_group_arn, Targets=[{'Id': instance_id}] ) except Exception: pass cfnresponse.send(event, context, cfnresponse.SUCCESS, {}) except Exception as e: print(f'Error: {e}') cfnresponse.send(event, context, cfnresponse.FAILED, {'Error': str(e)}) Tags: - Key: Name Value: !Sub '${AWS::StackName}-nlb-target-reg-func' - Key: Cost Value: 'cognitoadfs' # Custom resource to register WAP instance to External NLB target group WapNLBTargetRegistration: Type: Custom::NLBTargetRegistration DependsOn: WapInstance Properties: ServiceToken: !GetAtt WapNLBTargetRegistrationFunction.Arn TargetGroupArn: Fn::ImportValue: !Sub '${VpcStackName}-ExternalNlbTargetGroupArn' InstanceId: !Ref WapInstance Outputs: WapInstanceId: Description: 'WAP Server Instance ID' Value: !Ref WapInstance Export: Name: !Sub '${AWS::StackName}-WapInstanceId' WapInstancePrivateIp: Description: 'WAP Server Private IP Address' Value: !GetAtt WapInstance.PrivateIp Export: Name: !Sub '${AWS::StackName}-WapInstancePrivateIp' cfn-wap_adfs-wap.json [ { "ParameterKey": "VpcStackName", "ParameterValue": "adfs-pseudo-vpc" }, { "ParameterKey": "InstanceType", "ParameterValue": "t3.medium" }, { "ParameterKey": "LatestWindowsAmiId", "ParameterValue": "/aws/service/ami-windows-latest/Windows_Server-2019-Japanese-Full-Base" }, { "ParameterKey": "KeyPairName", "ParameterValue": "" } ] Step 4: ADテストユーザーの作成 SSM Run Commandを使用して、ADサーバー上にテストユーザーを作成します。 ユーザ名 パスワード testuser01@corp TestPass123! testuser02@corp TestPass123! testuser03@corp TestPass123! aws ssm send-command \ --instance-ids "<ADインスタンスID>" \ --document-name "AWS-RunPowerShellScript" \ --parameters file://ad-create-test-users.json \ --region ap-northeast-1 ad-create-test-users.json { "commands": [ "Import-Module ActiveDirectory", "$password = ConvertTo-SecureString 'TestPass123!' -AsPlainText -Force", "New-ADUser -Name 'testuser01' -SamAccountName 'testuser01' -UserPrincipalName 'testuser01@corp.local' -AccountPassword $password -Enabled $true -PasswordNeverExpires $true -Path 'CN=Users,DC=corp,DC=local'", "New-ADUser -Name 'testuser02' -SamAccountName 'testuser02' -UserPrincipalName 'testuser02@corp.local' -AccountPassword $password -Enabled $true -PasswordNeverExpires $true -Path 'CN=Users,DC=corp,DC=local'", "New-ADUser -Name 'testuser03' -SamAccountName 'testuser03' -UserPrincipalName 'testuser03@corp.local' -AccountPassword $password -Enabled $true -PasswordNeverExpires $true -Path 'CN=Users,DC=corp,DC=local'", "Write-Output 'Test users created. Verifying...'", "Get-ADUser -Filter * | Select-Object Name,SamAccountName,Enabled | Format-Table -AutoSize" ] } Step 5: ADFSファームの作成 Fleet Manager でADFSサーバーに接続し、PowerShellでADFSファームを作成します。adfs.example.comのところ(2か所)はADFSを外部公開するFQDNに置き換えてください。 ユーザ名 パスワード CORP\Administrator ChangeMe123! # 証明書サムプリント取得 $cert = Get-ChildItem -Path Cert:\LocalMachine\My | Where-Object {$_.Subject -like "*adfs.example.com*"} # ADFSファーム作成 $password = ConvertTo-SecureString "ChangeMe123!" -AsPlainText -Force $credential = New-Object System.Management.Automation.PSCredential( "CORP\Administrator", $password) Install-AdfsFarm ` -CertificateThumbprint $cert.Thumbprint ` -FederationServiceName "adfs.example.com" ` -ServiceAccountCredential $credential ` -OverwriteConfiguration Step 6: ADFSメタデータXMLのS3アップロード 自己署名証明書環境では、CognitoのMetadataURL方式が使えないため、メタデータXMLをS3にアップロードします。 # メタデータXML取得。ファイル名 adfs-metadata.xml として保存 curl -sk https://<ADFSのFQDN>/FederationMetadata/2007-06/FederationMetadata.xml \ -o /tmp/adfs-metadata.xml # S3バケット作成 aws s3 mb "s3://<バケット名>" --region ap-northeast-1 # S3にアップロード aws s3 cp tmp/adfs-metadata.xml "s3://<バケット名>/adfs-metadata.xml" \ --region ap-northeast-1 Step 7: スタック4(本番VPC)のデプロイ デプロイには、ウェブサーバの前に配置するALBにインストールするACMサーバ証明書のARNが必要になります。 コマンド aws cloudformation create-stack \ --stack-name adfs-prod-vpc \ --template-body file://cfn-production-vpc.yaml \ --parameters file://cfn-production-vpc_adfs-prod-vpc.json \ --region ap-northeast-1 cfn-production-vpc.yaml AWSTemplateFormatVersion: '2010-09-09' Description: 'Stack 4: Production VPC with ALB for Web Application' Parameters: VpcCidr: Type: String Description: 'CIDR block for the VPC' Default: '10.1.0.0/16' PublicSubnet1Cidr: Type: String Description: 'CIDR block for the first public subnet' Default: '10.1.1.0/24' PublicSubnet2Cidr: Type: String Description: 'CIDR block for the second public subnet' Default: '10.1.2.0/24' PrivateSubnet1Cidr: Type: String Description: 'CIDR block for the first private subnet' Default: '10.1.3.0/24' PrivateSubnet2Cidr: Type: String Description: 'CIDR block for the second private subnet' Default: '10.1.4.0/24' AvailabilityZone1: Type: String Description: 'First Availability Zone' Default: 'ap-northeast-1a' AvailabilityZone2: Type: String Description: 'Second Availability Zone' Default: 'ap-northeast-1c' ALBCertificateArn: Type: String Description: 'ARN of the ACM certificate for ALB HTTPS listener' Resources: # VPC ProductionVpc: Type: AWS::EC2::VPC Properties: CidrBlock: !Ref VpcCidr EnableDnsSupport: true EnableDnsHostnames: true Tags: - Key: Cost Value: cognitoadfs - Key: Name Value: !Sub '${AWS::StackName}-vpc' # Internet Gateway InternetGateway: Type: AWS::EC2::InternetGateway Properties: Tags: - Key: Cost Value: cognitoadfs - Key: Name Value: !Sub '${AWS::StackName}-igw' InternetGatewayAttachment: Type: AWS::EC2::VPCGatewayAttachment Properties: VpcId: !Ref ProductionVpc InternetGatewayId: !Ref InternetGateway # Public Subnet 1 PublicSubnet1: Type: AWS::EC2::Subnet Properties: VpcId: !Ref ProductionVpc CidrBlock: !Ref PublicSubnet1Cidr AvailabilityZone: !Ref AvailabilityZone1 MapPublicIpOnLaunch: true Tags: - Key: Cost Value: cognitoadfs - Key: Name Value: !Sub '${AWS::StackName}-public-subnet-1' # Public Subnet 2 PublicSubnet2: Type: AWS::EC2::Subnet Properties: VpcId: !Ref ProductionVpc CidrBlock: !Ref PublicSubnet2Cidr AvailabilityZone: !Ref AvailabilityZone2 MapPublicIpOnLaunch: true Tags: - Key: Cost Value: cognitoadfs - Key: Name Value: !Sub '${AWS::StackName}-public-subnet-2' # Private Subnet 1 PrivateSubnet1: Type: AWS::EC2::Subnet Properties: VpcId: !Ref ProductionVpc CidrBlock: !Ref PrivateSubnet1Cidr AvailabilityZone: !Ref AvailabilityZone1 MapPublicIpOnLaunch: false Tags: - Key: Cost Value: cognitoadfs - Key: Name Value: !Sub '${AWS::StackName}-private-subnet-1' # Private Subnet 2 PrivateSubnet2: Type: AWS::EC2::Subnet Properties: VpcId: !Ref ProductionVpc CidrBlock: !Ref PrivateSubnet2Cidr AvailabilityZone: !Ref AvailabilityZone2 MapPublicIpOnLaunch: false Tags: - Key: Cost Value: cognitoadfs - Key: Name Value: !Sub '${AWS::StackName}-private-subnet-2' # Elastic IP for NAT Gateway NatGatewayEIP: Type: AWS::EC2::EIP DependsOn: InternetGatewayAttachment Properties: Domain: vpc Tags: - Key: Cost Value: cognitoadfs - Key: Name Value: !Sub '${AWS::StackName}-nat-eip' # NAT Gateway (in Public Subnet 1) NatGateway: Type: AWS::EC2::NatGateway Properties: AllocationId: !GetAtt NatGatewayEIP.AllocationId SubnetId: !Ref PublicSubnet1 Tags: - Key: Cost Value: cognitoadfs - Key: Name Value: !Sub '${AWS::StackName}-nat-gateway' # Public Route Table PublicRouteTable: Type: AWS::EC2::RouteTable Properties: VpcId: !Ref ProductionVpc Tags: - Key: Cost Value: cognitoadfs - Key: Name Value: !Sub '${AWS::StackName}-public-rt' PublicRoute: Type: AWS::EC2::Route DependsOn: InternetGatewayAttachment Properties: RouteTableId: !Ref PublicRouteTable DestinationCidrBlock: '0.0.0.0/0' GatewayId: !Ref InternetGateway PublicSubnet1RouteTableAssociation: Type: AWS::EC2::SubnetRouteTableAssociation Properties: SubnetId: !Ref PublicSubnet1 RouteTableId: !Ref PublicRouteTable PublicSubnet2RouteTableAssociation: Type: AWS::EC2::SubnetRouteTableAssociation Properties: SubnetId: !Ref PublicSubnet2 RouteTableId: !Ref PublicRouteTable # Private Route Table PrivateRouteTable: Type: AWS::EC2::RouteTable Properties: VpcId: !Ref ProductionVpc Tags: - Key: Cost Value: cognitoadfs - Key: Name Value: !Sub '${AWS::StackName}-private-rt' PrivateRoute: Type: AWS::EC2::Route Properties: RouteTableId: !Ref PrivateRouteTable DestinationCidrBlock: '0.0.0.0/0' NatGatewayId: !Ref NatGateway PrivateSubnet1RouteTableAssociation: Type: AWS::EC2::SubnetRouteTableAssociation Properties: SubnetId: !Ref PrivateSubnet1 RouteTableId: !Ref PrivateRouteTable PrivateSubnet2RouteTableAssociation: Type: AWS::EC2::SubnetRouteTableAssociation Properties: SubnetId: !Ref PrivateSubnet2 RouteTableId: !Ref PrivateRouteTable # Security Group for ALB ALBSecurityGroup: Type: AWS::EC2::SecurityGroup Properties: GroupDescription: 'Security group for Application Load Balancer' VpcId: !Ref ProductionVpc SecurityGroupIngress: - IpProtocol: tcp FromPort: 443 ToPort: 443 CidrIp: '0.0.0.0/0' Description: 'Allow HTTPS from Internet' SecurityGroupEgress: - IpProtocol: -1 CidrIp: '0.0.0.0/0' Tags: - Key: Cost Value: cognitoadfs - Key: Name Value: !Sub '${AWS::StackName}-alb-sg' # Security Group for Web Application WebAppSecurityGroup: Type: AWS::EC2::SecurityGroup Properties: GroupDescription: 'Security group for Web Application server' VpcId: !Ref ProductionVpc SecurityGroupEgress: - IpProtocol: -1 CidrIp: '0.0.0.0/0' Tags: - Key: Cost Value: cognitoadfs - Key: Name Value: !Sub '${AWS::StackName}-webapp-sg' # Security Group Ingress Rule for WebApp (from ALB) WebAppSecurityGroupIngressFromALB: Type: AWS::EC2::SecurityGroupIngress Properties: GroupId: !Ref WebAppSecurityGroup IpProtocol: tcp FromPort: 80 ToPort: 80 SourceSecurityGroupId: !Ref ALBSecurityGroup Description: 'Allow HTTP from ALB' # Application Load Balancer ApplicationLoadBalancer: Type: AWS::ElasticLoadBalancingV2::LoadBalancer Properties: Name: !Sub '${AWS::StackName}-alb' Type: application Scheme: internet-facing IpAddressType: ipv4 SecurityGroups: - !Ref ALBSecurityGroup Subnets: - !Ref PublicSubnet1 - !Ref PublicSubnet2 Tags: - Key: Cost Value: cognitoadfs - Key: Name Value: !Sub '${AWS::StackName}-alb' # ALB Target Group ALBTargetGroup: Type: AWS::ElasticLoadBalancingV2::TargetGroup Properties: Name: !Sub '${AWS::StackName}-alb-tg' VpcId: !Ref ProductionVpc Port: 80 Protocol: HTTP TargetType: instance HealthCheckEnabled: true HealthCheckPath: /health HealthCheckProtocol: HTTP HealthCheckPort: traffic-port HealthyThresholdCount: 5 UnhealthyThresholdCount: 2 HealthCheckIntervalSeconds: 30 HealthCheckTimeoutSeconds: 6 Tags: - Key: Cost Value: cognitoadfs - Key: Name Value: !Sub '${AWS::StackName}-alb-tg' # ALB HTTPS Listener ALBListener: Type: AWS::ElasticLoadBalancingV2::Listener Properties: LoadBalancerArn: !Ref ApplicationLoadBalancer Port: 443 Protocol: HTTPS SslPolicy: ELBSecurityPolicy-TLS13-1-2-2021-06 Certificates: - CertificateArn: !Ref ALBCertificateArn DefaultActions: - Type: forward TargetGroupArn: !Ref ALBTargetGroup Outputs: VpcId: Description: 'VPC ID' Value: !Ref ProductionVpc Export: Name: !Sub '${AWS::StackName}-VpcId' PrivateSubnet1Id: Description: 'Private Subnet 1 ID' Value: !Ref PrivateSubnet1 Export: Name: !Sub '${AWS::StackName}-PrivateSubnet1Id' PrivateSubnet2Id: Description: 'Private Subnet 2 ID' Value: !Ref PrivateSubnet2 Export: Name: !Sub '${AWS::StackName}-PrivateSubnet2Id' PublicSubnet1Id: Description: 'Public Subnet 1 ID' Value: !Ref PublicSubnet1 Export: Name: !Sub '${AWS::StackName}-PublicSubnet1Id' PublicSubnet2Id: Description: 'Public Subnet 2 ID' Value: !Ref PublicSubnet2 Export: Name: !Sub '${AWS::StackName}-PublicSubnet2Id' ALBTargetGroupArn: Description: 'ALB Target Group ARN' Value: !Ref ALBTargetGroup Export: Name: !Sub '${AWS::StackName}-ALBTargetGroupArn' ALBListenerArn: Description: 'ALB Listener ARN' Value: !Ref ALBListener Export: Name: !Sub '${AWS::StackName}-ALBListenerArn' WebAppSecurityGroupId: Description: 'Web Application Security Group ID' Value: !Ref WebAppSecurityGroup Export: Name: !Sub '${AWS::StackName}-WebAppSecurityGroupId' ALBDnsName: Description: 'ALB DNS Name' Value: !GetAtt ApplicationLoadBalancer.DNSName Export: Name: !Sub '${AWS::StackName}-ALBDnsName' cfn-production-vpc_adfs-prod-vpc.json [ { "ParameterKey": "VpcCidr", "ParameterValue": "10.1.0.0/16" }, { "ParameterKey": "PublicSubnet1Cidr", "ParameterValue": "10.1.1.0/24" }, { "ParameterKey": "PublicSubnet2Cidr", "ParameterValue": "10.1.2.0/24" }, { "ParameterKey": "PrivateSubnet1Cidr", "ParameterValue": "10.1.3.0/24" }, { "ParameterKey": "PrivateSubnet2Cidr", "ParameterValue": "10.1.4.0/24" }, { "ParameterKey": "AvailabilityZone1", "ParameterValue": "ap-northeast-1a" }, { "ParameterKey": "AvailabilityZone2", "ParameterValue": "ap-northeast-1c" }, { "ParameterKey": "ALBCertificateArn", "ParameterValue": "<ACMで作成したウェブアプリ用サーバ証明書のARN>" } ] Step 8: スタック5(Cognito/WebApp)のデプロイ Step 6で作成したADFSメタデータXMLファイルを格納したS3バケット名をパラメータファイルで指定する必要があります。 ウェブサイトのDNS名を決定し、Route 53で名前解決設定(CNAMEまたはALIASをStep 7で作成したALBに向ける)をしてから実行してください。またそのDNS名をパラメータファイルで指定する必要があります。 パラメータファイルで指定するCognitoのドメインプレフィックスはAWSアカウント横断でグローバルに一意である必要があります。”AlreadyExists”エラーが発生した場合は、プレフィックスを変更してください。 コマンド aws cloudformation create-stack \ --stack-name adfs-webapp \ --template-body file://cfn-cognito-webapp.yaml \ --parameters file://cfn-cognito-webapp_adfs-webapp.json \ --capabilities CAPABILITY_NAMED_IAM \ --region ap-northeast-1 cfn-cognito-webapp.yaml 表示崩れるため別掲します。 cfn-cognito-webapp_adfs-webapp.json [ { "ParameterKey": "ProductionVpcStackName", "ParameterValue": "adfs-prod-vpc" }, { "ParameterKey": "PseudoOnPremVpcStackName", "ParameterValue": "adfs-pseudo-vpc" }, { "ParameterKey": "ADFSMetadataS3Bucket", "ParameterValue": "your-adfs-metadata-xml-bucket-name" }, { "ParameterKey": "ADFSMetadataS3Key", "ParameterValue": "adfs-metadata.xml" }, { "ParameterKey": "CognitoDomainPrefix", "ParameterValue": "your-cognito-domain-prefix" }, { "ParameterKey": "ALBDnsName", "ParameterValue": "<ウェブサーバのDNS名>" }, { "ParameterKey": "WebAppInstanceType", "ParameterValue": "t3.micro" }, { "ParameterKey": "WebAppAmiId", "ParameterValue": "/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-x86_64" } ] Step 9: WAP構成 Fleet Manager でWAPサーバーに接続し、以下の手順でWAPを構成します。 9-1. DNS解決設定 WAPサーバーのhostsファイルに、ADFS FQDNとInternal NLBのIPアドレスのマッピングを追加します。WAPはADFS FQDNに対してHTTPS通信を行いますが、通常のDNS解決ではExternal NLBに解決されてしまうため、hostsファイルでInternal NLB経由に向ける必要があります。 $internalNlbDns = "<Internal NLB DNS名>" $adfsIps = [System.Net.Dns]::GetHostAddresses($internalNlbDns) | Select-Object -ExpandProperty IPAddressToString $hostsEntry = "$($adfsIps[0]) adfs.example.com" Add-Content -Path "C:\Windows\System32\drivers\etc\hosts" -Value $hostsEntry 9-2. ADFS証明書のインポート ADFSサーバーから証明書をPFXファイルとしてエクスポートし、S3経由でWAPサーバーに転送してインポートします。 # WAPサーバーで証明書をインポート $password = ConvertTo-SecureString -String "<エクスポートパスワード>" ` -Force -AsPlainText Import-PfxCertificate -FilePath "C:\adfs-cert.pfx" ` -CertStoreLocation Cert:\LocalMachine\My -Password $password 9-3. Proxy Trust確立 WAPとADFS間の信頼関係を確立します。 $cred = Get-Credential # CORP\Administrator Install-WebApplicationProxy ` -FederationServiceName "adfs.example.com" ` -FederationServiceTrustCredential $cred ` -CertificateThumbprint "<証明書Thumbprint>" 9-4. ADFSアプリケーション公開 ADFSをWAP経由で外部に公開します。 Add-WebApplicationProxyApplication ` -Name "ADFS" ` -ExternalUrl "https://adfs.example.com/adfs/ls/" ` -ExternalCertificateThumbprint "<証明書Thumbprint>" ` -BackendServerUrl "https://adfs.example.com/adfs/ls/" ` -ExternalPreauthentication PassThrough 以上
はじめに(リモートアクセスについて) 現代では、コンテナやマネージドサービス等、運用者からの直接ログインなしで運用できる範囲が広がっています。 しかし、現実問題として、運用上の要件や制約によって完全にリモートアクセス(ログイン)を無くすのは難しいケースが多いと思います。 リモートアクセス経路・方式の設計にはいつも悩まされます。 統制・監査の実現方式 GUIを利用しようとする場合の接続プロトコルや方式 認証・認可等セキュリティの実現方式 ロケール対応(日本語対応)の実施 など、難しい設計要素を含むためです。 そこで今回はAWS(Amazon Web Services)でリモートアク
動画
該当するコンテンツが見つかりませんでした









