From 5f7e669eb95c266552c7a625ff8e08c544ee2c34 Mon Sep 17 00:00:00 2001 From: James Weakley Date: Sat, 30 Nov 2019 21:43:52 +1100 Subject: [PATCH 1/4] Cloudformation template --- infra/cfn/CLOUDFORMATION.md | 30 +++ infra/cfn/snowalert.yaml | 462 ++++++++++++++++++++++++++++++++++++ 2 files changed, 492 insertions(+) create mode 100644 infra/cfn/CLOUDFORMATION.md create mode 100644 infra/cfn/snowalert.yaml diff --git a/infra/cfn/CLOUDFORMATION.md b/infra/cfn/CLOUDFORMATION.md new file mode 100644 index 000000000..e798bd0e1 --- /dev/null +++ b/infra/cfn/CLOUDFORMATION.md @@ -0,0 +1,30 @@ +## Cloudformation template + +The template found inside `snowalert.yaml` contains the equivalent infrastructure produced by the k8s/terraform templates, but expressed natively for AWS. + +As such, it creates a fargate ECS cluster for the SnowAlert alert mechanism (using cloudwatch cron trigger). It optionally creates another fargate cluster for the SnowAlert Web UI, complete with an application load balancer, certificate, and Route53 DNS record. + +To prevent the creation of the SnowAlert Web UI, set `DeployWebUi` to "false". + +The most notable difference is that secrets are retrieved by ECS from AWS SSM rather than passed in as parameters. + +Prerequisites: +1) If the web UI is being deployed, a route 53 hosted zone and ACS certificate must already exist. +2) The ID of a VPC and two subnets must be known prior. +3) You must have stored your SnowAlert configuration in the following SSM parameters (change the values for "SnowAlert" or "master" if you plan to override ResourcesPrefix or Slice): +``` +/SnowAlert/master/PRIVATE_KEY +/SnowAlert/master/PRIVATE_KEY_PASSWORD +/SnowAlert/master/SA_USER +/SnowAlert/master/SA_WAREHOUSE +/SnowAlert/master/SA_DATABASE +/SnowAlert/master/SA_ROLE +/SnowAlert/master/SLACK_API_TOKEN +/SnowAlert/master/SNOWFLAKE_ACCOUNT +``` + +Assume AWS CLI has been configured with default region and credentials, you can provision the stack like so: +``` +aws cloudformation create-stack --template-body snowalert.yaml --stack-name SnowAlert-Prod --capabilities CAPABILITY_IAM --parameters ParameterKey=Vpc,ParameterValue=vpc-a1234567 ParameterKey=SubnetOne,ParameterValue=subnet-1ab23456 ParameterKey=SubnetTwo,ParameterValue=subnet-2cd34567 ParameterKey=CertificateArn,ParameterValue=arn:aws:acm:ap-southeast-2:123456789012:certificate/461b5dc1-443c-46ef-a5df-a6e6a7190d67 ParameterKey=InstanceFqdn,ParameterValue=snowalert.mydomain.com ParameterKey=InstanceHostedZone,ParameterValue=mydomain.com. +``` + diff --git a/infra/cfn/snowalert.yaml b/infra/cfn/snowalert.yaml new file mode 100644 index 000000000..a6ab21270 --- /dev/null +++ b/infra/cfn/snowalert.yaml @@ -0,0 +1,462 @@ +AWSTemplateFormatVersion: '2010-09-09' +Description: Deploys AWS infrastructure to support SnowAlert (https://github.com/snowflakedb/SnowAlert) +Parameters: + ResourcesPrefix: + Type: String + Default: SnowAlert + Description: A common prefix for all resource names + AlertsContainerCpu: + Type: Number + Default: 1024 + Description: How much CPU to give the alert execution container. 1024 is 1 CPU + AlertsContainerMemory: + Type: Number + Default: 2048 + Description: How much memory in megabytes to give the alert execution container + CronScheduleExpression: + Type: String + Default: cron(10 * * * ? *) + Description: The Cron schedule definition for triggering rule evaluation + Vpc: + Type: String + Default: "" + Description: The VPC ID to deploy to + SubnetOne: + Type: String + Default: "" + Description: First of two subnets to run tasks on within the VPC + SubnetTwo: + Type: String + Default: "" + Description: Second of two subnets to run tasks on within the VPC + DeployWebUi: + Type: String + Default: true + Description: Flags whether or not to deploy the SnowAlert Web UI, which includes another Fargate cluster and ALB (with DNS + cert) + AllowedValues: [true, false] + WebUiContainerCpu: + Type: Number + Default: 1024 + Description: How much CPU to give the Web UI container. 1024 is 1 CPU + WebUiContainerMemory: + Type: Number + Default: 2048 + Description: How much memory in megabytes to give the Web UI container + CertificateArn: + Type: String + Default: "" + Description: If deploying the Web UI, the Arn of a certificate to use on the ALB of the SnowAlert Web UI. It should already have been issued and include the InstanceFqdn as a valid subject name. + InstanceFqdn: + Type: String + Default: "" + Description: If deploying the Web UI, the fully qualified domain name of the instance (e.g. snowalert.mycompany.com) + InstanceHostedZone: + Type: String + Default: "" + Description: If deploying the Web UI, the hosted DNS zone of the instance (e.g. mycompany.com). This must have already been set up in Route53. + Slice: + Type: String + Default: "master" + Description: Used to differentiate between multiple deployments within the one account, e.g. a test AWS account supporting multiple branch builds +Conditions: + ShouldCreateWebUi: + !Equals [true, !Ref DeployWebUi] + +Resources: + LogGroup: + Type: AWS::Logs::LogGroup + Properties: + LogGroupName: !Join ["-",[!Ref 'ResourcesPrefix', !Ref 'Slice','LogGroup'] ] + RetentionInDays: 7 + + AlertsECSCluster: + Type: AWS::ECS::Cluster + Properties: + ClusterName: !Join ["-",[!Ref 'ResourcesPrefix', !Ref 'Slice', 'Runner'] ] + + EventRule: + Type: AWS::Events::Rule + Properties: + Description: 'Trigger ECS Service according to the specified schedule' + ScheduleExpression: !Ref CronScheduleExpression + State: ENABLED + Targets: + - Arn: !GetAtt AlertsECSCluster.Arn + Id: !Join ["-",[!Ref 'ResourcesPrefix', !Ref 'Slice', 'FargateTask'] ] + RoleArn: !GetAtt SnowAlertTaskExecutionRole.Arn + EcsParameters: + TaskDefinitionArn: !Ref AlertsTaskDefinition + TaskCount: 1 + LaunchType: 'FARGATE' + PlatformVersion: 'LATEST' + NetworkConfiguration: + AwsVpcConfiguration: + AssignPublicIp: ENABLED + SecurityGroups: + - Fn::GetAtt: [ SnowAlertEcsSecurityGroup, GroupId ] + Subnets: + - !Ref 'SubnetOne' + - !Ref 'SubnetTwo' + + SnowAlertTaskExecutionRole: + Type: 'AWS::IAM::Role' + Properties: + AssumeRolePolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Principal: + Service: + - ecs-tasks.amazonaws.com + Action: + - 'sts:AssumeRole' + Path: / + + SnowAlertExecutionPolicy: + Type: 'AWS::IAM::Policy' + Properties: + PolicyName: !Join ["-",[!Ref 'ResourcesPrefix', !Ref 'Slice', 'ExecutionPolicy'] ] + PolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Action: + - ecr:GetAuthorizationToken + - ecr:BatchCheckLayerAvailability + - ecr:GetDownloadUrlForLayer + - ecr:BatchGetImage + - logs:CreateLogStream + - logs:PutLogEvents + - cloudwatch:PutMetricData + Resource: '*' + - Effect: Allow + Action: + - ssm:GetParameters + - ssm:GetParameter + Resource: + - !Join [':', ['arn:aws:ssm', !Ref 'AWS::Region', !Ref 'AWS::AccountId',!Join ['/', [ 'parameter', !Ref ResourcesPrefix, !Ref 'Slice','*']]]] + Roles: + - !Ref 'SnowAlertTaskExecutionRole' + + SnowAlertDecryptionPolicy: + Type: 'AWS::IAM::Policy' + Properties: + PolicyName: !Join ["-",[!Ref 'ResourcesPrefix', !Ref 'Slice','DecryptionPolicy'] ] + PolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Action: + - kms:Decrypt + Resource: !GetAtt SnowAlertKmsKey.Arn + Roles: + - !Ref 'SnowAlertTaskExecutionRole' + + SnowAlertRunTaskRole: + Type: 'AWS::IAM::Role' + Properties: + RoleName: !Join ["-",[!Ref 'ResourcesPrefix', !Ref 'Slice','RunTaskRole'] ] + AssumeRolePolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Principal: + Service: + - events.amazonaws.com + Action: + - 'sts:AssumeRole' + Path: / + + SnowAlertRunTaskPolicy: + Type: 'AWS::IAM::Policy' + Properties: + PolicyName: !Join ["-",[!Ref 'ResourcesPrefix', !Ref 'Slice', 'RunTaskPolicy'] ] + PolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Action: + - ecs:RunTask + Resource: !GetAtt AlertsECSCluster.Arn + Condition: + ArnLike: + ecs:cluster: !GetAtt AlertsECSCluster.Arn + - Effect: Allow + Action: + - iam:PassRole + Resource: '*' + Condition: + StringLike: + iam:PassedToService: 'ecs-tasks.amazonaws.com' + Roles: + - !Ref 'SnowAlertRunTaskRole' + + SnowAlertKmsKey: + Type: AWS::KMS::Key + Properties: + Description: "Used for encyption and audit of SnowAlert" + Enabled: true + EnableKeyRotation: true + KeyPolicy: + Version: '2012-10-17' + Statement: + - Sid: Enable IAM User Permissions + Effect: Allow + Principal: + AWS: + !Join [":",["arn","aws","iam", '', !Ref 'AWS::AccountId', 'root'] ] + Action: 'kms:*' + Resource: '*' + + AlertsTaskDefinition: + Type: AWS::ECS::TaskDefinition + Properties: + Family: !Join ["-",[!Ref 'ResourcesPrefix', !Ref 'Slice', 'AlertsECSService'] ] + Cpu: !Ref 'AlertsContainerCpu' + Memory: !Ref 'AlertsContainerMemory' + NetworkMode: awsvpc + RequiresCompatibilities: + - FARGATE + ExecutionRoleArn: !Ref 'SnowAlertTaskExecutionRole' + TaskRoleArn: !Ref 'SnowAlertRunTaskRole' + ContainerDefinitions: + - Name: !Join ["-",[!Ref 'ResourcesPrefix', !Ref 'Slice', 'AlertsECSService'] ] + Cpu: !Ref 'AlertsContainerCpu' + Memory: !Ref 'AlertsContainerMemory' + Image: 'snowsec/snowalert:latest' + EntryPoint: + - sh + - -c + - | + ./run alerts + Environment: + - Name: REGION + Value: !Ref 'AWS::Region' + - Name: CLOUDWATCH_METRICS + Value: 'True' + - Name: SA_KMS_KEY + Value: !GetAtt SnowAlertKmsKey.Arn + Secrets: + - Name: PRIVATE_KEY + ValueFrom: !Join [':', ['arn:aws:ssm', !Ref 'AWS::Region', !Ref 'AWS::AccountId',!Join ['/', [ 'parameter', !Ref ResourcesPrefix, !Ref 'Slice','PRIVATE_KEY']]]] + - Name: PRIVATE_KEY_PASSWORD + ValueFrom: !Join [':', ['arn:aws:ssm', !Ref 'AWS::Region', !Ref 'AWS::AccountId',!Join ['/', [ 'parameter', !Ref ResourcesPrefix, !Ref 'Slice','PRIVATE_KEY_PASSWORD']]]] + - Name: SA_USER + ValueFrom: !Join [':', ['arn:aws:ssm', !Ref 'AWS::Region', !Ref 'AWS::AccountId',!Join ['/', [ 'parameter', !Ref ResourcesPrefix, !Ref 'Slice','SA_USER']]]] + - Name: SA_WAREHOUSE + ValueFrom: !Join [':', ['arn:aws:ssm', !Ref 'AWS::Region', !Ref 'AWS::AccountId',!Join ['/', [ 'parameter', !Ref ResourcesPrefix, !Ref 'Slice','SA_WAREHOUSE']]]] + - Name: SA_DATABASE + ValueFrom: !Join [':', ['arn:aws:ssm', !Ref 'AWS::Region', !Ref 'AWS::AccountId',!Join ['/', [ 'parameter', !Ref ResourcesPrefix, !Ref 'Slice','SA_DATABASE']]]] + - Name: SA_ROLE + ValueFrom: !Join [':', ['arn:aws:ssm', !Ref 'AWS::Region', !Ref 'AWS::AccountId',!Join ['/', [ 'parameter', !Ref ResourcesPrefix, !Ref 'Slice','SA_ROLE']]]] + - Name: SLACK_API_TOKEN + ValueFrom: !Join [':', ['arn:aws:ssm', !Ref 'AWS::Region', !Ref 'AWS::AccountId',!Join ['/', [ 'parameter', !Ref ResourcesPrefix, !Ref 'Slice','SLACK_API_TOKEN']]]] + - Name: SNOWFLAKE_ACCOUNT + ValueFrom: !Join [':', ['arn:aws:ssm', !Ref 'AWS::Region', !Ref 'AWS::AccountId',!Join ['/', [ 'parameter', !Ref ResourcesPrefix, !Ref 'Slice','SNOWFLAKE_ACCOUNT']]]] + LogConfiguration: + LogDriver: awslogs + Options: + awslogs-region: !Ref AWS::Region + awslogs-group: !Join ["-",[!Ref 'ResourcesPrefix', !Ref 'Slice', 'LogGroup'] ] + awslogs-stream-prefix: alerts + + SnowAlertEcsSecurityGroup: + Type: AWS::EC2::SecurityGroup + Condition: ShouldCreateWebUi + Properties: + GroupDescription: Access for the Fargate containers + VpcId: !Ref 'Vpc' + + SnowAlertSecurityGroupIngress8000: + Type: AWS::EC2::SecurityGroupIngress + Condition: ShouldCreateWebUi + Properties: + IpProtocol: tcp + FromPort: 8000 + ToPort: 8000 + SourceSecurityGroupId: !GetAtt WebUiAlbSg.GroupId + GroupId: !GetAtt SnowAlertEcsSecurityGroup.GroupId + + SnowAlertSecurityGroupEgress80: + Type: AWS::EC2::SecurityGroupEgress + Condition: ShouldCreateWebUi + Properties: + IpProtocol: tcp + FromPort: 80 + ToPort: 80 + CidrIp: 0.0.0.0/0 + GroupId: !GetAtt SnowAlertEcsSecurityGroup.GroupId + + SnowAlertSecurityGroupEgress443: + Type: AWS::EC2::SecurityGroupEgress + Condition: ShouldCreateWebUi + Properties: + IpProtocol: tcp + FromPort: 443 + ToPort: 443 + CidrIp: 0.0.0.0/0 + GroupId: !GetAtt SnowAlertEcsSecurityGroup.GroupId + + WebUiECSCluster: + Type: AWS::ECS::Cluster + Condition: ShouldCreateWebUi + Properties: + ClusterName: !Join ["-",[!Ref 'ResourcesPrefix', !Ref 'Slice', 'WebUi'] ] + + Service: + Type: AWS::ECS::Service + Condition: ShouldCreateWebUi + DependsOn : WebUiAlb + Properties: + ServiceName: !Join ["-",[!Ref 'ResourcesPrefix', !Ref 'Slice', 'WebUiECSService'] ] + Cluster: !Ref 'WebUiECSCluster' + LaunchType: FARGATE + DeploymentConfiguration: + MaximumPercent: 200 + MinimumHealthyPercent: 75 + DesiredCount: 1 + NetworkConfiguration: + AwsvpcConfiguration: + AssignPublicIp: ENABLED + SecurityGroups: + - Fn::GetAtt: [ SnowAlertEcsSecurityGroup, GroupId ] + Subnets: + - !Ref 'SubnetOne' + - !Ref 'SubnetTwo' + TaskDefinition: !Ref 'WebUiTaskDefinition' + LoadBalancers: + - ContainerName: !Join ["-",[!Ref 'ResourcesPrefix', !Ref 'Slice', 'WebUiECSService'] ] + ContainerPort: 8000 + TargetGroupArn: !Ref WebUiTargetGroupHTTPS + + WebUiTaskDefinition: + Type: AWS::ECS::TaskDefinition + Condition: ShouldCreateWebUi + Properties: + Family: !Join ["-",[!Ref 'ResourcesPrefix', !Ref 'Slice', 'WebUiECSService'] ] + Cpu: !Ref 'WebUiContainerCpu' + Memory: !Ref 'WebUiContainerMemory' + NetworkMode: awsvpc + RequiresCompatibilities: + - FARGATE + ExecutionRoleArn: !Ref 'SnowAlertTaskExecutionRole' + #TaskRoleArn: !Ref 'SnowAlertRunTaskRole' + ContainerDefinitions: + - Name: !Join ["-",[!Ref 'ResourcesPrefix', !Ref 'Slice', 'WebUiECSService'] ] + Cpu: !Ref 'WebUiContainerCpu' + Memory: !Ref 'WebUiContainerMemory' + Image: 'snowsec/snowalert-webui' + PortMappings: + - ContainerPort: 8000 + HostPort: 8000 + Protocol: tcp + Environment: + - Name: REGION + Value: !Ref 'AWS::Region' + - Name: CLOUDWATCH_METRICS + Value: 'True' + - Name: SA_KMS_KEY + Value: !GetAtt SnowAlertKmsKey.Arn + Secrets: + - Name: PRIVATE_KEY + ValueFrom: !Join [':', ['arn:aws:ssm', !Ref 'AWS::Region', !Ref 'AWS::AccountId',!Join ['/', [ 'parameter', !Ref ResourcesPrefix, !Ref 'Slice','PRIVATE_KEY']]]] + - Name: PRIVATE_KEY_PASSWORD + ValueFrom: !Join [':', ['arn:aws:ssm', !Ref 'AWS::Region', !Ref 'AWS::AccountId',!Join ['/', [ 'parameter', !Ref ResourcesPrefix, !Ref 'Slice','PRIVATE_KEY_PASSWORD']]]] + - Name: SA_USER + ValueFrom: !Join [':', ['arn:aws:ssm', !Ref 'AWS::Region', !Ref 'AWS::AccountId',!Join ['/', [ 'parameter', !Ref ResourcesPrefix, !Ref 'Slice','SA_USER']]]] + - Name: SA_WAREHOUSE + ValueFrom: !Join [':', ['arn:aws:ssm', !Ref 'AWS::Region', !Ref 'AWS::AccountId',!Join ['/', [ 'parameter', !Ref ResourcesPrefix, !Ref 'Slice','SA_WAREHOUSE']]]] + - Name: SA_DATABASE + ValueFrom: !Join [':', ['arn:aws:ssm', !Ref 'AWS::Region', !Ref 'AWS::AccountId',!Join ['/', [ 'parameter', !Ref ResourcesPrefix, !Ref 'Slice','SA_DATABASE']]]] + - Name: SA_ROLE + ValueFrom: !Join [':', ['arn:aws:ssm', !Ref 'AWS::Region', !Ref 'AWS::AccountId',!Join ['/', [ 'parameter', !Ref ResourcesPrefix, !Ref 'Slice','SA_ROLE']]]] + - Name: SLACK_API_TOKEN + ValueFrom: !Join [':', ['arn:aws:ssm', !Ref 'AWS::Region', !Ref 'AWS::AccountId',!Join ['/', [ 'parameter', !Ref ResourcesPrefix, !Ref 'Slice','SLACK_API_TOKEN']]]] + - Name: SNOWFLAKE_ACCOUNT + ValueFrom: !Join [':', ['arn:aws:ssm', !Ref 'AWS::Region', !Ref 'AWS::AccountId',!Join ['/', [ 'parameter', !Ref ResourcesPrefix, !Ref 'Slice','SNOWFLAKE_ACCOUNT']]]] + LogConfiguration: + LogDriver: awslogs + Options: + awslogs-region: !Ref AWS::Region + awslogs-group: !Join ["-",[!Ref 'ResourcesPrefix', !Ref 'Slice', 'LogGroup'] ] + awslogs-stream-prefix: webui + + + WebUiAlbSg: + Type: "AWS::EC2::SecurityGroup" + Condition: ShouldCreateWebUi + Properties: + GroupDescription: "Allows HTTPS through the SnowAlert Web UI load balancer" + VpcId: !Ref Vpc + SecurityGroupIngress: + - + Description: "HTTPS traffic" + IpProtocol: "tcp" + FromPort: "443" + ToPort: "443" + CidrIp: "0.0.0.0/0" + SecurityGroupEgress: + - Description: "Allow all outbound" + IpProtocol: 'tcp' + FromPort: "0" + ToPort: "65535" + CidrIp: '0.0.0.0/0' + + WebUiAlb: + Type: "AWS::ElasticLoadBalancingV2::LoadBalancer" + Condition: ShouldCreateWebUi + Properties: + Name: !Join ["-",[!Ref 'ResourcesPrefix', !Ref 'Slice', 'WebUiAlb'] ] + Scheme: "internet-facing" + Subnets: + - !Ref SubnetOne + - !Ref SubnetTwo + LoadBalancerAttributes: + - Key: idle_timeout.timeout_seconds + Value: "300" + - Key: deletion_protection.enabled + Value: False + SecurityGroups: + - !Ref WebUiAlbSg + + WebUiListenerHTTPS: + Type: "AWS::ElasticLoadBalancingV2::Listener" + Condition: ShouldCreateWebUi + Properties: + Certificates: + - CertificateArn: !Ref CertificateArn + DefaultActions: + - TargetGroupArn: !Ref WebUiTargetGroupHTTPS + Type: forward + LoadBalancerArn: !Ref WebUiAlb + Port: "443" + Protocol: "HTTPS" + + WebUiTargetGroupHTTPS: + Type: "AWS::ElasticLoadBalancingV2::TargetGroup" + Condition: ShouldCreateWebUi + Properties: + TargetType: ip + HealthCheckIntervalSeconds: "30" + HealthCheckPath: "/" + HealthCheckPort: "8000" + HealthCheckProtocol: "HTTP" + HealthCheckTimeoutSeconds: "10" + HealthyThresholdCount: "2" + Matcher: + HttpCode: "200-401" + Name: !Join ["-",[!Ref 'ResourcesPrefix', !Ref 'Slice', 'WebUiHttpTarget'] ] + Port: "8000" + Protocol: "HTTP" + UnhealthyThresholdCount: "3" + VpcId: !Ref Vpc + + WebUiRoute53Record: + Type: AWS::Route53::RecordSet + Condition: ShouldCreateWebUi + Properties: + HostedZoneName: !Ref InstanceHostedZone + Name: !Ref InstanceFqdn + Type: "CNAME" + TTL: "30" + ResourceRecords: [ !GetAtt WebUiAlb.DNSName ] From 98b4ec3afb800b8a25cbfb7ce27e76a9c61aee20 Mon Sep 17 00:00:00 2001 From: James Weakley Date: Mon, 2 Dec 2019 21:11:45 +1100 Subject: [PATCH 2/4] Tidy up template for consistency, default ALB to internal --- infra/cfn/CLOUDFORMATION.md | 15 ++++- infra/cfn/snowalert.yaml | 107 ++++++++++++++++++------------------ 2 files changed, 68 insertions(+), 54 deletions(-) diff --git a/infra/cfn/CLOUDFORMATION.md b/infra/cfn/CLOUDFORMATION.md index e798bd0e1..65d07ded1 100644 --- a/infra/cfn/CLOUDFORMATION.md +++ b/infra/cfn/CLOUDFORMATION.md @@ -23,8 +23,19 @@ Prerequisites: /SnowAlert/master/SNOWFLAKE_ACCOUNT ``` -Assume AWS CLI has been configured with default region and credentials, you can provision the stack like so: +Assuming the AWS CLI has been configured with default region and credentials, for a role similar to Power User, you can provision the stack from the commandline like so. + +No Web UI: +``` +aws cloudformation create-stack --template-body snowalert.yaml --stack-name SnowAlert-Prod --capabilities CAPABILITY_NAMED_IAM --parameters ParameterKey=Vpc,ParameterValue=vpc-a1234567 ParameterKey=SubnetOne,ParameterValue=subnet-1ab23456 ParameterKey=SubnetTwo,ParameterValue=subnet-2cd34567 ParameterKey=DeployWebUi,ParameterValue=false +``` + +With Web UI, internal VPC access only: ``` -aws cloudformation create-stack --template-body snowalert.yaml --stack-name SnowAlert-Prod --capabilities CAPABILITY_IAM --parameters ParameterKey=Vpc,ParameterValue=vpc-a1234567 ParameterKey=SubnetOne,ParameterValue=subnet-1ab23456 ParameterKey=SubnetTwo,ParameterValue=subnet-2cd34567 ParameterKey=CertificateArn,ParameterValue=arn:aws:acm:ap-southeast-2:123456789012:certificate/461b5dc1-443c-46ef-a5df-a6e6a7190d67 ParameterKey=InstanceFqdn,ParameterValue=snowalert.mydomain.com ParameterKey=InstanceHostedZone,ParameterValue=mydomain.com. +aws cloudformation create-stack --template-body snowalert.yaml --stack-name SnowAlert-Prod --capabilities CAPABILITY_NAMED_IAM --parameters ParameterKey=Vpc,ParameterValue=vpc-a1234567 ParameterKey=SubnetOne,ParameterValue=subnet-1ab23456 ParameterKey=SubnetTwo,ParameterValue=subnet-2cd34567 ParameterKey=CertificateArn,ParameterValue=arn:aws:acm:ap-southeast-2:123456789012:certificate/461b5dc1-443c-46ef-a5df-a6e6a7190d67 ParameterKey=InstanceFqdn,ParameterValue=snowalert.myinternaldomain.com ParameterKey=InstanceHostedZone,ParameterValue=myinternaldomain.com. ``` +With Web UI, public facing: +``` +aws cloudformation create-stack --template-body snowalert.yaml --stack-name SnowAlert-Prod --capabilities CAPABILITY_NAMED_IAM --parameters ParameterKey=Vpc,ParameterValue=vpc-a1234567 ParameterKey=SubnetOne,ParameterValue=subnet-1ab23456 ParameterKey=SubnetTwo,ParameterValue=subnet-2cd34567 ParameterKey=CertificateArn,ParameterValue=arn:aws:acm:ap-southeast-2:123456789012:certificate/461b5dc1-443c-46ef-a5df-a6e6a7190d67 ParameterKey=InstanceFqdn,ParameterValue=snowalert.mydomain.com ParameterKey=InstanceHostedZone,ParameterValue=mydomain.com. ParameterKey=WebUiAlbScheme,ParameterValue=internet-facing +``` diff --git a/infra/cfn/snowalert.yaml b/infra/cfn/snowalert.yaml index a6ab21270..8f966d401 100644 --- a/infra/cfn/snowalert.yaml +++ b/infra/cfn/snowalert.yaml @@ -34,6 +34,11 @@ Parameters: Default: true Description: Flags whether or not to deploy the SnowAlert Web UI, which includes another Fargate cluster and ALB (with DNS + cert) AllowedValues: [true, false] + WebUiAlbScheme: + Type: String + Default: internal + Description: Determines whether or not the SnowAlert Web UI (if enabled) is internal or internet facing + AllowedValues: [internal, internet-facing] WebUiContainerCpu: Type: Number Default: 1024 @@ -77,7 +82,7 @@ Resources: EventRule: Type: AWS::Events::Rule Properties: - Description: 'Trigger ECS Service according to the specified schedule' + Description: Trigger ECS Service according to the specified schedule ScheduleExpression: !Ref CronScheduleExpression State: ENABLED Targets: @@ -87,8 +92,8 @@ Resources: EcsParameters: TaskDefinitionArn: !Ref AlertsTaskDefinition TaskCount: 1 - LaunchType: 'FARGATE' - PlatformVersion: 'LATEST' + LaunchType: FARGATE + PlatformVersion: LATEST NetworkConfiguration: AwsVpcConfiguration: AssignPublicIp: ENABLED @@ -99,7 +104,7 @@ Resources: - !Ref 'SubnetTwo' SnowAlertTaskExecutionRole: - Type: 'AWS::IAM::Role' + Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: '2012-10-17' @@ -109,11 +114,11 @@ Resources: Service: - ecs-tasks.amazonaws.com Action: - - 'sts:AssumeRole' + - sts:AssumeRole Path: / SnowAlertExecutionPolicy: - Type: 'AWS::IAM::Policy' + Type: AWS::IAM::Policy Properties: PolicyName: !Join ["-",[!Ref 'ResourcesPrefix', !Ref 'Slice', 'ExecutionPolicy'] ] PolicyDocument: @@ -139,7 +144,7 @@ Resources: - !Ref 'SnowAlertTaskExecutionRole' SnowAlertDecryptionPolicy: - Type: 'AWS::IAM::Policy' + Type: AWS::IAM::Policy Properties: PolicyName: !Join ["-",[!Ref 'ResourcesPrefix', !Ref 'Slice','DecryptionPolicy'] ] PolicyDocument: @@ -153,7 +158,7 @@ Resources: - !Ref 'SnowAlertTaskExecutionRole' SnowAlertRunTaskRole: - Type: 'AWS::IAM::Role' + Type: AWS::IAM::Role Properties: RoleName: !Join ["-",[!Ref 'ResourcesPrefix', !Ref 'Slice','RunTaskRole'] ] AssumeRolePolicyDocument: @@ -168,7 +173,7 @@ Resources: Path: / SnowAlertRunTaskPolicy: - Type: 'AWS::IAM::Policy' + Type: AWS::IAM::Policy Properties: PolicyName: !Join ["-",[!Ref 'ResourcesPrefix', !Ref 'Slice', 'RunTaskPolicy'] ] PolicyDocument: @@ -194,7 +199,7 @@ Resources: SnowAlertKmsKey: Type: AWS::KMS::Key Properties: - Description: "Used for encyption and audit of SnowAlert" + Description: Used for encyption and audit of SnowAlert Enabled: true EnableKeyRotation: true KeyPolicy: @@ -262,7 +267,6 @@ Resources: SnowAlertEcsSecurityGroup: Type: AWS::EC2::SecurityGroup - Condition: ShouldCreateWebUi Properties: GroupDescription: Access for the Fargate containers VpcId: !Ref 'Vpc' @@ -381,82 +385,81 @@ Resources: awslogs-group: !Join ["-",[!Ref 'ResourcesPrefix', !Ref 'Slice', 'LogGroup'] ] awslogs-stream-prefix: webui - WebUiAlbSg: - Type: "AWS::EC2::SecurityGroup" + Type: AWS::EC2::SecurityGroup Condition: ShouldCreateWebUi Properties: - GroupDescription: "Allows HTTPS through the SnowAlert Web UI load balancer" + GroupDescription: Allows HTTPS through the SnowAlert Web UI load balancer VpcId: !Ref Vpc SecurityGroupIngress: - - Description: "HTTPS traffic" - IpProtocol: "tcp" - FromPort: "443" - ToPort: "443" - CidrIp: "0.0.0.0/0" + Description: HTTPS traffic + IpProtocol: tcp + FromPort: 443 + ToPort: 443 + CidrIp: 0.0.0.0/0 SecurityGroupEgress: - - Description: "Allow all outbound" - IpProtocol: 'tcp' - FromPort: "0" - ToPort: "65535" - CidrIp: '0.0.0.0/0' + - Description: Allow all outbound + IpProtocol: tcp + FromPort: 8000 + ToPort: 8000 + DestinationSecurityGroupId: !GetAtt SnowAlertEcsSecurityGroup.GroupId WebUiAlb: - Type: "AWS::ElasticLoadBalancingV2::LoadBalancer" + Type: AWS::ElasticLoadBalancingV2::LoadBalancer Condition: ShouldCreateWebUi Properties: Name: !Join ["-",[!Ref 'ResourcesPrefix', !Ref 'Slice', 'WebUiAlb'] ] - Scheme: "internet-facing" + Scheme: !Ref 'WebUiAlbScheme' Subnets: - - !Ref SubnetOne - - !Ref SubnetTwo + - !Ref 'SubnetOne' + - !Ref 'SubnetTwo' LoadBalancerAttributes: - Key: idle_timeout.timeout_seconds - Value: "300" + Value: 300 - Key: deletion_protection.enabled Value: False SecurityGroups: - - !Ref WebUiAlbSg + - !Ref 'WebUiAlbSg' WebUiListenerHTTPS: - Type: "AWS::ElasticLoadBalancingV2::Listener" + Type: AWS::ElasticLoadBalancingV2::Listener Condition: ShouldCreateWebUi Properties: Certificates: - - CertificateArn: !Ref CertificateArn + - CertificateArn: !Ref 'CertificateArn' DefaultActions: - - TargetGroupArn: !Ref WebUiTargetGroupHTTPS + - TargetGroupArn: !Ref 'WebUiTargetGroupHTTPS' Type: forward - LoadBalancerArn: !Ref WebUiAlb - Port: "443" - Protocol: "HTTPS" + LoadBalancerArn: !Ref 'WebUiAlb' + Port: 443 + Protocol: HTTPS WebUiTargetGroupHTTPS: - Type: "AWS::ElasticLoadBalancingV2::TargetGroup" + Type: AWS::ElasticLoadBalancingV2::TargetGroup Condition: ShouldCreateWebUi Properties: TargetType: ip - HealthCheckIntervalSeconds: "30" - HealthCheckPath: "/" - HealthCheckPort: "8000" - HealthCheckProtocol: "HTTP" - HealthCheckTimeoutSeconds: "10" - HealthyThresholdCount: "2" + HealthCheckIntervalSeconds: 30 + HealthCheckPath: / + HealthCheckPort: 8000 + HealthCheckProtocol: HTTP + HealthCheckTimeoutSeconds: 10 + HealthyThresholdCount: 2 Matcher: - HttpCode: "200-401" + HttpCode: 200-401 Name: !Join ["-",[!Ref 'ResourcesPrefix', !Ref 'Slice', 'WebUiHttpTarget'] ] - Port: "8000" - Protocol: "HTTP" - UnhealthyThresholdCount: "3" - VpcId: !Ref Vpc + Port: 8000 + Protocol: HTTP + UnhealthyThresholdCount: 3 + VpcId: !Ref 'Vpc' WebUiRoute53Record: Type: AWS::Route53::RecordSet Condition: ShouldCreateWebUi Properties: - HostedZoneName: !Ref InstanceHostedZone - Name: !Ref InstanceFqdn - Type: "CNAME" - TTL: "30" + HostedZoneName: !Ref 'InstanceHostedZone' + Name: !Ref 'InstanceFqdn' + Type: CNAME + TTL: 30 ResourceRecords: [ !GetAtt WebUiAlb.DNSName ] From 22cb8aaf9fe0cb7f5169325c176fdaef9b5b7b4b Mon Sep 17 00:00:00 2001 From: James Weakley Date: Thu, 5 Dec 2019 08:58:02 +1100 Subject: [PATCH 3/4] Add web listener dependancy on target group --- infra/cfn/snowalert.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/infra/cfn/snowalert.yaml b/infra/cfn/snowalert.yaml index 8f966d401..ecdd3d0af 100644 --- a/infra/cfn/snowalert.yaml +++ b/infra/cfn/snowalert.yaml @@ -425,6 +425,7 @@ Resources: WebUiListenerHTTPS: Type: AWS::ElasticLoadBalancingV2::Listener Condition: ShouldCreateWebUi + DependsOn : WebUiTargetGroupHTTPS Properties: Certificates: - CertificateArn: !Ref 'CertificateArn' From b4d1cd0652b89f7632cc514e5c0f89827e28d5d9 Mon Sep 17 00:00:00 2001 From: James Weakley Date: Thu, 5 Dec 2019 11:09:46 +1100 Subject: [PATCH 4/4] Fix service dependancies, remove direct credentials from web ui environment --- infra/cfn/snowalert.yaml | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/infra/cfn/snowalert.yaml b/infra/cfn/snowalert.yaml index ecdd3d0af..7c142b1d6 100644 --- a/infra/cfn/snowalert.yaml +++ b/infra/cfn/snowalert.yaml @@ -310,7 +310,7 @@ Resources: Service: Type: AWS::ECS::Service Condition: ShouldCreateWebUi - DependsOn : WebUiAlb + DependsOn : WebUiListenerHTTPS Properties: ServiceName: !Join ["-",[!Ref 'ResourcesPrefix', !Ref 'Slice', 'WebUiECSService'] ] Cluster: !Ref 'WebUiECSCluster' @@ -362,18 +362,6 @@ Resources: - Name: SA_KMS_KEY Value: !GetAtt SnowAlertKmsKey.Arn Secrets: - - Name: PRIVATE_KEY - ValueFrom: !Join [':', ['arn:aws:ssm', !Ref 'AWS::Region', !Ref 'AWS::AccountId',!Join ['/', [ 'parameter', !Ref ResourcesPrefix, !Ref 'Slice','PRIVATE_KEY']]]] - - Name: PRIVATE_KEY_PASSWORD - ValueFrom: !Join [':', ['arn:aws:ssm', !Ref 'AWS::Region', !Ref 'AWS::AccountId',!Join ['/', [ 'parameter', !Ref ResourcesPrefix, !Ref 'Slice','PRIVATE_KEY_PASSWORD']]]] - - Name: SA_USER - ValueFrom: !Join [':', ['arn:aws:ssm', !Ref 'AWS::Region', !Ref 'AWS::AccountId',!Join ['/', [ 'parameter', !Ref ResourcesPrefix, !Ref 'Slice','SA_USER']]]] - - Name: SA_WAREHOUSE - ValueFrom: !Join [':', ['arn:aws:ssm', !Ref 'AWS::Region', !Ref 'AWS::AccountId',!Join ['/', [ 'parameter', !Ref ResourcesPrefix, !Ref 'Slice','SA_WAREHOUSE']]]] - - Name: SA_DATABASE - ValueFrom: !Join [':', ['arn:aws:ssm', !Ref 'AWS::Region', !Ref 'AWS::AccountId',!Join ['/', [ 'parameter', !Ref ResourcesPrefix, !Ref 'Slice','SA_DATABASE']]]] - - Name: SA_ROLE - ValueFrom: !Join [':', ['arn:aws:ssm', !Ref 'AWS::Region', !Ref 'AWS::AccountId',!Join ['/', [ 'parameter', !Ref ResourcesPrefix, !Ref 'Slice','SA_ROLE']]]] - Name: SLACK_API_TOKEN ValueFrom: !Join [':', ['arn:aws:ssm', !Ref 'AWS::Region', !Ref 'AWS::AccountId',!Join ['/', [ 'parameter', !Ref ResourcesPrefix, !Ref 'Slice','SLACK_API_TOKEN']]]] - Name: SNOWFLAKE_ACCOUNT