AWSTemplateFormatVersion: 2010-09-09 Description: >- This template creates an S3 Bucket and a File Storage Gateway mapping to the S3 bucket. (qs-1r2g41227) Parameters: VPCID: Type: 'AWS::EC2::VPC::Id' VPCCIDR: Description: CIDR block for the VPC Type: String SubnetId: Description: Private subnet Id where the VM will be launched Type: AWS::EC2::Subnet::Id KeyPairName: Description: EC2 Instance Key pair name Type: AWS::EC2::KeyPair::KeyName GatewayName: Description: Name of Storage Gateway Type: String GatewayTimezone: Description: Timezone to choose for File Gateway Type: String Resources: SecurityGroupStorageGateway: Type: 'AWS::EC2::SecurityGroup' Properties: GroupDescription: Security Group allowing NFS client/server communication GroupName: !Join - '-' - - !Ref 'AWS::StackName' - UiPathStorageGatewaySecurityGroup Tags: - Key: Name Value: !Join - '-' - - !Ref 'AWS::StackName' - UiPathStorageGatewaySecurityGroup VpcId: !Ref VPCID SecurityGroupIngress: - IpProtocol: tcp FromPort: 2049 ToPort: 2049 CidrIp: !Ref VPCCIDR - IpProtocol: udp FromPort: 2049 ToPort: 2049 CidrIp: !Ref VPCCIDR - IpProtocol: tcp FromPort: 111 ToPort: 111 CidrIp: !Ref VPCCIDR - IpProtocol: udp FromPort: 111 ToPort: 111 CidrIp: !Ref VPCCIDR - IpProtocol: tcp FromPort: 20048 ToPort: 20048 CidrIp: !Ref VPCCIDR - IpProtocol: udp FromPort: 20048 ToPort: 20048 CidrIp: !Ref VPCCIDR - IpProtocol: tcp FromPort: 35790 ToPort: 35790 CidrIp: !Ref VPCCIDR - IpProtocol: tcp FromPort: 49000 ToPort: 49000 CidrIp: !Ref VPCCIDR - IpProtocol: udp FromPort: 54524 ToPort: 54524 CidrIp: !Ref VPCCIDR - IpProtocol: udp FromPort: 55481 ToPort: 55481 CidrIp: !Ref VPCCIDR - IpProtocol: tcp FromPort: 80 ToPort: 80 CidrIp: !Ref VPCCIDR SecurityGroupEgress: - IpProtocol: '-1' CidrIp: 0.0.0.0/0 - IpProtocol: '-1' CidrIpv6: ::/0 NugetStorageS3Bucket: Type: 'AWS::S3::Bucket' Properties: PublicAccessBlockConfiguration: BlockPublicAcls: true BlockPublicPolicy: true IgnorePublicAcls: true RestrictPublicBuckets: true VersioningConfiguration: Status: Enabled FileShareIAMRole: Type: 'AWS::IAM::Role' Properties: AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Principal: Service: - storagegateway.amazonaws.com Action: - 'sts:AssumeRole' Description: IAM Role providing S3 Access to Storage Gateway FileShareIAMPolicy: Type: 'AWS::IAM::Policy' Properties: PolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Action: - 's3:GetAccelerateConfiguration' - 's3:GetBucketLocation' - 's3:GetBucketVersioning' - 's3:ListBucket' - 's3:ListBucketVersions' - 's3:ListBucketMultipartUploads' Resource: !GetAtt NugetStorageS3Bucket.Arn - Effect: Allow Action: - 's3:AbortMultipartUpload' - 's3:DeleteObject' - 's3:DeleteObjectVersion' - 's3:GetObject' - 's3:GetObjectAcl' - 's3:GetObjectVersion' - 's3:ListMultipartUploadParts' - 's3:PutObject' - 's3:PutObjectAcl' Resource: !Join - '' - - !GetAtt NugetStorageS3Bucket.Arn - '/*' PolicyName: storagegateway-iam-policy Roles: - !Ref FileShareIAMRole GatewayCreationLambdaRole: Type: 'AWS::IAM::Role' Properties: Description: >- IAM Role to be assumed by Lambda functions creating Storage Gateway custom resources AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Principal: Service: - lambda.amazonaws.com Action: - 'sts:AssumeRole' Path: / Policies: - PolicyName: root PolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Action: - 'storagegateway:ActivateGateway' - 'storagegateway:AddCache' - 'storagegateway:ListLocalDisks' - 'storagegateway:CreateNFSFileShare' - 'storagegateway:DeleteGateway' - 'storagegateway:DeleteFileShare' - 'storagegateway:AddTagsToResource' Resource: !Sub 'arn:${AWS::Partition}:storagegateway:*:*:*' - Effect: Allow Action: - 'ec2:DescribeInstanceStatus' - 'ec2:CreateNetworkInterface' - 'ec2:DescribeNetworkInterfaces' - 'ec2:DeleteNetworkInterface' - 'ec2:AssignPrivateIpAddresses' - 'ec2:UnassignPrivateIpAddresses' Resource: !Sub 'arn:${AWS::Partition}:ec2:*:*:*' - Effect: Allow Action: - 'ec2:DescribeInstanceStatus' - 'ec2:DescribeNetworkInterfaces' - 'ec2:DescribeImages' Resource: '*' - Effect: Allow Action: - 'logs:CreateLogGroup' - 'logs:CreateLogStream' - 'logs:PutLogEvents' Resource: !Sub 'arn:${AWS::Partition}:logs:*:*:*' - Effect: Allow Action: - 'ssm:DescribeParameters' - 'ssm:GetParametersByPath' - 'ssm:GetParameters' - 'ssm:GetParameter' - 'ssm:SendCommand' - 'ssm:GetCommandInvocation' Resource: !Sub 'arn:${AWS::Partition}:ssm:*:*:parameter/aws/service/storagegateway/ami/FILE_S3/latest' - Sid: 'PassExecutionRole' Effect: Allow Action: - 'iam:PassRole' Resource: !GetAtt FileShareIAMRole.Arn - Effect: Allow Action: - 'xray:PutTraceSegments' Resource: !Sub 'arn:${AWS::Partition}:xray:*:*:*' FindStorageGatewayAMIFunction: Type: 'AWS::Lambda::Function' Properties: Description: >- Finds AMI for creating a Storage Gateway VM from an AWS owned SSM data store Handler: index.handler Role: !GetAtt GatewayCreationLambdaRole.Arn Code: ZipFile: | import json import boto3 import cfnresponse import threading def getRootDevice(imageId, regionName): rootDeviceName = '' ec2 = boto3.client('ec2', regionName) images = ec2.describe_images( ExecutableUsers=['all'], Filters=[ { 'Name': 'image-id', 'Values': [imageId] } ] )['Images'] if len(images) > 0: rootDeviceName = images[0]['RootDeviceName'] print(regionName, imageId, rootDeviceName) return rootDeviceName def create(properties, physical_id): regionName = properties['RegionName'] ssm = boto3.client('ssm', region_name=regionName) imageId = '' rootDeviceName = '' try: imageId = ssm.get_parameter( Name='/aws/service/storagegateway/ami/FILE_S3/latest' )['Parameter']['Value'] rootDeviceName = getRootDevice(imageId, regionName) except: return cfnresponse.FAILED, '', {} print(regionName, imageId) returnAttribute = {} returnAttribute['ImageId'] = imageId returnAttribute['RootDeviceName'] = rootDeviceName returnAttribute['Action'] = 'CREATE' return cfnresponse.SUCCESS, imageId, returnAttribute def update(properties, physical_id): imageId = physical_id returnAttribute = {} returnAttribute['ImageId'] = imageId returnAttribute['RootDeviceName'] = getRootDevice(imageId, properties['RegionName']) returnAttribute['Action'] = 'UPDATE' return cfnresponse.SUCCESS, imageId, returnAttribute def delete(properties, physical_id): imageId = physical_id returnAttribute = {} returnAttribute['ImageId'] = imageId returnAttribute['Action'] = 'DELETE' return cfnresponse.SUCCESS, imageId, returnAttribute def timeout(event, context): print('Execution is about to time out, sending failure response to CloudFormation') cfnresponse.send(event, context, cfnresponse.FAILED, {}, None) def handler(event, context): # make sure we send a failure to CloudFormation if the function is going to timeout timer = threading.Timer((context.get_remaining_time_in_millis() / 1000.00) - 0.5, timeout, args=[event, context]) timer.start() print('Received event: ' + json.dumps(event)) status = cfnresponse.FAILED new_physical_id = None returnAttribute = {} try: properties = event.get('ResourceProperties') physical_id = event.get('PhysicalResourceId') status, new_physical_id, returnAttribute = { 'Create': create, 'Update': update, 'Delete': delete }.get(event['RequestType'], lambda x, y: (cfnresponse.FAILED, None))(properties, physical_id) except Exception as e: print('Exception: ' + str(e)) status = cfnresponse.FAILED finally: cfnresponse.send(event, context, status, returnAttribute, new_physical_id) Runtime: python3.9 Timeout: 300 TracingConfig: Mode: Active StorageGatewayAMI: Type: 'Custom::StorageGatewayAMI' Properties: ServiceToken: !GetAtt - FindStorageGatewayAMIFunction - Arn RegionName: !Ref 'AWS::Region' Gateway: Type: 'AWS::EC2::Instance' Properties: BlockDeviceMappings: - DeviceName: /dev/sdb Ebs: DeleteOnTermination: true Encrypted: true VolumeSize: 150 VolumeType: gp2 - DeviceName: !GetAtt StorageGatewayAMI.RootDeviceName Ebs: Encrypted: true EbsOptimized: true ImageId: !Ref StorageGatewayAMI InstanceType: m4.xlarge KeyName: !Ref KeyPairName NetworkInterfaces: - AssociatePublicIpAddress: false DeleteOnTermination: true DeviceIndex: '0' GroupSet: - !Ref SecurityGroupStorageGateway SubnetId: !Ref SubnetId Tags: - Key: Name Value: UIPath Storage Gateway VM 1 Tenancy: default ActivationKeyFunction: Type: 'AWS::Lambda::Function' Properties: Description: Activates a Storage Gateway Handler: index.handler Role: !GetAtt GatewayCreationLambdaRole.Arn Code: ZipFile: | import os import time import json import boto3 import cfnresponse import threading import urllib3 ec2 = boto3.client('ec2') def create(properties, physical_id): instanceId = properties['InstanceId'] instanceIP = properties['InstanceIP'] instanceRegion = properties['InstanceRegion'] print('Trying to describe instances') instancestatuses = ec2.describe_instance_status(InstanceIds=[instanceId])['InstanceStatuses'] while len(instancestatuses) <= 0: instancestatuses = ec2.describe_instance_status(InstanceIds=[instanceId])['InstanceStatuses'] print(f'Waiting for Instance-{instanceId} to be launched ...') time.sleep(10) print(f'Described instances: {instancestatuses}') instancedetails = instancestatuses[0]['InstanceStatus']['Details'][0]['Status'] systemstatus = instancestatuses[0]['SystemStatus']['Status'] while instancedetails != 'passed' and systemstatus != 'ok': instancestatuses = ec2.describe_instance_status(InstanceIds=[instanceId])['InstanceStatuses'] instancedetails = instancestatuses[0]['InstanceStatus']['Details'][0]['Status'] systemstatus = instancestatuses[0]['SystemStatus']['Status'] print(f'Waiting for Instance-{instanceId} to pass status check ...') time.sleep(30) print('Retrieving activation key ...') activationKey = '' http = urllib3.PoolManager() r = http.request('GET', f'http://{instanceIP}/?activationRegion={instanceRegion}', redirect=False) redirect_url = r.get_redirect_location() if not redirect_url: raise Exception(f'No redirect url returned for ip: {instanceIP}') activationKey = redirect_url[redirect_url.find('activationKey=')+14:] if not activationKey: raise Exception(f'Unable to extract the key from the returned redirect url: {redirect_url}') print(f'Activation Key = "{activationKey}"') returnAttribute = {} returnAttribute['Key'] = activationKey returnAttribute['Action'] = 'CREATE' return cfnresponse.SUCCESS, activationKey, returnAttribute def update(properties, physical_id): activationKey = physical_id returnAttribute = {} returnAttribute['Key'] = activationKey returnAttribute['Action'] = 'UPDATE' return cfnresponse.SUCCESS, activationKey, returnAttribute def delete(properties, physical_id): activationKey = physical_id returnAttribute = {} returnAttribute['Key'] = activationKey returnAttribute['Action'] = 'DELETE' return cfnresponse.SUCCESS, activationKey, returnAttribute def timeout(event, context): print('Execution is about to time out, sending failure response to CloudFormation') cfnresponse.send(event, context, cfnresponse.FAILED, {}, None) def handler(event, context): # make sure we send a failure to CloudFormation if the function is going to timeout timer = threading.Timer((context.get_remaining_time_in_millis() / 1000.00) - 0.5, timeout, args=[event, context]) timer.start() print('Received event: ' + json.dumps(event)) status = cfnresponse.FAILED new_physical_id = None returnAttribute = {} try: properties = event.get('ResourceProperties') physical_id = event.get('PhysicalResourceId') status, new_physical_id, returnAttribute = { 'Create': create, 'Update': update, 'Delete': delete }.get(event['RequestType'], lambda x, y: (cfnresponse.FAILED, None))(properties, physical_id) except Exception as e: print('Exception: ' + str(e)) status = cfnresponse.FAILED finally: cfnresponse.send(event, context, status, returnAttribute, new_physical_id) Runtime: python3.9 Timeout: 900 TracingConfig: Mode: Active VpcConfig: SecurityGroupIds: - !Ref SecurityGroupStorageGateway SubnetIds: - !Ref SubnetId ActivationKey: Type: 'Custom::ActivationKey' Properties: ServiceToken: !GetAtt - ActivationKeyFunction - Arn InstanceId: !Ref Gateway InstanceIP: !GetAtt - Gateway - PrivateIp InstanceRegion: !Ref 'AWS::Region' ActivateGatewayFunction: Type: 'AWS::Lambda::Function' Properties: Description: Activates a Storage Gateway Handler: index.handler Role: !GetAtt GatewayCreationLambdaRole.Arn Code: ZipFile: | import time import json import boto3 import cfnresponse import threading from datetime import datetime from dateutil import tz gatewayClient = boto3.client('storagegateway') def create(properties, physical_id): activationKey = properties['ActivationKey'] instanceRegion = properties['InstanceRegion'] gatewayName = properties['GatewayName'] gatewayTimezone = properties['GatewayTimezone'] zone=datetime.now(tz.gettz(gatewayTimezone)).strftime('%z') timezonesign = zone[0:1] timezonehour = str(int(zone[1:3])) timezoneminute = zone[3:5] gatewayTimezoneOffset = f'GMT{timezonesign}{timezonehour}:{timezoneminute}' print(f'GatewayTimezoneOffset = {gatewayTimezoneOffset}') gatewayARN = gatewayClient.activate_gateway( ActivationKey=activationKey, GatewayName=gatewayName, GatewayTimezone=gatewayTimezoneOffset, GatewayRegion=instanceRegion, GatewayType='FILE_S3' )['GatewayARN'] print(f'Gateway ARN = {gatewayARN}, Gateway Name = {gatewayName}') returnAttribute = {} returnAttribute['Arn'] = gatewayARN returnAttribute['Name'] = gatewayName returnAttribute['Action'] = 'CREATE' return cfnresponse.SUCCESS, gatewayARN, returnAttribute def update(properties, physical_id): gatewayARN = physical_id gatewayName = properties['GatewayName'] gatewayTimezone = properties['GatewayTimezone'] zone=datetime.now(tz.gettz(gatewayTimezone)).strftime('%z') timezonesign = zone[0:1] timezonehour = str(int(zone[1:3])) timezoneminute = zone[3:5] gatewayTimezoneOffset = f'GMT{timezonesign}{timezonehour}:{timezoneminute}' gatewayName = gatewayClient.update_gateway_information( GatewayARN=gatewayARN, GatewayName=gatewayName, GatewayTimezone=gatewayTimezoneOffset )['GatewayName'] returnAttribute = {} returnAttribute['Arn'] = gatewayARN returnAttribute['Name'] = gatewayName returnAttribute['Action'] = 'UPDATE' return cfnresponse.SUCCESS, gatewayARN, returnAttribute def delete(properties, physical_id): gatewayARN = physical_id gatewayName = properties['GatewayName'] gatewayARN = gatewayClient.delete_gateway( GatewayARN=gatewayARN )['GatewayARN'] returnAttribute = {} returnAttribute['Arn'] = gatewayARN returnAttribute['Name'] = gatewayName returnAttribute['Action'] = 'DELETE' return cfnresponse.SUCCESS, gatewayARN, returnAttribute def timeout(event, context): print('Execution is about to time out, sending failure response to CloudFormation') cfnresponse.send(event, context, cfnresponse.FAILED, {}, None) def handler(event, context): # make sure we send a failure to CloudFormation if the function is going to timeout timer = threading.Timer((context.get_remaining_time_in_millis() / 1000.00) - 0.5, timeout, args=[event, context]) timer.start() print('Received event: ' + json.dumps(event)) status = cfnresponse.FAILED new_physical_id = None returnAttribute = {} try: properties = event.get('ResourceProperties') physical_id = event.get('PhysicalResourceId') status, new_physical_id, returnAttribute = { 'Create': create, 'Update': update, 'Delete': delete }.get(event['RequestType'], lambda x, y: (cfnresponse.FAILED, None))(properties, physical_id) except Exception as e: print('Exception: ' + str(e)) status = cfnresponse.FAILED finally: cfnresponse.send(event, context, status, returnAttribute, new_physical_id) Runtime: python3.9 Timeout: 300 TracingConfig: Mode: Active StorageGateway: Type: 'Custom::StorageGateway' Properties: ServiceToken: !GetAtt - ActivateGatewayFunction - Arn ActivationKey: !Ref ActivationKey InstanceRegion: !Ref 'AWS::Region' GatewayName: !Ref GatewayName GatewayTimezone: !Ref GatewayTimezone AddDiskCacheFunction: Type: 'AWS::Lambda::Function' Properties: Description: Adds a disk cache to storage gateway VM Handler: index.handler Role: !GetAtt GatewayCreationLambdaRole.Arn Code: ZipFile: | import boto3 import json import cfnresponse import threading gatewayClient = boto3.client('storagegateway') def create(properties, physical_id): gatewayARN = properties['GatewayARN'] print(f'Getting disks for Gateway {gatewayARN} ...') disks = [] while len(disks) <= 0: try: disks = gatewayClient.list_local_disks( GatewayARN=gatewayARN )['Disks'] print(disks) except gatewayClient.exceptions.InvalidGatewayRequestException as e: print('Exception: ' + str(e)) print(f'Found {len(disks)} disks') diskIds = [] for disk in disks: if disk['DiskAllocationType'] == 'AVAILABLE': diskId = disk['DiskId'] diskIds.append(diskId) print(f'Disk to be added to cache: {diskId}') break print(f'Adding Disk Cache to Gateway {gatewayARN} ...') if len(diskIds) > 0: gatewayClient.add_cache( GatewayARN=gatewayARN, DiskIds=diskIds ) print('Disk Cache added') else: print('No Disks to be added') print(f'Gateway ARN = {gatewayARN}, Disk Id = {diskIds[0]}') returnAttribute = {} returnAttribute['Arn'] = gatewayARN returnAttribute['Action'] = 'CREATE' return cfnresponse.SUCCESS, gatewayARN, returnAttribute def update(properties, physical_id): gatewayARN = physical_id returnAttribute = {} returnAttribute['Arn'] = gatewayARN returnAttribute['Action'] = 'UPDATE' return cfnresponse.SUCCESS, gatewayARN, returnAttribute def delete(properties, physical_id): gatewayARN = physical_id returnAttribute = {} returnAttribute['Arn'] = gatewayARN returnAttribute['Action'] = 'DELETE' return cfnresponse.SUCCESS, gatewayARN, returnAttribute def timeout(event, context): print('Execution is about to time out, sending failure response to CloudFormation') cfnresponse.send(event, context, cfnresponse.FAILED, {}, None) def handler(event, context): # make sure we send a failure to CloudFormation if the function is going to timeout timer = threading.Timer((context.get_remaining_time_in_millis() / 1000.00) - 0.5, timeout, args=[event, context]) timer.start() print('Received event: ' + json.dumps(event)) status = cfnresponse.FAILED new_physical_id = None returnAttribute = {} try: properties = event.get('ResourceProperties') physical_id = event.get('PhysicalResourceId') status, new_physical_id, returnAttribute = { 'Create': create, 'Update': update, 'Delete': delete }.get(event['RequestType'], lambda x, y: (cfnresponse.FAILED, None))(properties, physical_id) except Exception as e: print('Exception: ' + str(e)) status = cfnresponse.FAILED finally: cfnresponse.send(event, context, status, returnAttribute, new_physical_id) Runtime: python3.9 Timeout: 300 TracingConfig: Mode: Active DiskCache: Type: 'Custom::DiskCache' Properties: ServiceToken: !GetAtt - AddDiskCacheFunction - Arn GatewayARN: !Ref StorageGateway CreateFileShareFunction: Type: 'AWS::Lambda::Function' Properties: Description: Creates a gateway S3 file share Handler: index.handler Role: !GetAtt GatewayCreationLambdaRole.Arn Code: ZipFile: | import boto3 import json import cfnresponse import threading gatewayClient = boto3.client('storagegateway') def create(properties, physical_id): gatewayARN = properties['GatewayARN'] storageBucketARN = properties['StorageBucketARN'] fileShareIAMRole = properties['FileShareIAMRole'] print(f'Creating NFS File Share for Gateway {gatewayARN} ...') fileShareARN = gatewayClient.create_nfs_file_share( ClientToken='UIPathS3FileStorageGatewayClient', GatewayARN=gatewayARN, KMSEncrypted=False, Role=fileShareIAMRole, LocationARN=storageBucketARN, DefaultStorageClass='S3_STANDARD', ObjectACL='private', ClientList=[ '0.0.0.0/0'], Squash='RootSquash', ReadOnly=False, GuessMIMETypeEnabled=True, RequesterPays=False, Tags=[ { 'Key': 'Name', 'Value': 'UIPathS3FileStorageGatewayShare' } ] )['FileShareARN'] print(f'Gateway ARN = {gatewayARN}, Fileshare ARN = {fileShareARN}') returnAttribute = {} returnAttribute['Arn'] = fileShareARN returnAttribute['Action'] = 'CREATE' return cfnresponse.SUCCESS, fileShareARN, returnAttribute def update(properties, physical_id): fileShareARN = physical_id returnAttribute = {} returnAttribute['Arn'] = fileShareARN returnAttribute['Action'] = 'UPDATE' return cfnresponse.SUCCESS, fileShareARN, returnAttribute def delete(properties, physical_id): fileShareARN = physical_id print(f'Deleting file share {fileShareARN} ...') fileShareARN = gatewayClient.delete_file_share( FileShareARN=fileShareARN, ForceDelete=True )['FileShareARN'] returnAttribute = {} returnAttribute['Arn'] = fileShareARN returnAttribute['Action'] = 'DELETE' return cfnresponse.SUCCESS, fileShareARN, returnAttribute def timeout(event, context): print('Execution is about to time out, sending failure response to CloudFormation') cfnresponse.send(event, context, cfnresponse.FAILED, {}, None) def handler(event, context): # make sure we send a failure to CloudFormation if the function is going to timeout timer = threading.Timer((context.get_remaining_time_in_millis() / 1000.00) - 0.5, timeout, args=[event, context]) timer.start() print('Received event: ' + json.dumps(event)) status = cfnresponse.FAILED new_physical_id = None returnAttribute = {} try: properties = event.get('ResourceProperties') physical_id = event.get('PhysicalResourceId') status, new_physical_id, returnAttribute = { 'Create': create, 'Update': update, 'Delete': delete }.get(event['RequestType'], lambda x, y: (cfnresponse.FAILED, None))(properties, physical_id) except Exception as e: print('Exception: ' + str(e)) status = cfnresponse.FAILED finally: cfnresponse.send(event, context, status, returnAttribute, new_physical_id) Runtime: python3.9 Timeout: 300 TracingConfig: Mode: Active FileShare: Type: 'Custom::FileShare' Properties: ServiceToken: !GetAtt - CreateFileShareFunction - Arn GatewayARN: !Ref StorageGateway StorageBucketARN: !GetAtt NugetStorageS3Bucket.Arn FileShareIAMRole: !GetAtt FileShareIAMRole.Arn Outputs: GatewayVMID1: Value: !Ref Gateway Description: ID of Gateway VM in Private Subnet 1 GatewayVMPrivateIP1: Value: !GetAtt - Gateway - PrivateIp Description: Private IP of Gateway VM in Private Subnet 1 GatewayARN1: Value: !Ref StorageGateway Description: ARN of Storage Gateway in Private Subnet 1 FileShareARN1: Value: !Ref FileShare Description: ARN of S3 File Share in Public Subnet 1 NugetStorageBucketName: Value: !Ref NugetStorageS3Bucket Description: >- Name of S3 bucket created for the storage of Nuget packages NugetStorageBucketArn: Value: !GetAtt NugetStorageS3Bucket.Arn Description: >- Arn of S3 bucket created for the storage of Nuget packages