# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"). # You may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. AWSTemplateFormatVersion: '2010-09-09' Transform: AWS::Serverless-2016-10-31 Description: Amazon Transcribe Live Call Analytics with Agent Assist - Amazon Chime SDK Voice Connector with Asterisk demo server Parameters: InstanceType: Description: Instance type to launch EC2 instances. Type: String Default: c6g.large AllowedValues: [c6g.medium, c6g.large, c6g.xlarge, c6g.2xlarge] LatestImageId: Type: AWS::SSM::Parameter::Value Default: '/aws/service/canonical/ubuntu/server/jammy/stable/current/arm64/hvm/ebs-gp2/ami-id' SoftPhoneCIDR: Description: CIDR block for soft phone access (optional) Type: String AllowedPattern: ^([0-9]{1,3}.){3}[0-9]{1,3}(/([0-9]|[1-2][0-9]|3[0-2]))$||"" ConstraintDescription: Must be a valid public CIDR block in the form x.x.x.x/y DemoAsteriskAgentAudioURL: Type: String Default: https://raw.githubusercontent.com/aws-samples/amazon-transcribe-live-call-analytics/main/lca-chimevc-stack/demo-audio/agent.wav Description: URL for audio (agent.wav) file download for demo Asterisk server. Ec2Troubleshoot: Description: > Controls whether cloud init errors cause the stack to fail. Set to true when troubleshooting. Type: String Default: false AllowedValues: - true - false Resources: AsteriskVPC: Type: AWS::EC2::VPC Properties: CidrBlock: 10.0.0.0/16 EnableDnsHostnames: true EnableDnsSupport: true InstanceTenancy: default Tags: - Key: Name Value: LCAAsterisk/VPC AsteriskSubnet1: Type: AWS::EC2::Subnet Properties: CidrBlock: 10.0.0.0/24 VpcId: Ref: AsteriskVPC AvailabilityZone: Fn::Select: - 0 - Fn::GetAZs: '' MapPublicIpOnLaunch: true Tags: - Key: aws-cdk:subnet-name Value: asterisk - Key: aws-cdk:subnet-type Value: Public - Key: Name Value: LCAAsterisk/VPC/asteriskSubnet1 AsteriskSubnet1RouteTable: Type: AWS::EC2::RouteTable Properties: VpcId: Ref: AsteriskVPC Tags: - Key: Name Value: LCAAsterisk/VPC/asteriskSubnet1 AsteriskSubnet1RouteTableAssociation: Type: AWS::EC2::SubnetRouteTableAssociation Properties: RouteTableId: Ref: AsteriskSubnet1RouteTable SubnetId: Ref: AsteriskSubnet1 AsteriskSubnet1DefaultRoute: Type: AWS::EC2::Route Properties: RouteTableId: Ref: AsteriskSubnet1RouteTable DestinationCidrBlock: 0.0.0.0/0 GatewayId: Ref: VPCInternetGateway DependsOn: - VPCGatewayAttachment AsteriskSubnet2: Type: AWS::EC2::Subnet Properties: CidrBlock: 10.0.1.0/24 VpcId: Ref: AsteriskVPC AvailabilityZone: Fn::Select: - 1 - Fn::GetAZs: '' MapPublicIpOnLaunch: true Tags: - Key: aws-cdk:subnet-name Value: asterisk - Key: aws-cdk:subnet-type Value: Public - Key: Name Value: LCAAsterisk/VPC/asteriskSubnet2 AsteriskSubnet2RouteTable: Type: AWS::EC2::RouteTable Properties: VpcId: Ref: AsteriskVPC Tags: - Key: Name Value: LCAAsterisk/VPC/asteriskSubnet2 AsteriskSubnet2RouteTableAssociation: Type: AWS::EC2::SubnetRouteTableAssociation Properties: RouteTableId: Ref: AsteriskSubnet2RouteTable SubnetId: Ref: AsteriskSubnet2 AsteriskSubnet2DefaultRoute: Type: AWS::EC2::Route Properties: RouteTableId: Ref: AsteriskSubnet2RouteTable DestinationCidrBlock: 0.0.0.0/0 GatewayId: Ref: VPCInternetGateway DependsOn: - VPCGatewayAttachment VPCInternetGateway: Type: AWS::EC2::InternetGateway Properties: Tags: - Key: Name Value: LCAAsterisk/VPC VPCGatewayAttachment: Type: AWS::EC2::VPCGatewayAttachment Properties: VpcId: Ref: AsteriskVPC InternetGatewayId: Ref: VPCInternetGateway AsteriskSecurityGroup: Type: AWS::EC2::SecurityGroup Properties: GroupDescription: Security Group for Asterisk Server SecurityGroupEgress: - CidrIp: 0.0.0.0/0 Description: Allow all outbound traffic by default IpProtocol: '-1' SecurityGroupIngress: - CidrIp: 3.80.16.0/23 Description: Allow Amazon Chime SDK Voice Connector Signaling Access FromPort: 5060 IpProtocol: udp ToPort: 5060 - CidrIp: 3.80.16.0/23 Description: Allow Amazon Chime SDK Voice Connector Signaling Access FromPort: 5060 IpProtocol: tcp ToPort: 5061 - CidrIp: 99.77.253.0/24 Description: Allow Amazon Chime SDK Voice Connector Signaling Access FromPort: 5060 IpProtocol: udp ToPort: 5060 - CidrIp: 99.77.253.0/24 Description: Allow Amazon Chime SDK Voice Connector Signaling Access FromPort: 5060 IpProtocol: tcp ToPort: 5061 - CidrIp: 99.77.253.0/24 Description: Allow Amazon Chime SDK Voice Connector Signaling Access FromPort: 5000 IpProtocol: udp ToPort: 65000 - CidrIp: 3.80.16.0/23 Description: Allow Amazon Chime SDK Voice Connector Media Access FromPort: 5000 IpProtocol: udp ToPort: 65000 - CidrIp: 52.55.62.128/25 Description: Allow Amazon Chime SDK Voice Connector Media Access FromPort: 1024 IpProtocol: udp ToPort: 65535 - CidrIp: 52.55.63.0/25 Description: Allow Amazon Chime SDK Voice Connector Media Access FromPort: 1024 IpProtocol: udp ToPort: 65535 - CidrIp: 34.212.95.128/25 Description: Allow Amazon Chime SDK Voice Connector Media Access FromPort: 1024 IpProtocol: udp ToPort: 65535 - CidrIp: 34.223.21.0/25 Description: Allow Amazon Chime SDK Voice Connector Media Access FromPort: 1024 IpProtocol: udp ToPort: 65535 - CidrIp: !If [SoftPhoneCIDRExists, !Ref SoftPhoneCIDR, !Ref 'AWS::NoValue'] Description: Soft Phone CIDR IpProtocol: '-1' VpcId: Ref: AsteriskVPC InstanceRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Statement: - Action: sts:AssumeRole Effect: Allow Principal: Service: Fn::Join: - '' - - ec2. - Ref: AWS::URLSuffix Version: '2012-10-17' ManagedPolicyArns: - Fn::Join: - '' - - 'arn:' - Ref: AWS::Partition - :iam::aws:policy/AmazonSSMManagedInstanceCore InstanceDefaultPolicy: Type: AWS::IAM::Policy Properties: PolicyDocument: Statement: - Action: - ssm:DescribeParameters - ssm:GetParameters - ssm:GetParameter - ssm:GetParameterHistory Effect: Allow Resource: Fn::Join: - '' - - 'arn:' - Ref: AWS::Partition - ':ssm:' - Ref: AWS::Region - ':' - Ref: AWS::AccountId - :parameter/ - Ref: AWS::StackName - '*' Version: '2012-10-17' PolicyName: InstanceDefaultPolicy Roles: - Ref: InstanceRole # Allows cfn-init to signal back to the cloudformation stack # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/cfn-helper-scripts-reference.html CfnRolePolicy: Type: AWS::IAM::Policy Properties: PolicyName: CfnDescribeSignal PolicyDocument: Statement: - Action: - cloudformation:DescribeStackResource - cloudformation:SignalResource Effect: Allow Resource: !Ref AWS::StackId Roles: - !Ref InstanceRole EIP: Type: AWS::EC2::EIP createChimeLambdaRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Statement: - Action: sts:AssumeRole Effect: Allow Principal: Service: lambda.amazonaws.com Version: '2012-10-17' ManagedPolicyArns: - Fn::Join: - '' - - 'arn:' - Ref: AWS::Partition - :iam::aws:policy/service-role/AWSLambdaBasicExecutionRole Policies: - PolicyDocument: Statement: - Action: - chime:CreateVoiceConnector - chime:DeleteVoiceConnector - chime:GetPhoneNumberOrder - chime:SearchAvailablePhoneNumbers - chime:CreatePhoneNumberOrder - chime:GetPhoneNumberOrder - chime:DeletePhoneNumber - chime:DisassociatePhoneNumbersFromVoiceConnector - chime:AssociatePhoneNumbersWithVoiceConnector - chime:PutVoiceConnectorTermination - chime:PutVoiceConnectorStreamingConfiguration - chime:PutVoiceConnectorOrigination - chime:PutVoiceConnectorLoggingConfiguration - cloudformation:DescribeStacks - iam:CreateServiceLinkedRole - iam:PutRolePolicy - logs:ListLogDeliveries - logs:DescribeLogGroups - logs:CreateLogDelivery - logs:GetLogDelivery - logs:DeleteLogDelivery - ssm:GetParameter - ssm:PutParameter - ssm:DeleteParameter Effect: Allow Resource: '*' Version: '2012-10-17' PolicyName: chimePolicy createVCLambda: Type: AWS::Lambda::Function Properties: Code: ZipFile: | import cfnresponse import json import boto3 import time import uuid import urllib.parse chime = boto3.client('chime-sdk-voice') cloudformation = boto3.resource("cloudformation") ssm_client = boto3.client('ssm') def authorizeEIP (voiceConnectorId, elasticIP): response = chime.put_voice_connector_origination( VoiceConnectorId=voiceConnectorId, Origination={ 'Routes': [ { 'Host': elasticIP, 'Port': 5060, 'Protocol': 'UDP', 'Priority': 1, 'Weight': 1 }, ], 'Disabled': False } ) print(response) response = chime.put_voice_connector_termination( VoiceConnectorId=voiceConnectorId, Termination={ 'CpsLimit': 1, 'CallingRegions': [ 'US', ], 'CidrAllowedList': [ elasticIP + '/32', ], 'Disabled': False } ) print(response) def getPhoneNumber (): search_response = chime.search_available_phone_numbers( # AreaCode='string', # City='string', # Country='string', State='IL', # TollFreePrefix='string', MaxResults=1 ) phoneNumberToOrder = search_response['E164PhoneNumbers'][0] print ('Phone Number: ' + phoneNumberToOrder) phone_order = chime.create_phone_number_order( ProductType='VoiceConnector', E164PhoneNumbers=[ phoneNumberToOrder, ] ) print ('Phone Order: ' + str(phone_order)) check_phone_order = chime.get_phone_number_order( PhoneNumberOrderId=phone_order['PhoneNumberOrder']['PhoneNumberOrderId'] ) order_status = check_phone_order['PhoneNumberOrder']['Status'] timeout = 0 while not order_status == 'Successful': timeout += 1 print('Checking status: ' + str(order_status)) time.sleep(5) check_phone_order = chime.get_phone_number_order( PhoneNumberOrderId=phone_order['PhoneNumberOrder']['PhoneNumberOrderId'] ) print('Current Response: ' + str(check_phone_order)) order_status = check_phone_order['PhoneNumberOrder']['Status'] if timeout == 15: return 'Could not get phone number' return phoneNumberToOrder def createVoiceConnector (region, phoneNumber, event): print("Creating voice connector...") print(str(uuid.uuid1())) print(region) response = chime.create_voice_connector( Name='LCA-Trunk' + str(uuid.uuid1()), AwsRegion=region, RequireEncryption=False ) voiceConnectorId = response['VoiceConnector']['VoiceConnectorId'] outboundHostName = response['VoiceConnector']['OutboundHostName'] response = chime.associate_phone_numbers_with_voice_connector( VoiceConnectorId=voiceConnectorId, E164PhoneNumbers=[ phoneNumber, ], ForceAssociate=True ) streamingConfiguration = { 'DataRetentionInHours': 1, 'Disabled': False, 'StreamingNotificationTargets': [ { 'NotificationTarget': 'EventBridge' }, ] } response = chime.put_voice_connector_streaming_configuration( VoiceConnectorId=voiceConnectorId, StreamingConfiguration=streamingConfiguration ) print('Configuration created.') chime.put_voice_connector_logging_configuration( VoiceConnectorId=voiceConnectorId, LoggingConfiguration={ 'EnableSIPLogs': True, 'EnableMediaMetricLogs': True } ) voiceConnector = { 'voiceConnectorId': voiceConnectorId, 'outboundHostName': outboundHostName, 'phoneNumber': phoneNumber} return voiceConnector def handler(event, context): print(json.dumps(event)) stack_name = event["ResourceProperties"]["stackName"] phone_number_parameter = '/' + stack_name + '/phoneNumber' voice_connector_parameter = '/' + stack_name + '/voiceConnector' print('Phone Number Parameter: ' + phone_number_parameter) print('Voice Connector Parameter: ' + voice_connector_parameter) if event['RequestType'] == 'Create': try: physical_id = 'VoiceConnectorResources' region = event['ResourceProperties']['region'] elasticIP = event['ResourceProperties']['eip'] newPhoneNumber = getPhoneNumber() voiceConnector = createVoiceConnector(region, newPhoneNumber, event) authorizeEIP(voiceConnector['voiceConnectorId'], elasticIP) ssm_client.put_parameter( Name=phone_number_parameter, Value=newPhoneNumber, Type='String', Overwrite=True ) ssm_client.put_parameter( Name=voice_connector_parameter, Value=voiceConnector['voiceConnectorId'], Type='String', Overwrite=True ) cfnresponse.send(event, context, cfnresponse.SUCCESS, voiceConnector, physical_id) except Exception as e: error = f'Exception thrown: {e}. Please see https://github.com/aws-samples/amazon-transcribe-live-call-analytics/blob/main/TROUBLESHOOTING.md for more information.' print(error) # print(e.Message) cfnresponse.send(event, context, cfnresponse.FAILED, {}, reason=error ) elif event['RequestType'] == 'Delete': try: physical_id = 'VoiceConnectorResources' phone_number = ssm_client.get_parameter(Name=phone_number_parameter)['Parameter']['Value'] voice_connector = ssm_client.get_parameter(Name=voice_connector_parameter)['Parameter']['Value'] print('Phone Number to delete: ' + phone_number) print('Voice Connector to delete: ' + voice_connector) chime.disassociate_phone_numbers_from_voice_connector( VoiceConnectorId=voice_connector, E164PhoneNumbers=[ phone_number, ] ) time.sleep(10) response = {} response['delete_vc'] = chime.delete_voice_connector(VoiceConnectorId=voice_connector) parsed_number = urllib.parse.quote_plus(phone_number) time.sleep(10) response['delete_phone'] = chime.delete_phone_number(PhoneNumberId=parsed_number) ssm_client.delete_parameter( Name=phone_number_parameter, ) ssm_client.delete_parameter( Name=voice_connector_parameter, ) cfnresponse.send(event, context, cfnresponse.SUCCESS, response, physical_id) except Exception as e: error = f'Exception thrown: {e}. Please see https://github.com/aws-samples/amazon-transcribe-live-call-analytics/blob/main/TROUBLESHOOTING.md for more information.' print(error) cfnresponse.send(event, context, cfnresponse.FAILED, {}, reason=error ) else: responseData = {'Message': 'Update is no-op. Returning success status.'} cfnresponse.send(event, context, cfnresponse.SUCCESS, responseData) Role: Fn::GetAtt: - createChimeLambdaRole - Arn Handler: index.handler Runtime: python3.10 Timeout: 300 voiceConnectorResource: Type: AWS::CloudFormation::CustomResource Properties: ServiceToken: Fn::GetAtt: - createVCLambda - Arn region: Ref: AWS::Region eip: Ref: EIP stackName: Ref: AWS::StackName UpdateReplacePolicy: Delete DeletionPolicy: Delete InstanceProfile: Type: AWS::IAM::InstanceProfile Properties: Roles: - Ref: InstanceRole AsteriskInstanceTemplate: Type: AWS::EC2::LaunchTemplate Properties: LaunchTemplateData: ImageId: !Ref LatestImageId InstanceType: !Ref InstanceType MetadataOptions: HttpTokens: required HttpPutResponseHopLimit: 2 AsteriskInstance: Type: AWS::EC2::Instance CreationPolicy: ResourceSignal: Timeout: PT10M Count: '1' Properties: LaunchTemplate: LaunchTemplateId: !Ref AsteriskInstanceTemplate Version: !GetAtt AsteriskInstanceTemplate.LatestVersionNumber AvailabilityZone: Fn::Select: - 0 - Fn::GetAZs: '' IamInstanceProfile: Ref: InstanceProfile SecurityGroupIds: - Fn::GetAtt: - AsteriskSecurityGroup - GroupId SubnetId: Ref: AsteriskSubnet1 Tags: - Key: Name Value: !Sub 'AsteriskInstance-${AWS::StackName}' UserData: Fn::Base64: !Sub | Content-Type: multipart/mixed; boundary="//" MIME-Version: 1.0 --// Content-Type: text/cloud-config; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename="cloud-config.txt" #cloud-config cloud_final_modules: - [scripts-user, always] --// Content-Type: text/x-shellscript; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename="userdata.txt" #!/bin/bash -xe echo "remove old userdata for upgraded servers, if it exists" rm -f /var/lib/cloud/instance/scripts/part-001 #!/bin/bash sudo apt-get update while fuser /var/lib/dpkg/lock >/dev/null 2>&1 ; do sleep 1 ; done sudo apt-get install unzip asterisk python3-pip jq -y curl "https://awscli.amazonaws.com/awscli-exe-linux-aarch64.zip" -o "awscliv2.zip" unzip -q awscliv2.zip sudo ./aws/install mkdir -p /opt/aws/ sudo pip3 install https://s3.amazonaws.com/cloudformation-examples/aws-cfn-bootstrap-py3-latest.tar.gz sudo ln -s /usr/local/init/ubuntu/cfn-hup /etc/init.d/cfn-hup /usr/local/bin/cfn-init -v --stack ${AWS::StackName} --resource AsteriskInstance --configsets full_install --region ${AWS::Region} /usr/local/bin/cfn-signal -e $? --stack ${AWS::StackName} --resource AsteriskInstance --region ${AWS::Region} echo ========================================= echo Create crontab to reload asterisk hourly echo ========================================= echo '0 * * * * /sbin/asterisk -rx "core reload"' > /etc/asterisk/crontab.txt # every hour (doesn't disrupt in-progress calls) crontab /etc/asterisk/crontab.txt echo ================================ echo Create asterisk configuration echo ================================ INSTANCE_ID=$( curl http://169.254.169.254/latest/meta-data/instance-id) IP=$( curl http://169.254.169.254/latest/meta-data/public-ipv4 ) REGION=$( curl http://169.254.169.254/latest/meta-data/placement/region ) PHONE_NUMBER=$( aws ssm get-parameter --name /${AWS::StackName}/phoneNumber --region $REGION | jq -r '.Parameter.Value' ) VOICE_CONNECTOR=$( aws ssm get-parameter --name /${AWS::StackName}/voiceConnector --region $REGION | jq -r '.Parameter.Value' ) curl -L ${DemoAsteriskAgentAudioURL} --output /var/lib/asterisk/sounds/agent.wav echo Phone Number: $PHONE_NUMBER echo Voice Connector: $VOICE_CONNECTOR echo Instance ID: $INSTANCE_ID sed -i "s/PUBLIC_IP/$PUBLIC_IP/g" /etc/asterisk/pjsip.conf sed -i "s/PHONE_NUMBER/$PHONE_NUMBER/g" /etc/asterisk/pjsip.conf sed -i "s/VOICE_CONNECTOR/$VOICE_CONNECTOR/g" /etc/asterisk/pjsip.conf sed -i "s/INSTANCE_ID/$INSTANCE_ID/g" /etc/asterisk/pjsip.conf groupadd asterisk || echo "Group already exists" useradd -r -d /var/lib/asterisk -g asterisk asterisk || echo "User already exists" usermod -aG audio,dialout asterisk chown -R asterisk.asterisk /etc/asterisk chown -R asterisk.asterisk /var/lib/asterisk chown -R asterisk.asterisk /var/log/asterisk chown -R asterisk.asterisk /var/spool/asterisk echo ================================ echo Start asterisk and exit echo ================================ systemctl restart asterisk --//-- Metadata: AWS::CloudFormation::Init: configSets: full_install: - install_and_enable_cfn_hup install_and_enable_cfn_hup: files: /etc/cfn/cfn-hup.conf: content: !Sub | [main] stack=${AWS::StackId} region=${AWS::Region} mode: '000400' owner: root group: root /etc/cfn/hooks.d/cfn-auto-reloader.conf: content: !Sub | [cfn-auto-reloader-hook] triggers=post.update path=Resources.AsteriskInstance.Metadata.AWS::CloudFormation::Init action=/opt/aws/bin/cfn-init -v --stack ${AWS::StackName} --resource AsteriskInstance --region ${AWS::Region} --configsets InstallAndRun mode: '000400' owner: root group: root /lib/systemd/system/cfn-hup.service: content: !Sub | [Unit] Description=cfn-hup daemon [Service] Type=simple ExecStart=/usr/local/bin/cfn-hup Restart=always [Install] WantedBy=multi-user.target /etc/asterisk/pjsip.conf: content: !Sub | [udp] type=transport protocol=udp bind=0.0.0.0 external_media_address=PUBLIC_IP external_signaling_address=PUBLIC_IP allow_reload=yes [VoiceConnector] type=endpoint context=from-voiceConnector transport=udp disallow=all allow=ulaw aors=VoiceConnector direct_media=no ice_support=yes force_rport=yes [VoiceConnector] type=identify endpoint=VoiceConnector match=VOICE_CONNECTOR.voiceconnector.chime.aws [VoiceConnector] type=aor contact=sip:VOICE_CONNECTOR.voiceconnector.chime.aws [PHONE_NUMBER] type=endpoint context=from-phone disallow=all allow=ulaw transport=udp auth=PHONE_NUMBER aors=PHONE_NUMBER send_pai=yes direct_media=no rewrite_contact=yes ice_support=yes force_rport=yes [PHONE_NUMBER] type=auth auth_type=userpass password=INSTANCE_ID username=PHONE_NUMBER [PHONE_NUMBER] type=aor max_contacts=5 /etc/asterisk/extensions.conf: content: !Sub | [general] static=yes writeprotect=no clearglobalvars=no [catch-all] exten => _[+0-9].,1,Answer() exten => _[+0-9].,n,Wait(1) exten => _[+0-9].,n,Playback(/var/lib/asterisk/sounds/agent) exten => _[+0-9].,n,Wait(1) exten => _[+0-9].,n,echo() exten => _[+0-9].,n,Wait(1) exten => _[+0-9].,n,Hangup() [from-phone] include => outbound_phone [outbound_phone] exten => _+X.,1,NoOP(Outbound Normal) same => n,Dial(PJSIP/\${!EXTEN}@VoiceConnector,20) same => n,Congestion [from-voiceConnector] include => phones include => catch-all [phones] exten => $PhoneNumber,1,Dial(PJSIP/PHONE_NUMBER) /etc/asterisk/asterisk.conf: content: !Sub | [options] runuser = asterisk rungroup = asterisk /etc/asterisk/logger.conf: content: !Sub | [general] [logfiles] console = verbose,notice,warning,error messages = notice,warning,error commands: 01enable_cfn_hup: command: systemctl enable cfn-hup.service 02start_cfn_hup: command: systemctl start cfn-hup.service DependsOn: - InstanceDefaultPolicy - InstanceRole EIPAssociation: Type: AWS::EC2::EIPAssociation Properties: EIP: Ref: EIP InstanceId: Ref: AsteriskInstance Conditions: SoftPhoneCIDRExists: !Not [!Equals [!Ref SoftPhoneCIDR, '']] Outputs: IPAddress: Value: Fn::GetAtt: - AsteriskInstance - PublicIp InstanceID: Value: Ref: AsteriskInstance PhoneNumber: Value: Fn::GetAtt: - voiceConnectorResource - phoneNumber VoiceConnector: Value: Fn::GetAtt: - voiceConnectorResource - voiceConnectorId