from aws_cdk import ( aws_iam as iam, aws_s3 as s3, aws_ec2 as ec2, aws_sagemaker as sagemaker, aws_secretsmanager as secretsmanager, CfnOutput, RemovalPolicy, ) import aws_cdk as cdk from constructs import Construct class User(Construct): def __init__( self, construct: Construct, construct_id: str, name: str, studio_common_policy, domain, vpc, account_id, region, ): super().__init__(construct, construct_id) # IAM User 用のパスワードを Secrets Manager で生成 secret = secretsmanager.Secret(self, "Secret") # Secrets Manager の名前を出力する CfnOutput(self, "Password", value=secret.secret_name) # EC2 へのログイン用パスワードを Secrets Manager で生成 ec2_secret = secretsmanager.Secret(self, "EC2Secret") # Secrets Manager の名前を出力する CfnOutput(self, "EC2Password", value=ec2_secret.secret_name) # EC2 インスタンスに設定するタグ NAME_TAG_VALUE = f"ec2-individual-{name}" # 各人用の sagemaker studio の user profile にアタッチするロール self.studio_role = iam.Role( self, "Role", assumed_by=iam.ServicePrincipal("sagemaker.amazonaws.com"), managed_policies=[ iam.ManagedPolicy.from_aws_managed_policy_name( "AmazonSageMakerFullAccess" ), studio_common_policy, ], ) self.studio_role.add_managed_policy(studio_common_policy) # sagemaker studio 内から 署名付き URL 発行を禁じる # (他人の user_profile で署名付きURL発行防止目的だが、そもそも署名付き URL を発行することがないため) deny_presigned_policy = iam.ManagedPolicy(self, "DenyPresignedPolicy") deny_presigned_policy.add_statements( iam.PolicyStatement( effect=iam.Effect.DENY, actions=["sagemaker:CreatePresignedDomainUrl"], resources=["*"], ) ) self.studio_role.add_managed_policy(deny_presigned_policy) # SageMaker User Profile user_profile = sagemaker.CfnUserProfile( self, "UserProfile", domain_id=domain.attr_domain_id, user_profile_name=name, user_settings={ "executionRole": self.studio_role.role_arn, }, ) # インスタンス用 Role instance_role = iam.Role( self, "InstanceRole", assumed_by=iam.ServicePrincipal("ec2.amazonaws.com"), managed_policies=[ iam.ManagedPolicy.from_aws_managed_policy_name( "AmazonSSMManagedInstanceCore" ) ], ) instance_policy = iam.ManagedPolicy(self, "InstancePolicy") instance_policy.add_statements( iam.PolicyStatement( effect=iam.Effect.ALLOW, actions=["sagemaker:CreatePresignedDomainUrl"], resources=[ user_profile.attr_user_profile_arn, ], ) ) instance_policy.add_statements( iam.PolicyStatement( effect=iam.Effect.ALLOW, actions=["secretsmanager:GetSecretValue"], resources=[ec2_secret.secret_arn], ) ) instance_role.add_managed_policy(instance_policy) # EC2 インスタンス instance = ec2.Instance( self, "Instance", vpc=vpc, vpc_subnets=ec2.SubnetSelection( subnet_type=ec2.SubnetType.PRIVATE_ISOLATED ), instance_type=ec2.InstanceType.of( ec2.InstanceClass.T3, ec2.InstanceSize.LARGE, ), machine_image=ec2.WindowsImage( ec2.WindowsVersion.WINDOWS_SERVER_2022_ENGLISH_FULL_BASE ), block_devices=[ ec2.BlockDevice( device_name="/dev/sda1", volume=ec2.BlockDeviceVolume.ebs(30, encrypted=True), ), ], role=instance_role, user_data=ec2.UserData.custom( f""" $password = (Get-SECSecretValue -SecretId {ec2_secret.secret_arn} -Region {region}).SecretString New-LocalUser -Name user-1 -Password (ConvertTo-SecureString $password -AsPlainText -Force) Add-LocalGroupMember -Group "Remote Desktop Users" -Member user-1 Add-LocalGroupMember -Group Administrators -Member user-1 Write-Output '$url = New-SMPresignedDomainUrl -DomainId {domain.attr_domain_id} -ExpiresInSecond 30 -UserProfileName {user_profile.user_profile_name} -Region {region}' | Set-Content -Encoding Default 'C:\\GetPresignedUrl.ps1' Write-Output 'start microsoft-edge:$url' | Add-Content -Encoding Default 'C:\\GetPresignedUrl.ps1' true """ ), require_imdsv2=True, ) cdk.Tags.of(instance).add("Name", NAME_TAG_VALUE) # IAM ユーザー self.user = iam.User( self, "User", user_name=name, managed_policies=[ iam.ManagedPolicy.from_aws_managed_policy_name("IAMUserChangePassword"), ], password=secret.secret_value, password_reset_required=True, ) # IAM ユーザーにアタッチする Fleet Manager を使うためのポリシー START_SESSION_POLICY_JSON = { "Version": "2012-10-17", "Statement": [ { "Sid": "EC2GetPasswordData", "Effect": "Allow", "Action": ["ec2:GetPasswordData"], "Resource": f"arn:aws:ec2:{region}:{account_id}:instance/{instance.instance_id}", }, { "Sid": "EC2DescribeInstances", "Effect": "Allow", "Action": ["ec2:DescribeInstances"], "Resource": "*", }, { "Sid": "SSM", "Effect": "Allow", "Action": [ "ssm:DescribeInstanceProperties", "ssm:GetCommandInvocation", "ssm:GetInventorySchema", ], "Resource": "*", }, { "Sid": "SSMStartSession", "Effect": "Allow", "Action": ["ssm:StartSession"], "Resource": [ "arn:aws:ssm:*::document/AWS-StartPortForwardingSession" ], "Condition": { "BoolIfExists": { "ssm:SessionDocumentAccessCheck": "true", } }, }, { "Sid": "AccessTaggedInstances", "Effect": "Allow", "Action": ["ssm:StartSession"], "Resource": [ f"arn:aws:ec2:*:{account_id}:instance/*", f"arn:aws:ssm:*:{account_id}:managed-instance/*", ], "Condition": { "StringLike": { "ssm:resourceTag/Name": [NAME_TAG_VALUE], } }, }, { "Sid": "GuiConnect", "Effect": "Allow", "Action": [ "ssm-guiconnect:CancelConnection", "ssm-guiconnect:GetConnection", "ssm-guiconnect:StartConnection", ], "Resource": "*", }, ], } start_session_policy_document = iam.PolicyDocument.from_json( START_SESSION_POLICY_JSON ) start_session_managed_policy = iam.ManagedPolicy( self, "StartSessionManagedPolicy", document=start_session_policy_document, ) start_session_managed_policy.attach_to_user(self.user) # 個人バケット self.bucket = s3.Bucket( self, "Bucket", block_public_access=s3.BlockPublicAccess.BLOCK_ALL, encryption=s3.BucketEncryption.S3_MANAGED, enforce_ssl=True, removal_policy=RemovalPolicy.DESTROY, ) # 持ち主は読み書きできる self.bucket.grant_read_write(self.studio_role)