AWSTemplateFormatVersion: 2010-09-09 Description: CloudFormation template to automate deploying AWS Client VPN Endpoints into the Biotech Blueprint. Parameters: pDMZSubnetA: Description: Subnet to associate the VPN Endpoint to. Typically a public/DMZ subnet. Type: 'AWS::SSM::Parameter::Value' Default: /BB/Networking/VPC/Research/Subnet/DMZ/A pLogGroup: Description: CloudWatch Log Group for Client VPN Connections Type: String Default: "/biotechblueprint/vpn" pLogStream: Description: CloudWatch Log Stream for Client VPN Connection logging Type: String Default: "VPNConnections" pLogRetentionInDays: Description: Amount of time in days to retain Client VPN Access logs. Type: Number Default: 365 pClientCidr: Description: CIDR Range to use for your VPN Clients. Type: String Default: "11.0.0.0/16" pParamStorePath: Description: Parameter store path for you VPN Endpoint (to be used for associations and routes). Leave the default if you are unsure. Type: String Default: "/BB/vpn/vpnendpointid" pTargetNetworkCidr: Description: CIDR Range to allow remote ingress access to connect to the Client VPN. This could be your corporate network CIDR range or 0.0.0.0/0 for public access. Type: String AllowedPattern: '((\d{1,3})\.){3}\d{1,3}/\d{1,2}' pVpcSecurityGroup: Description: Security group to associate VPN Endpoint with Type: 'AWS::SSM::Parameter::Value' Default: /BB/Networking/VPC/Research/InformaticsAccessSG pVpcId: Description: VPC that DMZ Subnet belongs to. Type: 'AWS::SSM::Parameter::Value' Default: /BB/Networking/VPC/Research pVpcAandBClassProvidingDns: Description: The A and B class octet values for the VPC that will provide DNS to VPN clients. Type: 'AWS::SSM::Parameter::Value' Default: /BB/Networking/VPC/Research/AandBblock pVpcAandBClassToRoutableNetworks: Description: List of A and B class octets that will be NATed to the Client VPN endpoint. These become route entries in the router config that your clients will connect to. Type: AWS::SSM::Parameter::Value Default: /BB/Networking/VPN/RoutableOctets QSS3BucketName: Default: aws-quickstart Description: S3 bucket name for the Quick Start assets. Quick Start bucket name can include numbers, lowercase letters, uppercase letters, and hyphens (-). It cannot start or end with a hyphen (-). Type: String QSS3BucketRegion: Default: 'us-east-1' Description: 'The AWS Region where the Quick Start S3 bucket (QSS3BucketName) is hosted. When using your own bucket, you must specify this value.' Type: String QSS3KeyPrefix: Default: quickstart-biotech-blueprint/ Description: S3 key prefix for the Quick Start assets. Quick Start key prefix can include numbers, lowercase letters, uppercase letters, hyphens (-), and forward slash (/). Type: String Conditions: UsingDefaultBucket: !Equals [!Ref QSS3BucketName, 'aws-quickstart'] Resources: rCreateClientVPNEndpointLambda: Type: AWS::Lambda::Function DependsOn: - rClientVpnLogStream - rVpnConfigBucket Properties: Handler: index.handlerShim Runtime: python3.6 Timeout: 240 MemorySize: 1024 Role: !GetAtt rCreateClientVpnEndpointLambdaRole.Arn Code: ZipFile: | import urllib.request import sys import json import boto3 def handlerShim(event, context): try: print(event) tmpFileName = "/tmp/handlermeat.py" s3 = boto3.client('s3') s3.download_file(event['ResourceProperties']['HandlerShimBucket'], event['ResourceProperties']['HandlerShimObjectName'], tmpFileName) sys.path.insert(0, '/tmp/') import handlermeat handlermeat.lambda_handler(event, context) except: sendSignal(event, context, FAILED, { 'error': 'Failed to reach target script.'}) SUCCESS = "SUCCESS" FAILED = "FAILED" def sendSignal(event, context, responseStatus, responseData, physicalResourceId=None, noEcho=False): responseUrl = event['ResponseURL'] responseBody = {} responseBody['Status'] = responseStatus responseBody['Reason'] = 'See the details in CloudWatch Log Stream: ' + context.log_stream_name responseBody['PhysicalResourceId'] = physicalResourceId or context.log_stream_name responseBody['StackId'] = event['StackId'] responseBody['RequestId'] = event['RequestId'] responseBody['LogicalResourceId'] = event['LogicalResourceId'] responseBody['NoEcho'] = noEcho responseBody['Data'] = responseData json_responseBody = json.dumps(responseBody) headers = { 'content-type' : '', 'content-length' : str(len(json_responseBody)) } try: response = requests.put(responseUrl, data=json_responseBody, headers=headers) except Exception as e: error = e rVpnConfigBucket: Type: AWS::S3::Bucket Properties: BucketEncryption: ServerSideEncryptionConfiguration: - ServerSideEncryptionByDefault: SSEAlgorithm: AES256 rCustomLambdaResourceTrigger: Type: Custom::rCustomLambdaResourceTrigger Properties: ServiceToken: !GetAtt rCreateClientVPNEndpointLambda.Arn LogGroup: !Ref pLogGroup LogStream: !Ref pLogStream ClientCidr: !Ref pClientCidr ParamStorePath: !Ref pParamStorePath SubnetToAssociate: !Ref pDMZSubnetA TargetNetworkCidr: !Ref pTargetNetworkCidr VpnConfigBucket: !Ref rVpnConfigBucket VpcSecurityGroup: !Ref pVpcSecurityGroup VpcId: !Ref pVpcId DNSServerIP: !Sub "${pVpcAandBClassProvidingDns}.0.2" VpcAandBClassToRoutableNetworks: !Ref pVpcAandBClassToRoutableNetworks HandlerShimBucket: !If [UsingDefaultBucket, !Sub '${QSS3BucketName}-${AWS::Region}', !Ref QSS3BucketName] HandlerShimObjectName: !Sub "${QSS3KeyPrefix}scripts/clientvpnendpoint-customlambdaresource.py" HandlerShimUrl: !Sub - 'https://${S3Bucket}.s3.${S3Region}.${AWS::URLSuffix}/${QSS3KeyPrefix}scripts/clientvpnendpoint-customlambdaresource.py' - S3Region: !If [UsingDefaultBucket, !Ref 'AWS::Region', !Ref QSS3BucketRegion] S3Bucket: !If [UsingDefaultBucket, !Sub '${QSS3BucketName}-${AWS::Region}', !Ref QSS3BucketName] rClientVPNLogGroup: Type: AWS::Logs::LogGroup Properties: LogGroupName: !Ref pLogGroup RetentionInDays: !Ref pLogRetentionInDays rClientVpnLogStream: DependsOn: - rClientVPNLogGroup Type: AWS::Logs::LogStream Properties: LogGroupName: !Ref pLogGroup LogStreamName: !Ref pLogStream rCreateClientVpnEndpointLambdaRole: DependsOn: - rVpnConfigBucket Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Principal: Service: - lambda.amazonaws.com Action: - sts:AssumeRole Path: / Policies: - PolicyName: BiotechBlueprintManageClientVPNLambdaFunctionPolicy PolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Action: - s3:GetObject Resource: !Sub - 'arn:aws:s3:::${S3Bucket}/${QSS3KeyPrefix}scripts/clientvpnendpoint-customlambdaresource.py' - S3Bucket: !If [UsingDefaultBucket, !Sub '${QSS3BucketName}-${AWS::Region}', !Ref QSS3BucketName] - Effect: Allow Action: - ssm:GetParameter - ssm:PutParameter Resource: !Sub "arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter${pParamStorePath}*" - Effect: Allow Action: - acm:ImportCertificate Resource: "*" - Effect: Allow Action: - ec2:CreateClientVpnEndpoint - ec2:DeleteClientVpnEndpoint - ec2:AssociateClientVpnTargetNetwork - ec2:DisassociateClientVpnTargetNetwork - ec2:CreateClientVpnRoute - ec2:AuthorizeClientVpnIngress - ec2:ExportClientVpnClientConfiguration - ec2:ApplySecurityGroupsToClientVpnTargetNetwork Resource: "*" - Effect: Allow Action: - logs:CreateLogGroup - logs:CreateLogStream - logs:PutLogEvents Resource: "arn:aws:logs:*:*:*" - Effect: Allow Action: - s3:* Resource: !Sub "${rVpnConfigBucket.Arn}/*" - Effect: Allow Action: - iam:CreateServiceLinkedRole Resource: !Sub "arn:aws:iam::${AWS::AccountId}:role/aws-service-role/clientvpn.amazonaws.com/AWSServiceRoleForClientVPN"