# CloudFormation template deploys a VPC with subnets in 2 AZs, and EC2 instances in those AZs, from t4g instance family, running NTP, and placed behind a load balancer. AWSTemplateFormatVersion: 2010-09-09 Description: Deploys a Highly available NTP solution in AWS Parameters: VpcCidr: Type: String Default: 10.0.0.0/16 Description: The CIDR block for the VPC. PublicSubnet1Cidr: Type: String Default: 10.0.0.0/24 Description: The CIDR block for the public subnet in AZ1. PublicSubnet2Cidr: Type: String Default: 10.0.1.0/24 Description: The CIDR block for the public subnet in AZ2. PrivateSubnet1Cidr: Type: String Default: 10.0.2.0/24 Description: The CIDR block for the private subnet in AZ1. PrivateSubnet2Cidr: Type: String Default: 10.0.3.0/24 Description: The CIDR block for the private subnet in AZ2. AllowedAccessCidr: Type: String Default: 0.0.0.0/0 Description: The CIDR block to allow access to the load balancer. MinLength: "9" MaxLength: "18" AllowedPattern: (\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})/(\d{1,2}) ConstraintDescription: must be a valid IP CIDR range of the form x.x.x.x/x. InstanceType: Type: String Default: t4g.small Description: The EC2 instance type. AllowedValues: - t4g.micro - t4g.small - t4g.medium - t4g.large - t4g.xlarge - t4g.2xlarge - t4g.4xlarge - t4g.8xlarge - t4g.16xlarge ConstraintDescription: Must be a valid EC2 instance type. NtpServer: Type: String Default: 169.254.169.123 Description: The NTP server to use. Defaults to Amazon Time Sync Service AllowedPattern: ^[a-zA-Z0-9._-]+$ ConstraintDescription: Must be a valid NTP server. MaxLength: 255 MinLength: 1 InstanceCount: Type: Number Default: 2 Description: The number of instances to deploy. MinValue: 1 MaxValue: 6 ConstraintDescription: Must be a valid number of instances. AMI: Type: AWS::SSM::Parameter::Value Default: /aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-arm64 Description: Latest Amazon Linux 2023 AMI ID Resources: Vpc: Type: AWS::EC2::VPC Properties: CidrBlock: !Ref VpcCidr EnableDnsHostnames: true EnableDnsSupport: true Tags: - Key: Name Value: !Sub ${AWS::StackName}-VPC #IAM role for EC2 instances to use for Session Manager EC2Role: 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/service-role/AmazonEC2RoleforSSM InternetGateway: Type: AWS::EC2::InternetGateway Properties: Tags: - Key: Name Value: !Sub ${AWS::StackName}-InternetGateway AttachGateway: Type: AWS::EC2::VPCGatewayAttachment Properties: VpcId: !Ref Vpc InternetGatewayId: !Ref InternetGateway PublicSubnet1: Type: AWS::EC2::Subnet Properties: VpcId: !Ref Vpc CidrBlock: !Ref PublicSubnet1Cidr AvailabilityZone: !Select [ 0, !GetAZs ] MapPublicIpOnLaunch: false Tags: - Key: Name Value: !Sub ${AWS::StackName}-PublicSubnet1 PublicSubnet2: Type: AWS::EC2::Subnet Properties: VpcId: !Ref Vpc CidrBlock: !Ref PublicSubnet2Cidr AvailabilityZone: !Select [ 1, !GetAZs ] MapPublicIpOnLaunch: false Tags: - Key: Name Value: !Sub ${AWS::StackName}-PublicSubnet2 PrivateSubnet1: Type: AWS::EC2::Subnet Properties: VpcId: !Ref Vpc CidrBlock: !Ref PrivateSubnet1Cidr AvailabilityZone: !Select [ 0, !GetAZs ] Tags: - Key: Name Value: !Sub ${AWS::StackName}-PrivateSubnet1 PrivateSubnet2: Type: AWS::EC2::Subnet Properties: VpcId: !Ref Vpc CidrBlock: !Ref PrivateSubnet2Cidr AvailabilityZone: !Select [ 1, !GetAZs ] Tags: - Key: Name Value: !Sub ${AWS::StackName}-PrivateSubnet2 PublicRouteTable: Type: AWS::EC2::RouteTable Properties: VpcId: !Ref Vpc Tags: - Key: Name Value: !Sub ${AWS::StackName}-PublicRouteTable PublicRoute: Type: AWS::EC2::Route 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 PrivateRouteTable1: Type: AWS::EC2::RouteTable Properties: VpcId: !Ref Vpc Tags: - Key: Name Value: !Sub ${AWS::StackName}-PrivateRouteTable1 PrivateRouteTable2: Type: AWS::EC2::RouteTable Properties: VpcId: !Ref Vpc Tags: - Key: Name Value: !Sub ${AWS::StackName}-PrivateRouteTable2 PrivateSubnet1RouteTableAssociation: Type: AWS::EC2::SubnetRouteTableAssociation Properties: SubnetId: !Ref PrivateSubnet1 RouteTableId: !Ref PrivateRouteTable1 PrivateSubnet2RouteTableAssociation: Type: AWS::EC2::SubnetRouteTableAssociation Properties: SubnetId: !Ref PrivateSubnet2 RouteTableId: !Ref PrivateRouteTable2 # A security group to be used exclusively in the SSM Endpoint interface, with a self-referencing rule SSMSecurityGroup: Type: AWS::EC2::SecurityGroup Properties: GroupDescription: Allow SSM Endpoint traffic VpcId: !Ref Vpc Tags: - Key: Name Value: !Sub ${AWS::StackName}-SSMSecurityGroup SSMSecurityGroupIngress: Type: AWS::EC2::SecurityGroupIngress Properties: GroupId: !Ref SSMSecurityGroup IpProtocol: tcp FromPort: 443 ToPort: 443 SourceSecurityGroupId: !Ref SSMSecurityGroup Description: Self-referencing rule for SSM Endpoint traffic SSMSecurityGroupEgress: Type: AWS::EC2::SecurityGroupEgress Properties: GroupId: !Ref SSMSecurityGroup IpProtocol: tcp FromPort: 443 ToPort: 443 DestinationSecurityGroupId: !Ref SSMSecurityGroup Description: Self-referencing rule for SSM Endpoint traffic # The SSM Endpoints needed to get a session opened in a private subnet VPCSSMEndpoint: Type: AWS::EC2::VPCEndpoint Properties: ServiceName: !Sub "com.amazonaws.${AWS::Region}.ssm" VpcId: !Ref Vpc SubnetIds: - !Ref PrivateSubnet1 - !Ref PrivateSubnet2 SecurityGroupIds: - !Ref SSMSecurityGroup PrivateDnsEnabled: true VpcEndpointType: Interface SSMMessagesEndpoint: Type: AWS::EC2::VPCEndpoint Properties: ServiceName: !Sub "com.amazonaws.${AWS::Region}.ssmmessages" VpcId: !Ref Vpc SubnetIds: - !Ref PrivateSubnet1 - !Ref PrivateSubnet2 SecurityGroupIds: - !Ref SSMSecurityGroup PrivateDnsEnabled: true VpcEndpointType: Interface EC2SecurityGroup: Type: AWS::EC2::SecurityGroup Properties: GroupDescription: Allow NTP Traffic from NLB. VpcId: !Ref Vpc # Allow all outbound explictly SecurityGroupEgress: - IpProtocol: tcp FromPort: 0 ToPort: 65535 CidrIp: 0.0.0.0/0 Description: Allow all outbound traffic - NLB SecurityGroupIngress: - IpProtocol: udp FromPort: 123 ToPort: 123 SourceSecurityGroupId: !Ref NLBSecurityGroup Description: Allow NTP traffic from NLB - IpProtocol: tcp FromPort: 4460 ToPort: 4460 SourceSecurityGroupId: !Ref NLBSecurityGroup Description: Allow Health Check traffic from NLB IamInstanceProfile: Type: AWS::IAM::InstanceProfile Properties: InstanceProfileName: !Sub ${AWS::StackName}-${AWS::Region}-IamInstanceProfile Path: "/" Roles: - !Ref EC2Role LaunchTemplate: Type: AWS::EC2::LaunchTemplate Properties: LaunchTemplateName: !Sub ${AWS::StackName}-LaunchTemplate LaunchTemplateData: ImageId: !Ref AMI InstanceType: !Ref InstanceType SecurityGroupIds: - !Ref EC2SecurityGroup - !Ref SSMSecurityGroup IamInstanceProfile: Arn: !GetAtt - IamInstanceProfile - Arn UserData: Fn::Base64: !Sub | #!/bin/bash echo "server ${NtpServer} prefer iburst minpoll 4 maxpoll 4" >> /etc/chrony.conf # Enabling NTS purely for Health Check purposes, port TCP 4460 will not be externally exposed. KEY=/etc/pki/tls/private/server.key CERT=/etc/pki/tls/certs/server.crt IP=127.0.0.1 openssl req \ -newkey rsa:2048 \ -x509 \ -nodes \ -keyout /etc/pki/tls/private/server.key \ -new \ -out /etc/pki/tls/certs/server.crt \ -subj /CN=127.0.0.1 \ -reqexts SAN \ -extensions SAN \ -config <(cat /etc/pki/tls/openssl.cnf \ <(printf '[SAN]\nsubjectAltName=IP:127.0.0.1')) \ -sha256 \ -days 7300 chmod u=rw,g=,o= $KEY chmod u=rw,g=rw,o=r $CERT chown chrony:chrony $KEY $CERT echo "ntsserverkey $KEY" >> /etc/chrony.conf echo "ntsservercert $CERT" >> /etc/chrony.conf echo "ratelimit interval 3 burst 8 leak 2" >> /etc/chrony.conf echo "allow ${AllowedAccessCidr}" >> /etc/chrony.conf echo "clientloglimit 134217728" >> /etc/chrony.conf systemctl enable chronyd.service systemctl restart chronyd.service TagSpecifications: - ResourceType: instance Tags: - Key: Name Value: !Sub ${AWS::StackName}-Instance AutoScalingGroup: Type: AWS::AutoScaling::AutoScalingGroup Properties: AutoScalingGroupName: !Sub ${AWS::StackName}-AutoScalingGroup DesiredCapacity: !Ref InstanceCount HealthCheckType: ELB LaunchTemplate: LaunchTemplateId: !Ref LaunchTemplate Version: !GetAtt LaunchTemplate.LatestVersionNumber MaxSize: !Ref InstanceCount MinSize: !Ref InstanceCount VPCZoneIdentifier: - !Ref PrivateSubnet1 - !Ref PrivateSubnet2 TargetGroupARNs: - !Ref NTPTargetGroup #Target Group for NLB service NTP NTPTargetGroup: Type: AWS::ElasticLoadBalancingV2::TargetGroup Properties: Port: 123 Protocol: UDP VpcId: !Ref Vpc TargetType: instance HealthCheckIntervalSeconds: 30 HealthCheckProtocol: TCP HealthCheckPort: 4460 HealthCheckTimeoutSeconds: 5 HealthyThresholdCount: 2 UnhealthyThresholdCount: 2 Tags: - Key: Name Value: !Sub ${AWS::StackName}-TargetGroup #NLB Security Group NLBSecurityGroup: Type: AWS::EC2::SecurityGroup Properties: GroupDescription: Allow access to NLB VpcId: !Ref Vpc SecurityGroupEgress: - IpProtocol: tcp FromPort: 0 ToPort: 65535 CidrIp: 0.0.0.0/0 Description: Allow all outbound traffic - NLB - IpProtocol: udp FromPort: 0 ToPort: 65535 CidrIp: 0.0.0.0/0 Description: Allow all outbound traffic - NLB SecurityGroupIngress: - IpProtocol: udp FromPort: 123 ToPort: 123 CidrIp: !Ref AllowedAccessCidr Description: Allow NTP traffic from anywhere Tags: - Key: Name Value: !Sub ${AWS::StackName}-NLBSecurityGroup #NLB pointing to Target Group LoadBalancer: Type: AWS::ElasticLoadBalancingV2::LoadBalancer Properties: Type: network SecurityGroups: - !Ref NLBSecurityGroup Subnets: - !Ref PublicSubnet1 - !Ref PublicSubnet2 Tags: - Key: Name Value: !Sub ${AWS::StackName}-LoadBalancer #Listener for NLB NTP 123 ListenerNTP: Type: AWS::ElasticLoadBalancingV2::Listener Properties: DefaultActions: - TargetGroupArn: !Ref NTPTargetGroup Type: forward LoadBalancerArn: !Ref LoadBalancer Port: 123 Protocol: UDP Outputs: LoadBalancerARN: Value: !Ref LoadBalancer Description: The ARN of the load balancer.