kfs_opsworks.yaml
---

KFS Environment CloudFormation Deployment

This CloudFormation template will build out a whole KFS environment, including an OpsWorks stack, Load Balancer, Application nodes and Database.

AWSTemplateFormatVersion: '2010-09-09' Description: KFS OpsWorks Stack - Development (dev, tst, trn, etc)

Metadata

Metadata is mostly for organizing and presenting Parameters in a better way when using CloudFormation in the AWS Web UI.

Metadata: AWS::CloudFormation::Interface: ParameterGroups: - Label: default: Deployer Information Parameters: - DeploymentRoute53RegistererAccessKeyID - DeploymentRoute53RegistererAccessKey - HostedZoneId - DatabaseSecurityGroup - Label: default: Application Information Parameters: - EnvAppName - AppSlug - EnvSlug - Label: default: Load Balancer Settings Parameters: - HostedZoneName - SSLCertARN - LBSubnetA - LBSubnetB - Label: default: Instance Settings Parameters: - InstanceType - KeyName - VPCID - HostnameA - HostnameB - AppSubnetA - AppSubnetB - EFSTargetSecurityGroup - CustomCookbooksSource - Label: default: Application Settings Parameters: - AppDockerImage - RhubarbDockerImage - AppBaseURL - AppDBHostname - AppDBServicename - AppDBUsername - AppDBPassword - AppKeystorePassword - AppKeystoreEncryptionKey - AppB2BPurchaseOrderUsername - AppB2BPurchaseOrderPassword - AppB2BShoppingUsername - AppB2BShoppingPassword - AppLdapUsername - AppLdapPassword - AppNewRelicKey - AppS3ResourceBucket - AppSMTPUsername - AppSMTPPassword - Label: default: Tags Parameters: - TagService - TagName - TagEnvironment - TagCreatedBy - TagContactNetId - TagAccountNumber - TagSubAccount - TagTicketNumber - TagResourceFunction ParameterLabels: DeploymentRoute53RegistererAccessKeyID: default: 'Deployment Route 53 Registerer Access Key ID:' DeploymentRoute53RegistererAccessKey: default: 'Deployment Route 53 Registerer Access Key:' HostedZoneId: default: 'Hosted Zone ID:' DatabaseSecurityGroup: default: 'Database Security Group:' EnvAppName: default: 'Application Name:' AppSlug: default: 'Application Slug:' EnvSlug: default: 'Environment Slug:' LBSubnetA: default: 'Load Balancer Subnet A:' LBSubnetB: default: 'Load Balancer Subnet B:' HostedZoneName: default: 'Route53 DNS Zone:' HostnameA: default: 'Hostname A:' HostnameB: default: 'Hostname B:' AppSubnetA: default: 'App Instance Subnet A:' AppSubnetB: default: 'App Instance Subnet B:' AppDockerImage: default: Docker Image AppBaseURL: default: Base URL AppDBUsername: default: DB Username AppDBPassword: default: DB Password AppKeystorePassword: default: Keystore Password AppKeystoreEncryptionKey: default: Keystore Encryption Key AppB2BPurchaseOrderUsername: default: B2B PO Username AppB2BPurchaseOrderPassword: default: B2B PO Password AppB2BShoppingUsername: default: B2B Shopping Username AppB2BShoppingPassword: default: B2B Shopping Password AppLdapUsername: default: EDS LDAP Username AppLdapPassword: default: EDS LDAP Password AppNewRelicKey: default: New Relic Key AppS3ResourceBucket: default: S3 Resource Bucket AppSMTPUsername: default: SMTP Username AppSMTPPassword: default: SMTP Password

Parameters

These are the input parameters for this template. All of these parameters must be supplied for this template to be deployed.

Parameters: DeploymentRoute53RegistererAccessKeyID: Type: String Description: Access key ID for the AWS user that will allow the application instances to self-register their hostnames with Route 53. DeploymentRoute53RegistererAccessKey: Type: String Description: Access key for the AWS user that will allow the application instances to self-register their hostnames with Route 53. HostedZoneId: Type: String Description: Hosted zone name (NOT the full ARN) describing the Route53 hosted zone to self-register with. DatabaseSecurityGroup: Type: AWS::EC2::SecurityGroup::Id Description: Security group to apply to the application instance so that the RDS instance will allow traffic from the application to the RDS instance Default: sg-68043b12 EnvAppName: MinLength: '3' Type: String Description: Full Application name, ie 'Kuali Financials' EnvSlug: MinLength: '2' Type: String Description: Short environment slug, ie 'dev', or 'markdev'. Lowercase letters, numbers and dashes only AllowedPattern: "[a-z0-9]*" AppSlug: MinLength: '3' Type: String Description: Short application slug, ie 'kfs'. Lowercase letters, numbers and dashes only AllowedPattern: "[a-z0-9-]*" VPCID: Description: Target VPC Type: AWS::EC2::VPC::Id HostnameA: Description: Hostname to use for the primary application instance, such as 'a50'. Type: String HostnameB: Description: Hostname to use for the secondary application instance (if this deployment will use one), such as 'a51'. Type: String AppSubnetA: Description: Application Subnet for Zone A Type: AWS::EC2::Subnet::Id AppSubnetB: Description: Application Subnet for Zone B Type: AWS::EC2::Subnet::Id SSLCertARN: Description: ARN of the SSL Certificate to use Type: String LBSubnetA: Description: Load Balancer Subnet for Zone A Type: AWS::EC2::Subnet::Id LBSubnetB: Description: Load Balancer Subnet for Zone B Type: AWS::EC2::Subnet::Id HostedZoneName: MinLength: '3' Type: String Description: 'Name of Route53 Hosted Zone: ie ''aws.arizona.edu''' KeyName: Description: Amazon EC2 Key Pair Type: AWS::EC2::KeyPair::KeyName CustomCookbooksSource: Description: URL to S3 cookbooks, ie 'https://s3.amazonaws.com/edu-arizona-pilots-eas/cookbooks.tar.gz' Default: https://s3-us-west-2.amazonaws.com/edu-arizona-pilots-eas/financials/kfs-cookbooks.tar.gz Type: String InstanceType: Description: EC2 Instance Type Type: String Default: m4.large EFSTargetSecurityGroup: Description: EFS Target Security group, ie 'sg-28b8a14e' Type: AWS::EC2::SecurityGroup::Id Default: sg-28b8a14e AppEFSMountInfo: Description: 'EFS Volumes to mount. Comma list of mountname:efs-id pairs, ie: kfs:fs-49b14ce0,uar:fs-76b14cdf' Type: String RhubarbDockerImage: Description: 'Docker Image, i.e.: uakuali/rhubarb:latest' Type: String AppDockerImage: Description: 'Docker Image, i.e.: easksd/kfs6:dev' Type: String AppDockerAccessKey: Description: 'ie: ABCDEFG' Type: String AppDockerSecretKey: Description: AWS Secret Access Key Type: String NoEcho: 'true' AppBaseURL: Description: 'Base Application URL: i.e.: https://ka-foo.mosiac.arizona.edu/' Type: String AppDBHostname: Description: Database Host Name Type: String AppDBServicename: Description: Database Service Name Type: String AppDBUsername: Description: Database Username Type: String AppDBPassword: Description: Database Password Type: String NoEcho: 'true' AppKeystorePassword: Description: Rice Key Store Password Type: String NoEcho: 'true' AppKeystoreEncryptionKey: Description: Rice Key Store Encryption Key Type: String NoEcho: 'true' AppB2BPurchaseOrderUsername: Description: B2B Purchase Order Username Type: String AppB2BPurchaseOrderPassword: Description: B2B Purchase Order Password Type: String NoEcho: 'true' AppB2BShoppingUsername: Description: B2B Shopping Username Type: String AppB2BShoppingPassword: Description: B2B Shopping Password Type: String NoEcho: 'true' AppLdapUsername: Description: EDS LDAP Username Type: String NoEcho: 'true' AppLdapPassword: Description: EDS LDAP Password Type: String NoEcho: 'true' AppNewRelicKey: Description: NewRelic License Key Type: String NoEcho: 'true' AppS3ResourceBucket: Description: S3 Bucket containing /security and /classes folders. Just the bucket name, not a full arn. Type: String TagService: Description: Service name (from the service catalog) that is utilizing this resource Type: String TagName: Description: Descriptive identifier of resource. Type: String TagEnvironment: Description: Type of environment that is using this resource, such as 'dev', 'tst', 'prd'. Type: String TagCreatedBy: Description: NetID of the user that created this resource Type: String TagContactNetId: Description: NetID of the person to contact for information about this resource Type: String TagAccountNumber: Description: Financial system account number for the service utilizing this resource Type: String TagSubAccount: Description: Financial system subaccount number for the service utilizing this resource Type: String TagTicketNumber: Description: Ticket number that this resource is for Type: String TagResourceFunction: Description: Human-readable description of what function this resource is providing Type: String AppSMTPUsername: Description: AWS SES Username Credentials Type: String NoEcho: 'true' AppSMTPPassword: Description: AWS SES Password Credentials Type: String NoEcho: 'true' Conditions: IsPrototypeEnvironment: !Not [ !Or [ !Equals [!Ref EnvSlug, "dev"], !Equals [!Ref EnvSlug, "tst"], !Equals [!Ref EnvSlug, "stg"], !Equals [!Ref EnvSlug, "sup"], !Equals [!Ref EnvSlug, "sup"], !Equals [!Ref EnvSlug, "trn"] ] ] IsPrdOrSup: !Or [ !Equals [!Ref EnvSlug, "sup"], !Equals [!Ref EnvSlug, "prd"] ] IsPrd: !Equals [!Ref EnvSlug, "prd"]

Resources

These are all of the actual AWS resources created for this application.

Resources:

Application Layer

This defines a Layer in the OpsWorks stack. It sets up the Chef cookbooks to pull in from S3, as well as a list of specific Chef recipies to run.

EnvApplicationLayer: Type: AWS::OpsWorks::Layer Properties: Name: KFS Shortname: app Type: custom AutoAssignElasticIps: 'false' AutoAssignPublicIps: 'false' CustomRecipes:

You have to mount the EFS volumes BEFORE installing and running docker, otherwise docker doesn't pick up the mounts.

Setup: - ecs-utilities::efs_mount - ecs-docker::docker_install Deploy: - kfs::create_transactional_directories - kfs::install_configs - ecs-docker::docker_login_ecr - kfs::kfs_docker_pull_deploy - kfs::register_with_route53 - kfs::cloudwatchlogs_install Undeploy: - kfs::unregister_with_route53 CustomSecurityGroupIds: - !Ref InstanceSecurityGroup - !Ref EFSTargetSecurityGroup #This is the App SG that will allow the App servers to connect to the DB - !Ref DatabaseSecurityGroup EnableAutoHealing: 'false' StackId: Ref: EnvStack

OpsWorks Application

This defines the application for the OpsWorks stack. There's one Application for each Docker container to be deployed. For KFS its just the one container.

The Application mostly provides a secure place to store secret texts for use with the Docker container passed in via environment variables.

KFSApp: Type: AWS::OpsWorks::App Properties: AppSource: Type: other Name: Ref: EnvAppName Shortname: Ref: AppSlug StackId: Ref: EnvStack Type: other Environment: - Key: deployment_route53_registerer_access_key_id Value: !Ref DeploymentRoute53RegistererAccessKeyID - Key: deployment_route53_registerer_access_key Value: !Ref DeploymentRoute53RegistererAccessKey - Key: hosted_zone_id Value: !Ref HostedZoneId - Key: hosted_zone_name Value: !Ref HostedZoneName - Key: layer Value: !Ref EnvAppName

This env var sets up an EFS volume to be mounted at '/efs/shared'. The chef recipe ecs-utilities::efs_mount does this.

- Key: efs_mounts Value: !Ref AppEFSMountInfo

Various Docker relate env vars used by ecs-docker::docker_login_ecr and ecs-docker::docker_pull_deploy chef recipies

- Key: docker_deploy_order Value: 1 - Key: docker_container Value: !Ref AppDockerImage - Key: docker_ports Value: 0.0.0.0:80:8080,0.0.0.0:8444:8444 - Key: docker_run_cmd Value: "/usr/local/bin/tomcat-start"

This part is a bit of a mess, because we have to splice in the environment name into the list of paths to mount into the docker container. There's a single "Non-Prod" and "Prod" EFS volume, and the specific environments will be folders in there, ie "dev", "tst", etc. So for this specific environment being deployed, only the appropriate folder should be mapped in.

The other folders, conf, security, and logs are not shared across EFS, they're built out on the host by the kfs::install_configs chef recipe

- Key: docker_volumes Value: !Sub "/efs/kfs/${EnvSlug}/transactional:/transactional,/kuali-configs/configuration:/configuration:ro,/kuali-configs/security:/security:ro,/tmp:/logs" - Key: APP_SLUG_NAME Value: !Ref AppSlug - Key: KFS_ENV_NAME Value: !Ref EnvSlug - Key: base_url Value: !Ref AppBaseURL - Key: db_host Value: !Ref AppDBHostname - Key: db_service_name Value: !Ref AppDBServicename - Key: db_username Value: !Ref AppDBUsername - Key: db_password Secure: true Value: !Ref AppDBPassword - Key: keystore_alias Value: rice - Key: keystore_password Secure: true Value: !Ref AppKeystorePassword - Key: encryption_key Secure: true Value: !Ref AppKeystoreEncryptionKey - Key: b2b_po_identity Value: !Ref AppB2BPurchaseOrderUsername - Key: b2b_po_password Secure: true Value: !Ref AppB2BPurchaseOrderPassword - Key: b2b_shop_identity Value: !Ref AppB2BShoppingUsername - Key: b2b_shop_password Secure: true Value: !Ref AppB2BShoppingPassword - Key: ldap_username Value: !Ref AppLdapUsername - Key: ldap_password Secure: true Value: !Ref AppLdapPassword - Key: ldap_url Value: ldaps://eds.arizona.edu:636 - Key: ldap_base Value: ou=People,dc=eds,dc=arizona,dc=edu - Key: newrelic_license_key Secure: true Value: !Ref AppNewRelicKey - Key: s3resource_bucket Value: !Ref AppS3ResourceBucket - Key: smtp_username Secure: true Value: !Ref AppSMTPUsername - Key: smtp_password Secure: true Value: !Ref AppSMTPPassword

docker_login Application

Now that we need multiple docker containers, we need multiple OpsWorks Apps.

The way the ecs-docker Chef recipes deal with multiple Apps requires the Docker Login environment variables be on their own App, rather than just part of the single App.

DockerLoginApp: Type: AWS::OpsWorks::App Properties: AppSource: Type: other Name: "docker_login" Shortname: "docker_login" StackId: !Ref EnvStack Type: other Environment: - Key: docker_deploy_order Value: 0 - Key: docker_repository_access_key Value: !Ref AppDockerAccessKey - Key: docker_repository_secret_key Secure: true Value: !Ref AppDockerSecretKey

nginx Application

In order to properly respond to initial HTTP traffic on port 80, we need some way to detect and redirect that traffic to HTTPS on 443. This App launches a very stripped down nginx container which only does that.

NginxApp: Type: AWS::OpsWorks::App Properties: AppSource: Type: other Name: "nginx" Shortname: "nginx" StackId: !Ref EnvStack Type: other Environment: - Key: docker_deploy_order Value: 2 - Key: layer Value: !Ref EnvAppName - Key: docker_container Value: "760232551367.dkr.ecr.us-west-2.amazonaws.com/kuali/nginx-redirect:kfs3-1" - Key: docker_ports Value: "0.0.0.0:8080:80,0.0.0.0:8888:8888" - Key: docker_links Value: !Ref AppSlug

UAFAWS-301 : Add these two variables hydrated by jenkins, so KFS_docker_pull_deploy can calculate CloudWatch LOG_Group_Name

- Key: APP_SLUG_NAME Value: !Ref AppSlug - Key: KFS_ENV_NAME Value: !Ref EnvSlug

Rhubarb Application

Rhubarb is used to run batch jobs, and is deployed as a separate container along side the application container on the same host.

RhubarbApp: Type: AWS::OpsWorks::App Properties: AppSource: Type: other Name: "rhubarb" Shortname: "rhubarb" StackId: !Ref EnvStack Type: other Environment: - Key: docker_deploy_order Value: 3 - Key: layer Value: !Ref EnvAppName - Key: docker_container Value: !Ref RhubarbDockerImage - Key: docker_ports Value: "0.0.0.0:2022:22" - Key: docker_volumes Value: !Sub "/efs/kfs/${EnvSlug}/transactional:/transactional,/kuali-configs/security/rhubarb-security:/rhubarb-security:ro" - Key: docker_privileged Value: privileged - Key: APP_SLUG_NAME Value: !Ref AppSlug - Key: KFS_ENV_NAME Value: !Ref EnvSlug

OpsWorks Stack

This defines the OpsWork stack itself. Mostly its network configs and defaults. The main thing in here is the source for the Chef Cookbooks to be used by any layers.

EnvStack: Type: AWS::OpsWorks::Stack Properties: Name: !Sub "${EnvAppName} ${EnvSlug}" VpcId: !Ref VPCID ServiceRoleArn: !GetAtt EnvServiceRole.Arn DefaultInstanceProfileArn: !GetAtt EnvInstanceProfile.Arn DefaultOs: Amazon Linux 2016.03 DefaultSshKeyName: !Ref KeyName DefaultRootDeviceType: ebs DefaultSubnetId: !Ref AppSubnetA HostnameTheme: Layer_Dependent UseCustomCookbooks: 'true' Attributes: Color: rgb(209, 105, 41) ConfigurationManager: Name: Chef Version: '12' CustomCookbooksSource: Type: s3 Url: !Ref CustomCookbooksSource CustomJson: opsworks_tags: instances: service: !Ref TagService Name: !Ref TagName environment: !Ref TagEnvironment createdby: !Ref TagCreatedBy contactnetid: !Ref TagContactNetId accountnumber: !Ref TagAccountNumber subaccount: !Ref TagSubAccount ticketnumber: !Ref TagTicketNumber resourcefunction: !Ref TagResourceFunction

Application Instance Node

This creates and turns on an OpsWorks EC2 Instance to actually run the application. It is assigned to a specific layer, and that's where it gets the Chef recipes to run.

Currently only a single App Instance is created. Subsequent instances could be created in OpsWorks. IMPORTANT! Any resources created outside of CloudFormation cannot be removed via CloudFormation stack deletion. To be safe, delete all Instances out of OpsWorks before deleting the CloudFormation Stack.

AppInstanceZoneA: Type: AWS::OpsWorks::Instance Properties: StackId: !Ref EnvStack InstanceType: !Ref InstanceType SubnetId: !Ref AppSubnetA SshKeyName: !Ref KeyName Hostname: !Ref HostnameA InstallUpdatesOnBoot: true LayerIds: - Ref: EnvApplicationLayer RootDeviceType: ebs BlockDeviceMappings: - DeviceName: ROOT_DEVICE Ebs: DeleteOnTermination: true VolumeSize: 20 VolumeType: gp2 AppInstanceZoneB: Type: AWS::OpsWorks::Instance

Only bring up this instance if we are running production Not need for two instances for SUP

Condition: IsPrd Properties: StackId: !Ref EnvStack InstanceType: !Ref InstanceType SubnetId: !Ref AppSubnetB SshKeyName: !Ref KeyName Hostname: !Ref HostnameB InstallUpdatesOnBoot: true LayerIds: - Ref: EnvApplicationLayer RootDeviceType: ebs BlockDeviceMappings: - DeviceName: ROOT_DEVICE Ebs: DeleteOnTermination: true VolumeSize: 20 VolumeType: gp2

Elastic Load Balancer (ELB)

Defines the Load Balancer for KFS. http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-elb.html UAFAWS-198 - To change subnets(in Jenkins) and Scheme (in ELB) to Private network; Two option available for ELB Scheme: "internet-facing" (public) vs "internal" (private)

EnvApplicationLayerLoadBalancer: Type: AWS::ElasticLoadBalancing::LoadBalancer Properties: Scheme: internal ConnectionSettings: IdleTimeout: '300' HealthCheck: HealthyThreshold: '5' Interval: '60' Target: TCP:80 Timeout: '10' UnhealthyThreshold: '3' LBCookieStickinessPolicy: - CookieExpirationPeriod: '28800' PolicyName: DefaultSessionTimeout Listeners: - LoadBalancerPort: '443' Protocol: HTTPS InstancePort: '80' InstanceProtocol: HTTP PolicyNames: - DefaultSessionTimeout SSLCertificateId: !Ref SSLCertARN - LoadBalancerPort: '80' Protocol: HTTP InstancePort: '8080' InstanceProtocol: HTTP PolicyNames: - DefaultSessionTimeout LoadBalancerName: !Sub "${AppSlug}-${EnvSlug}-app-lb" SecurityGroups: - Ref: LoadBalancerSecurityGroup Subnets: - Ref: LBSubnetA - Ref: LBSubnetB Tags: - Key: service Value: !Ref TagService - Key: Name Value: !Sub "${TagName}-app-lb" - Key: environment Value: !Ref TagEnvironment - Key: createdby Value: !Ref TagCreatedBy - Key: contactnetid Value: !Ref TagContactNetId - Key: accountnumber Value: !Ref TagAccountNumber - Key: subaccount Value: !Ref TagSubAccount - Key: ticketnumber Value: !Ref TagTicketNumber - Key: resourcefunction Value: !Ref TagResourceFunction EnvApplicationLayerLoadBalancerAttachment: Type: AWS::OpsWorks::ElasticLoadBalancerAttachment Properties: ElasticLoadBalancerName: !Ref EnvApplicationLayerLoadBalancer LayerId: !Ref EnvApplicationLayer

OpsWorks Service Role

This is the role that is given to the OpsWorks service, which allows OpsWorks to manage AWS resources. This is a standard policy provided by AWS. See the AWS Documentation for OpsWorks Service Role

EnvServiceRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: - opsworks.amazonaws.com Action: - sts:AssumeRole Path: "/" Policies: - PolicyName: opsworks-service PolicyDocument: Statement: - Action: - ec2:* - iam:PassRole - cloudwatch:GetMetricStatistics - elasticloadbalancing:* - rds:* Effect: Allow Resource: - "*"

Instance Role

This is the IAM role that will be applied to the OpsWorks EC2 Instances. Any AWS specific permissions that the node might need should be defined here.

EnvInstanceRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: - ec2.amazonaws.com Action: - sts:AssumeRole Path: "/" Policies:

Access to the S3 Bucket which holds application specific files that need to be loaded on each application node. (ojdbc.jar, encrypted keystores, etc)

- PolicyName: !Sub "${AppSlug}-${EnvSlug}-s3policy" PolicyDocument: Version: '2012-10-17' Statement: - Sid: Stmt1452033379000 Effect: Allow Action: - s3:GetObject - s3:ListBucket Resource: !Sub "arn:aws:s3:::${AppS3ResourceBucket}/*"

Access to CloudWatch for the log group and log stream from each application environment.

- PolicyName: !Sub "${AppSlug}-${EnvSlug}-cloudwatchlogspolicy" PolicyDocument: Version: '2012-10-17' Statement: - Sid: cloudwatchlogsaccess Effect: Allow Action: - logs:CreateLogGroup - logs:CreateLogStream - logs:Describe* - logs:PutLogEvents Resource: - "*"

Access to the S3 Bucket which holds application specific files that need to be loaded on each application node. (ojdbc.jar, encrypted keystores, etc)

- PolicyName: !Sub "${AppSlug}-${EnvSlug}-route53policy" PolicyDocument: Version: '2012-10-17' Statement: - Sid: Stmt1491867038000 Effect: Allow Action: - route53:ChangeResourceRecordSets Resource: !Sub "arn:aws:route53:::hostedzone/ZP57AJPWE08JI"

Allow the Rhubarb container on this host to update ELB Listeners

- PolicyName: !Sub "${AppSlug}-${EnvSlug}-elbpolicy" PolicyDocument: Version: '2012-10-17' Statement: - Sid: elbdescribepolicy Effect: Allow Action: - elasticloadbalancing:DescribeListeners - elasticloadbalancing:DescribeLoadBalancers - elasticloadbalancing:DescribeLoadBalancerAttributes Resource: "*" - Sid: elbupdatepolicy Effect: Allow Action: - elasticloadbalancing:ConfigureHealthCheck - elasticloadbalancing:CreateLoadBalancerListeners - elasticloadbalancing:DeleteLoadBalancerListeners - elasticloadbalancing:ModifyLoadBalancerAttributes - elasticloadbalancing:SetLoadBalancerPoliciesForBackendServer Resource: !Sub "arn:aws:elasticloadbalancing:${AWS::Region}:${AWS::AccountId}:loadbalancer/${EnvApplicationLayerLoadBalancer}"

CloudWatch Logs Group

Create a CloudWatch Log Group for each application environment. This allows us to set the retention timeframe. UAFAWS-302 - Create dependency on CWlog Log group and EC2 instance with CWlogs agent. During CF template delete CW agent has access to recreate log group after first pass

EnvLogGroup: Type: "AWS::Logs::LogGroup" DependsOn: EnvStack Properties: LogGroupName: !Sub "${AppSlug}-${EnvSlug}-application" RetentionInDays: 7

Instance Profile

This is just a little construct to connect a set of roles together into a profile. The profile is referenced in the OpsWorks stack itself.

EnvInstanceProfile: Type: AWS::IAM::InstanceProfile Properties: Path: "/" Roles: - !Ref EnvInstanceRole

Instance Security Group

Security group for the OpsWorks application instances themselves. Needs to permit incoming traffice from the ELB, and any other authorized incoming sources.

InstanceSecurityGroup: Type: AWS::EC2::SecurityGroup Properties: GroupDescription: Allow Load Balancer and SSH to host VpcId: !Ref VPCID SecurityGroupIngress: - IpProtocol: tcp FromPort: '22' ToPort: '22' CidrIp: 0.0.0.0/0 - IpProtocol: tcp FromPort: '2022' ToPort: '2022' CidrIp: 0.0.0.0/0 - IpProtocol: tcp FromPort: '80' ToPort: '80' SourceSecurityGroupId: !Ref LoadBalancerSecurityGroup - IpProtocol: tcp FromPort: '8888' ToPort: '8888' SourceSecurityGroupId: !Ref LoadBalancerSecurityGroup - IpProtocol: tcp FromPort: '8080' ToPort: '8080' SourceSecurityGroupId: !Ref LoadBalancerSecurityGroup Tags: - Key: service Value: !Ref TagService - Key: Name Value: !Sub "${TagName}-instance-sg" - Key: environment Value: !Ref TagEnvironment - Key: createdby Value: !Ref TagCreatedBy - Key: contactnetid Value: !Ref TagContactNetId - Key: accountnumber Value: !Ref TagAccountNumber - Key: subaccount Value: !Ref TagSubAccount - Key: ticketnumber Value: !Ref TagTicketNumber - Key: resourcefunction Value: !Ref TagResourceFunction

Load Balancer Security Group

This is the Security Group that wraps the Load Balancer. This controls what network traffic is allowed into the ELB. Just web traffic is allowed from anywhere.

LoadBalancerSecurityGroup: Type: AWS::EC2::SecurityGroup Properties: GroupDescription: Allow web traffic to the Load Balancer VpcId: !Ref VPCID SecurityGroupIngress: - IpProtocol: tcp FromPort: '80' ToPort: '80' CidrIp: 0.0.0.0/0 - IpProtocol: tcp FromPort: '8080' ToPort: '8080' CidrIp: 0.0.0.0/0 - IpProtocol: tcp FromPort: '443' ToPort: '443' CidrIp: 0.0.0.0/0 Tags: - Key: service Value: !Ref TagService - Key: Name Value: !Sub "${TagName}-lb-sg" - Key: environment Value: !Ref TagEnvironment - Key: createdby Value: !Ref TagCreatedBy - Key: contactnetid Value: !Ref TagContactNetId - Key: accountnumber Value: !Ref TagAccountNumber - Key: subaccount Value: !Ref TagSubAccount - Key: ticketnumber Value: !Ref TagTicketNumber - Key: resourcefunction Value: !Ref TagResourceFunction

DB Security Group

Defines the Security Group for the RDS Database. This restricts DB access to only the devices in the InstanceSecurityGroup, so our App nodes.

DBSecurityGroup: Type: AWS::EC2::SecurityGroup Properties: GroupDescription: Allow DB traffic from Application Instances VpcId: !Ref VPCID SecurityGroupIngress: - IpProtocol: tcp FromPort: '1521' ToPort: '1521' SourceSecurityGroupId: !Ref InstanceSecurityGroup Tags: - Key: service Value: !Ref TagService - Key: Name Value: !Sub "${TagName}-db-sg" - Key: environment Value: !Ref TagEnvironment - Key: createdby Value: !Ref TagCreatedBy - Key: contactnetid Value: !Ref TagContactNetId - Key: accountnumber Value: !Ref TagAccountNumber - Key: subaccount Value: !Ref TagSubAccount - Key: ticketnumber Value: !Ref TagTicketNumber - Key: resourcefunction Value: !Ref TagResourceFunction

Route53 DNS Record

Create a DNS entry in Route53 for this environment. This creates a CNAME pointing at the DNS name of the Load Balancer.

AppDnsRecord: Type: AWS::Route53::RecordSet Properties: HostedZoneName: !Sub "${HostedZoneName}." Name: !Sub "${AppSlug}-${EnvSlug}.${HostedZoneName}." Type: CNAME TTL: '900' ResourceRecords: - !GetAtt EnvApplicationLayerLoadBalancer.DNSName

Outputs

Output values that can be viewed from the AWS CloudFormation console.

Outputs: LoadBalancerDNS: Value: !GetAtt EnvApplicationLayerLoadBalancer.DNSName AppDNS: Value: !Sub "https://${AppSlug}-${EnvSlug}.${HostedZoneName}" AppInstanceZoneA: Value: !Ref AppInstanceZoneA CloudWatchLogGroupName: Description: The name of the CloudWatch log group Value: !Ref EnvLogGroup