ããã«ã¡ã¯ã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 以äž