kfs7_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 - Label: default: Application Information Parameters: - EnvAppName - AppSlug - EnvSlug - Label: default: Load Balancer Settings Parameters: - HostedZoneName - SSLCertARN - LBSubnetA - LBSubnetB - Label: default: Instance Settings Parameters: - KFSInstanceType - RiceInstanceType - KeyName - VPCID - HostnameA - HostnameB - HostnameC - HostnameD - AppSubnetA - AppSubnetB - EFSTargetSecurityGroup - CustomCookbooksSource - Label: default: Application Settings Parameters: - AppDockerImage - RhubarbDockerImage - AppBaseURL - AppDBHostname - AppDBServicename - AppDBUsername - AppDBPassword - AppKeystorePassword - AppKeystoreEncryptionKey - AppB2BPurchaseOrderUsername - AppB2BPurchaseOrderPassword - AppB2BShoppingUsername - AppB2BShoppingPassword - AppB2BSelectSiteHostname - AppLdapUsername - AppLdapPassword - AppLdapHostname - AppNewRelicKey - AppS3ResourceBucket - AppSMTPUsername - AppSMTPPassword - Label: default: Rice Settings Parameters: - RiceDockerImage - RiceDBUsername - RiceDBPassword - RiceBaseURL - Label: default: Tags Parameters: - TagService - TagName - TagEnvironment - TagCreatedBy - TagContactNetId - TagAccountNumber - TagSubAccount - TagTicketNumber - TagResourceFunction - Label: default: SNS Stack Parameters: - SNSStackName ParameterLabels: DeploymentRoute53RegistererAccessKeyID: default: 'Deployment Route 53 Registerer Access Key ID:' DeploymentRoute53RegistererAccessKey: default: 'Deployment Route 53 Registerer Access Key:' HostedZoneId: default: 'Hosted Zone ID:' 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:' HostnameC: default: 'Hostname C:' HostnameD: default: 'Hostname D:' 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 AppB2BSelectSiteHostname: default: B2B SelectSite Hostname AppLdapUsername: default: EDS LDAP Username AppLdapPassword: default: EDS LDAP Password AppLdapHostname: default: EDS LDAP Hostname AppNewRelicKey: default: New Relic Key AppS3ResourceBucket: default: S3 Resource Bucket AppSMTPUsername: default: SMTP Username AppSMTPPassword: default: SMTP Password RiceDockerImage: default: Rice Docker Image RiceDBUsername: default: Rice DB Username RiceDBPassword: default: Rice DB Password SNSStackName: default: kfs-resources

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. NoEcho: 'true' HostedZoneId: Type: String Description: Hosted zone name (NOT the full ARN) describing the Route53 hosted zone to self-register with. 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 HostnameC: Description: Hostname to use for the secondary application instance (if this deployment will use one), such as 'a52'. Type: String HostnameD: Description: Hostname to use for the secondary application instance (if this deployment will use one), such as 'a53'. 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: Application SSL Certificate ARN 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 KFSInstanceType: Description: EC2 Instance Type Type: String Default: m4.xlarge RiceInstanceType: 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.: kuali/rhubarb:dev7' Type: String AppDockerImage: Description: 'Docker Image, i.e.: kuali/kfs7:dev' Type: String AppBaseURL: Description: 'Base Application URL: i.e.: https://ka-foo.mosaic.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' AppB2BSelectSiteHostname: Description: B2B SelectSite Hostname Type: String AppLdapUsername: Description: EDS LDAP Username Type: String NoEcho: 'true' AppLdapPassword: Description: EDS LDAP Password Type: String NoEcho: 'true' AppLdapHostname: Description: EDS LDAP Hostname Type: String 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 Default: "12AWS" 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' RiceDockerImage: Description: Rice Docker Image Type: String RiceDBUsername: Description: Rice Database Username Type: String RiceDBPassword: Description: Rice Database Password Type: String NoEcho: 'true' RiceBaseURL: Description: 'Rice Application URL: i.e.: https://rice-foo.mosaic.arizona.edu/' Type: String SNSStackName: Description: Stack Name for SNS Subscriptions Type: String Default: kfs-resources #Logic is:

If DEV, TST, we only want a single, timer-based application instance If STG, we want A1 to be a 24/7 KFS application instance, B1 to be timer-based KFS application instance. Rice will be 24/7 instances. If SUP, we don't do anything extra since its deployment is controlled by MET If PRD, want A to be 24/7 application instance, B to be timer-based application instance If it is a prototype, this will be a single NON timer instance handled by Jenkins and cloud-custodian WeekDayTimerEnv is for those environments it is decided to be up only on weekdays mon - fri.

Conditions: IsPrototypeEnv: !Not [ !Or [ !Equals [!Ref EnvSlug, "dev"], !Equals [!Ref EnvSlug, "tst"], !Equals [!Ref EnvSlug, "stg"], !Equals [!Ref EnvSlug, "sup"], !Equals [!Ref EnvSlug, "prd"], !Equals [!Ref EnvSlug, "trn"] ] ] IsStg: !Equals [!Ref EnvSlug, "stg"] IsSup: !Equals [!Ref EnvSlug, "sup"] IsPrd: !Equals [!Ref EnvSlug, "prd"] IsSingleTimerEnv: !Or [ !Equals [!Ref EnvSlug, "dev"], !Equals [!Ref EnvSlug, "tst"]] WeekDayTimerEnv: !Equals [!Ref EnvSlug, "trn"] IsMultiInstance: !Or [Condition: IsStg, Condition: IsPrd]

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 recipes 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 - ecs-utilities::zabbix_agent Deploy: - kfs::create_transactional_directories - kfs::create_logs_directories - kfs::create_integration_directories - kfs::install_configs - kfs::create_hostnameA_runnable - kfs::kfs_docker_pull_deploy - kfs::register_with_route53 - kfs::cloudwatchlogs_install Undeploy: - kfs::unregister_with_route53 CustomSecurityGroupIds: - !Ref InstanceSecurityGroup - !Ref EFSTargetSecurityGroup EnableAutoHealing: 'false' StackId: Ref: EnvStack

Rice Layer

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

RiceApplicationLayer: Type: AWS::OpsWorks::Layer Properties: Name: rice Shortname: rice 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 - ecs-utilities::zabbix_agent Deploy: - rice::create_log_directory - rice::install_configs - rice::kfs_docker_pull_deploy - rice::register_with_route53 - rice::cloudwatchlogs_install CustomSecurityGroupIds: - !Ref InstanceSecurityGroup - !Ref EFSTargetSecurityGroup EnableAutoHealing: 'false' StackId: Ref: EnvStack

OpsWorks Application

This defines the application for the OpsWorks stack. There's one Application for eac 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

Docker env variables used by kfs_docker_pull_deploy chef recipe

- Key: docker_deploy_order Value: 2 - 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,/efs/kfs/${EnvSlug}/transactional/logs/kfs:/efs/logs,/kuali-configs/configuration:/configuration:ro,/kuali-configs/security:/security:ro,/efs/kfs/${EnvSlug}/integration:/integration" - 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: b2b_selectsite_hostname Value: !Ref AppB2BSelectSiteHostname - Key: ldap_username Value: !Ref AppLdapUsername - Key: ldap_password Secure: true Value: !Ref AppLdapPassword - Key: ldap_url Value: !Ref AppLdapHostname - 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 - Key: HOSTNAME_A Value: !Sub "kfs7-${HostnameA}" - Key: HOSTNAME_B Value: !Sub "kfs7-${HostnameB}" - Key: HOSTNAME_C Value: !Sub "kfs7-${HostnameC}" - Key: HOSTNAME_D Value: !Sub "kfs7-${HostnameD}"

Really, these values should ONLY be in the Rice app, and the Rice app should do its own configuration hydration, but at the moment KFS is the only app set up to hydrate configuration files, so now it's responsible for hydrating ALL the configuration files.

- Key: rice_db_username Value: !Ref RiceDBUsername - Key: rice_db_password Secure: true Value: !Ref RiceDBPassword - Key: runnable_hostname Value: !Ref HostnameA - Key: rice_base_url Value: !Ref RiceBaseURL

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

nginx Application for KFS

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: 3 - Key: layer Value: !Ref EnvAppName - Key: docker_container Value: !Sub "${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/kuali/nginx-redirect:kfs7-2018-05-11" - Key: docker_ports Value: "0.0.0.0:8080:80,0.0.0.0:8888:8888,0.0.0.0:8088:8088" - 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

nginx Application for Rice

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.

RiceNginxApp: Type: AWS::OpsWorks::App Properties: AppSource: Type: other Name: "rice-nginx" Shortname: "rice-nginx" StackId: !Ref EnvStack Type: other Environment: - Key: docker_deploy_order Value: 3 - Key: layer

This is hardcoded to match the Shortname property of the RiceApplicationLayer

Value: rice - Key: docker_container Value: !Sub "${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/kuali/nginx-redirect:rice-2018-05-29" - Key: docker_ports Value: "0.0.0.0:8080:80,0.0.0.0:8888:8888" - Key: docker_links Value: rice

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: 4 - 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/smtp:/rhubarb-security:ro,/efs/kfs/${EnvSlug}/integration:/integration" - Key: docker_privileged Value: privileged - Key: APP_SLUG_NAME Value: !Ref AppSlug - Key: KFS_ENV_NAME Value: !Ref EnvSlug

Rice Application

KFS 7 uses a standalone Rice installation

RiceApp: Type: AWS::OpsWorks::App Properties: AppSource: Type: other Name: "rice" Shortname: "rice" StackId: !Ref EnvStack Type: other Environment: - Key: docker_deploy_order Value: 1 - Key: layer Value: rice # This is hardcoded to match the Shortname property of the RiceApplicationLayer - Key: docker_container Value: !Ref RiceDockerImage - Key: docker_ports Value: "0.0.0.0:80:80,0.0.0.0:8444:8444" - Key: docker_volumes Value: !Sub "/efs/kfs/${EnvSlug}/transactional:/transactional,/efs/kfs/${EnvSlug}/transactional/logs/rice:/efs/logs,/kuali-configs/configuration:/configuration:ro,/kuali-configs/security:/security:ro,/efs/kfs/${EnvSlug}/integration:/integration" - Key: docker_privileged Value: privileged - Key: APP_SLUG_NAME Value: !Ref AppSlug - Key: KFS_ENV_NAME Value: !Ref EnvSlug - Key: rice_base_url Value: !Ref RiceBaseURL - Key: kfs_base_url Value: !Ref AppBaseURL - Key: db_host Value: !Ref AppDBHostname - Key: db_service_name Value: !Ref AppDBServicename - 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: b2b_selectsite_hostname Value: !Ref AppB2BSelectSiteHostname - Key: ldap_username Value: !Ref AppLdapUsername - Key: ldap_password Secure: true Value: !Ref AppLdapPassword - Key: ldap_url Value: !Ref AppLdapHostname - Key: ldap_base Value: ou=People,dc=eds,dc=arizona,dc=edu - Key: rice_db_username Value: !Ref RiceDBUsername - Key: rice_db_password Secure: true Value: !Ref RiceDBPassword - Key: hosted_zone_id Value: !Ref HostedZoneId - Key: hosted_zone_name Value: !Ref HostedZoneName - Key: deployment_route53_registerer_access_key_id Value: !Ref DeploymentRoute53RegistererAccessKeyID - Key: deployment_route53_registerer_access_key Value: !Ref DeploymentRoute53RegistererAccessKey - Key: smtp_username Secure: true Value: !Ref AppSMTPUsername - Key: smtp_password Secure: true Value: !Ref AppSMTPPassword - Key: newrelic_license_key Secure: true Value: !Ref AppNewRelicKey - Key: s3resource_bucket Value: !Ref AppS3ResourceBucket - Key: HOSTNAME_A Value: !Sub "rice-${HostnameA}" - Key: HOSTNAME_B Value: !Sub "rice-${HostnameB}" - Key: efs_mounts Value: !Ref AppEFSMountInfo

OpsWorks Stack

The prototype Stack will now have -proto appended to their names. Because of the limitations of Cloud custodian + Cloud Formation templates we are going this route.

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: # if statement below determines if the name has -proto in it. Name: !If [IsPrototypeEnv, !Sub "${EnvAppName} ${EnvSlug}-proto", !Sub "${EnvAppName} ${EnvSlug}"] VpcId: !Ref VPCID ServiceRoleArn: !ImportValue fdn-iam-opsworks-service-role-arn DefaultInstanceProfileArn: !GetAtt EnvInstanceProfile.Arn DefaultOs: Amazon Linux 2018.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 subaccountnumber: !Ref TagSubAccount ticketnumber: !Ref TagTicketNumber resourcefunction: !Ref TagResourceFunction

KFS Hosts

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.

AppInstanceSingleTimerEnv: Type: AWS::OpsWorks::Instance Condition: IsSingleTimerEnv Properties: StackId: !Ref EnvStack InstanceType: !Ref KFSInstanceType SubnetId: !Ref AppSubnetA SshKeyName: !Ref KeyName Hostname: !Sub "kfs7-${HostnameA}" InstallUpdatesOnBoot: true AutoScalingType: "timer" TimeBasedAutoScaling:

5am - 10pm (non-inclusive) AZ time = 12 - 05 (non-inclusive) UTC 24 hour time

Monday: "12": "on" "13": "on" "14": "on" "15": "on" "16": "on" "17": "on" "18": "on" "19": "on" "20": "on" "21": "on" "22": "on" "23": "on" "0": "on" "1": "on" "2": "on" "3": "on" "4": "on" Tuesday: "12": "on" "13": "on" "14": "on" "15": "on" "16": "on" "17": "on" "18": "on" "19": "on" "20": "on" "21": "on" "22": "on" "23": "on" "0": "on" "1": "on" "2": "on" "3": "on" "4": "on" Wednesday: "12": "on" "13": "on" "14": "on" "15": "on" "16": "on" "17": "on" "18": "on" "19": "on" "20": "on" "21": "on" "22": "on" "23": "on" "0": "on" "1": "on" "2": "on" "3": "on" "4": "on" Thursday: "12": "on" "13": "on" "14": "on" "15": "on" "16": "on" "17": "on" "18": "on" "19": "on" "20": "on" "21": "on" "22": "on" "23": "on" "0": "on" "1": "on" "2": "on" "3": "on" "4": "on" Friday: "12": "on" "13": "on" "14": "on" "15": "on" "16": "on" "17": "on" "18": "on" "19": "on" "20": "on" "21": "on" "22": "on" "23": "on" "0": "on" "1": "on" "2": "on" "3": "on" "4": "on" Saturday: "12": "on" "13": "on" "14": "on" "15": "on" "16": "on" "17": "on" "18": "on" "19": "on" "20": "on" "21": "on" "22": "on" "23": "on" "0": "on" "1": "on" "2": "on" "3": "on" "4": "on" Sunday: "12": "on" "13": "on" "14": "on" "15": "on" "16": "on" "17": "on" "18": "on" "19": "on" "20": "on" "21": "on" "22": "on" "23": "on" "0": "on" "1": "on" "2": "on" "3": "on" "4": "on" LayerIds: - Ref: EnvApplicationLayer RootDeviceType: ebs BlockDeviceMappings: - DeviceName: ROOT_DEVICE Ebs: DeleteOnTermination: true VolumeSize: 20 VolumeType: gp2

KFS Application node for weekdays only This type of instance will be a timed instance up Monday - Friday 5AM - 10PM

AppInstanceWeekDayTimerEnv: Type: AWS::OpsWorks::Instance Condition: WeekDayTimerEnv Properties: StackId: !Ref EnvStack InstanceType: !Ref KFSInstanceType SubnetId: !Ref AppSubnetA SshKeyName: !Ref KeyName Hostname: !Sub "kfs7-${HostnameA}" InstallUpdatesOnBoot: true AutoScalingType: "timer" TimeBasedAutoScaling:

5am - 10pm (mon - fri) AZ time = 12 - 05 (non-inclusive) UTC 24 hour time

Monday: "12": "on" "13": "on" "14": "on" "15": "on" "16": "on" "17": "on" "18": "on" "19": "on" "20": "on" "21": "on" "22": "on" "23": "on" "0": "on" "1": "on" "2": "on" "3": "on" "4": "on" Tuesday: "12": "on" "13": "on" "14": "on" "15": "on" "16": "on" "17": "on" "18": "on" "19": "on" "20": "on" "21": "on" "22": "on" "23": "on" "0": "on" "1": "on" "2": "on" "3": "on" "4": "on" Wednesday: "12": "on" "13": "on" "14": "on" "15": "on" "16": "on" "17": "on" "18": "on" "19": "on" "20": "on" "21": "on" "22": "on" "23": "on" "0": "on" "1": "on" "2": "on" "3": "on" "4": "on" Thursday: "12": "on" "13": "on" "14": "on" "15": "on" "16": "on" "17": "on" "18": "on" "19": "on" "20": "on" "21": "on" "22": "on" "23": "on" "0": "on" "1": "on" "2": "on" "3": "on" "4": "on" Friday: "12": "on" "13": "on" "14": "on" "15": "on" "16": "on" "17": "on" "18": "on" "19": "on" "20": "on" "21": "on" "22": "on" "23": "on" "0": "on" "1": "on" "2": "on" "3": "on" "4": "on" LayerIds: - Ref: EnvApplicationLayer RootDeviceType: ebs BlockDeviceMappings: - DeviceName: ROOT_DEVICE Ebs: DeleteOnTermination: true VolumeSize: 20 VolumeType: gp2 AppInstanceZoneA1: Type: AWS::OpsWorks::Instance Condition: IsMultiInstance Properties: StackId: !Ref EnvStack InstanceType: !Ref KFSInstanceType SubnetId: !Ref AppSubnetA SshKeyName: !Ref KeyName Hostname: !Sub "kfs7-${HostnameA}" InstallUpdatesOnBoot: true LayerIds: - Ref: EnvApplicationLayer RootDeviceType: ebs BlockDeviceMappings: - DeviceName: ROOT_DEVICE Ebs: DeleteOnTermination: true VolumeSize: 20 VolumeType: gp2 AppInstanceZoneB1: Type: AWS::OpsWorks::Instance Condition: IsMultiInstance Properties: StackId: !Ref EnvStack InstanceType: !Ref KFSInstanceType SubnetId: !Ref AppSubnetB SshKeyName: !Ref KeyName Hostname: !Sub "kfs7-${HostnameB}" InstallUpdatesOnBoot: true AutoScalingType: "timer" TimeBasedAutoScaling:

5am - 8pm (non-inclusive) AZ time = 12 - 03 (non-inclusive) UTC 24 hour time

Monday: "12": "on" "13": "on" "14": "on" "15": "on" "16": "on" "17": "on" "18": "on" "19": "on" "20": "on" "21": "on" "22": "on" "23": "on" "0": "on" "1": "on" "2": "on" Tuesday: "12": "on" "13": "on" "14": "on" "15": "on" "16": "on" "17": "on" "18": "on" "19": "on" "20": "on" "21": "on" "22": "on" "23": "on" "0": "on" "1": "on" "2": "on" Wednesday: "12": "on" "13": "on" "14": "on" "15": "on" "16": "on" "17": "on" "18": "on" "19": "on" "20": "on" "21": "on" "22": "on" "23": "on" "0": "on" "1": "on" "2": "on" Thursday: "12": "on" "13": "on" "14": "on" "15": "on" "16": "on" "17": "on" "18": "on" "19": "on" "20": "on" "21": "on" "22": "on" "23": "on" "0": "on" "1": "on" "2": "on" Friday: "12": "on" "13": "on" "14": "on" "15": "on" "16": "on" "17": "on" "18": "on" "19": "on" "20": "on" "21": "on" "22": "on" "23": "on" "0": "on" "1": "on" "2": "on" Saturday: "12": "on" "13": "on" "14": "on" "15": "on" "16": "on" "17": "on" "18": "on" "19": "on" "20": "on" "21": "on" "22": "on" "23": "on" "0": "on" "1": "on" "2": "on" Sunday: "12": "on" "13": "on" "14": "on" "15": "on" "16": "on" "17": "on" "18": "on" "19": "on" "20": "on" "21": "on" "22": "on" "23": "on" "0": "on" "1": "on" "2": "on" LayerIds: - Ref: EnvApplicationLayer RootDeviceType: ebs BlockDeviceMappings: - DeviceName: ROOT_DEVICE Ebs: DeleteOnTermination: true VolumeSize: 20 VolumeType: gp2 AppInstanceZoneA2: Type: AWS::OpsWorks::Instance Condition: IsPrd Properties: StackId: !Ref EnvStack InstanceType: !Ref KFSInstanceType SubnetId: !Ref AppSubnetA SshKeyName: !Ref KeyName Hostname: !Sub "kfs7-${HostnameC}" InstallUpdatesOnBoot: true AutoScalingType: "timer" TimeBasedAutoScaling:

5am - 8pm (non-inclusive) AZ time = 12 - 03 (non-inclusive) UTC 24 hour time

Monday: "12": "on" "13": "on" "14": "on" "15": "on" "16": "on" "17": "on" "18": "on" "19": "on" "20": "on" "21": "on" "22": "on" "23": "on" "0": "on" "1": "on" "2": "on" Tuesday: "12": "on" "13": "on" "14": "on" "15": "on" "16": "on" "17": "on" "18": "on" "19": "on" "20": "on" "21": "on" "22": "on" "23": "on" "0": "on" "1": "on" "2": "on" Wednesday: "12": "on" "13": "on" "14": "on" "15": "on" "16": "on" "17": "on" "18": "on" "19": "on" "20": "on" "21": "on" "22": "on" "23": "on" "0": "on" "1": "on" "2": "on" Thursday: "12": "on" "13": "on" "14": "on" "15": "on" "16": "on" "17": "on" "18": "on" "19": "on" "20": "on" "21": "on" "22": "on" "23": "on" "0": "on" "1": "on" "2": "on" Friday: "12": "on" "13": "on" "14": "on" "15": "on" "16": "on" "17": "on" "18": "on" "19": "on" "20": "on" "21": "on" "22": "on" "23": "on" "0": "on" "1": "on" "2": "on" Saturday: "12": "on" "13": "on" "14": "on" "15": "on" "16": "on" "17": "on" "18": "on" "19": "on" "20": "on" "21": "on" "22": "on" "23": "on" "0": "on" "1": "on" "2": "on" Sunday: "12": "on" "13": "on" "14": "on" "15": "on" "16": "on" "17": "on" "18": "on" "19": "on" "20": "on" "21": "on" "22": "on" "23": "on" "0": "on" "1": "on" "2": "on" LayerIds: - Ref: EnvApplicationLayer RootDeviceType: ebs BlockDeviceMappings: - DeviceName: ROOT_DEVICE Ebs: DeleteOnTermination: true VolumeSize: 20 VolumeType: gp2 AppInstanceZoneB2: Type: AWS::OpsWorks::Instance Condition: IsPrd Properties: StackId: !Ref EnvStack InstanceType: !Ref KFSInstanceType SubnetId: !Ref AppSubnetB SshKeyName: !Ref KeyName Hostname: !Sub "kfs7-${HostnameD}" InstallUpdatesOnBoot: true AutoScalingType: "timer" TimeBasedAutoScaling:

5am - 8pm (non-inclusive) AZ time = 12 - 03 (non-inclusive) UTC 24 hour time

Monday: "12": "on" "13": "on" "14": "on" "15": "on" "16": "on" "17": "on" "18": "on" "19": "on" "20": "on" "21": "on" "22": "on" "23": "on" "0": "on" "1": "on" "2": "on" Tuesday: "12": "on" "13": "on" "14": "on" "15": "on" "16": "on" "17": "on" "18": "on" "19": "on" "20": "on" "21": "on" "22": "on" "23": "on" "0": "on" "1": "on" "2": "on" Wednesday: "12": "on" "13": "on" "14": "on" "15": "on" "16": "on" "17": "on" "18": "on" "19": "on" "20": "on" "21": "on" "22": "on" "23": "on" "0": "on" "1": "on" "2": "on" Thursday: "12": "on" "13": "on" "14": "on" "15": "on" "16": "on" "17": "on" "18": "on" "19": "on" "20": "on" "21": "on" "22": "on" "23": "on" "0": "on" "1": "on" "2": "on" Friday: "12": "on" "13": "on" "14": "on" "15": "on" "16": "on" "17": "on" "18": "on" "19": "on" "20": "on" "21": "on" "22": "on" "23": "on" "0": "on" "1": "on" "2": "on" Saturday: "12": "on" "13": "on" "14": "on" "15": "on" "16": "on" "17": "on" "18": "on" "19": "on" "20": "on" "21": "on" "22": "on" "23": "on" "0": "on" "1": "on" "2": "on" Sunday: "12": "on" "13": "on" "14": "on" "15": "on" "16": "on" "17": "on" "18": "on" "19": "on" "20": "on" "21": "on" "22": "on" "23": "on" "0": "on" "1": "on" "2": "on" LayerIds: - Ref: EnvApplicationLayer RootDeviceType: ebs BlockDeviceMappings: - DeviceName: ROOT_DEVICE Ebs: DeleteOnTermination: true VolumeSize: 20 VolumeType: gp2 AppInstanceSupEnv: Type: AWS::OpsWorks::Instance Condition: IsSup Properties: StackId: !Ref EnvStack InstanceType: !Ref KFSInstanceType SubnetId: !Ref AppSubnetA SshKeyName: !Ref KeyName Hostname: !Sub "kfs7-${HostnameA}" InstallUpdatesOnBoot: true LayerIds: - Ref: EnvApplicationLayer RootDeviceType: ebs BlockDeviceMappings: - DeviceName: ROOT_DEVICE Ebs: DeleteOnTermination: true VolumeSize: 20 VolumeType: gp2 AppInstancePrototypeEnv: Type: AWS::OpsWorks::Instance Condition: IsPrototypeEnv Properties: StackId: !Ref EnvStack InstanceType: !Ref KFSInstanceType SubnetId: !Ref AppSubnetA SshKeyName: !Ref KeyName Hostname: !Sub "kfs7-${HostnameA}" InstallUpdatesOnBoot: true LayerIds: - Ref: EnvApplicationLayer RootDeviceType: ebs BlockDeviceMappings: - DeviceName: ROOT_DEVICE Ebs: DeleteOnTermination: true VolumeSize: 20 VolumeType: gp2

Rice Hosts

These are the various hosts for the Rice application

Rice 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.

RiceInstanceSingleTimerEnv: Type: AWS::OpsWorks::Instance Condition: IsSingleTimerEnv Properties: StackId: !Ref EnvStack InstanceType: !Ref RiceInstanceType SubnetId: !Ref AppSubnetA SshKeyName: !Ref KeyName Hostname: !Sub "rice-${HostnameA}" InstallUpdatesOnBoot: true AutoScalingType: "timer" TimeBasedAutoScaling:

5am - 10pm (non-inclusive) AZ time = 12 - 05 (non-inclusive) UTC 24 hour time

Monday: "12": "on" "13": "on" "14": "on" "15": "on" "16": "on" "17": "on" "18": "on" "19": "on" "20": "on" "21": "on" "22": "on" "23": "on" "0": "on" "1": "on" "2": "on" "3": "on" "4": "on" Tuesday: "12": "on" "13": "on" "14": "on" "15": "on" "16": "on" "17": "on" "18": "on" "19": "on" "20": "on" "21": "on" "22": "on" "23": "on" "0": "on" "1": "on" "2": "on" "3": "on" "4": "on" Wednesday: "12": "on" "13": "on" "14": "on" "15": "on" "16": "on" "17": "on" "18": "on" "19": "on" "20": "on" "21": "on" "22": "on" "23": "on" "0": "on" "1": "on" "2": "on" "3": "on" "4": "on" Thursday: "12": "on" "13": "on" "14": "on" "15": "on" "16": "on" "17": "on" "18": "on" "19": "on" "20": "on" "21": "on" "22": "on" "23": "on" "0": "on" "1": "on" "2": "on" "3": "on" "4": "on" Friday: "12": "on" "13": "on" "14": "on" "15": "on" "16": "on" "17": "on" "18": "on" "19": "on" "20": "on" "21": "on" "22": "on" "23": "on" "0": "on" "1": "on" "2": "on" "3": "on" "4": "on" Saturday: "12": "on" "13": "on" "14": "on" "15": "on" "16": "on" "17": "on" "18": "on" "19": "on" "20": "on" "21": "on" "22": "on" "23": "on" "0": "on" "1": "on" "2": "on" "3": "on" "4": "on" Sunday: "12": "on" "13": "on" "14": "on" "15": "on" "16": "on" "17": "on" "18": "on" "19": "on" "20": "on" "21": "on" "22": "on" "23": "on" "0": "on" "1": "on" "2": "on" "3": "on" "4": "on" LayerIds: - Ref: RiceApplicationLayer RootDeviceType: ebs BlockDeviceMappings: - DeviceName: ROOT_DEVICE Ebs: DeleteOnTermination: true VolumeSize: 20 VolumeType: gp2

Rice Application node for weekdays only This type of instance will be a timed instance up Monday - Friday 5AM - 10PM

RiceWeekDayTimerEnv: Type: AWS::OpsWorks::Instance Condition: WeekDayTimerEnv Properties: StackId: !Ref EnvStack InstanceType: !Ref RiceInstanceType SubnetId: !Ref AppSubnetA SshKeyName: !Ref KeyName Hostname: !Sub "rice-${HostnameA}" InstallUpdatesOnBoot: true AutoScalingType: "timer" TimeBasedAutoScaling:

5am - 10pm (non-inclusive) AZ time = 12 - 05 (non-inclusive) UTC 24 hour time

Monday: "12": "on" "13": "on" "14": "on" "15": "on" "16": "on" "17": "on" "18": "on" "19": "on" "20": "on" "21": "on" "22": "on" "23": "on" "0": "on" "1": "on" "2": "on" "3": "on" "4": "on" Tuesday: "12": "on" "13": "on" "14": "on" "15": "on" "16": "on" "17": "on" "18": "on" "19": "on" "20": "on" "21": "on" "22": "on" "23": "on" "0": "on" "1": "on" "2": "on" "3": "on" "4": "on" Wednesday: "12": "on" "13": "on" "14": "on" "15": "on" "16": "on" "17": "on" "18": "on" "19": "on" "20": "on" "21": "on" "22": "on" "23": "on" "0": "on" "1": "on" "2": "on" "3": "on" "4": "on" Thursday: "12": "on" "13": "on" "14": "on" "15": "on" "16": "on" "17": "on" "18": "on" "19": "on" "20": "on" "21": "on" "22": "on" "23": "on" "0": "on" "1": "on" "2": "on" "3": "on" "4": "on" Friday: "12": "on" "13": "on" "14": "on" "15": "on" "16": "on" "17": "on" "18": "on" "19": "on" "20": "on" "21": "on" "22": "on" "23": "on" "0": "on" "1": "on" "2": "on" "3": "on" "4": "on" LayerIds: - Ref: RiceApplicationLayer RootDeviceType: ebs BlockDeviceMappings: - DeviceName: ROOT_DEVICE Ebs: DeleteOnTermination: true VolumeSize: 20 VolumeType: gp2 RiceInstanceZoneA: Type: AWS::OpsWorks::Instance Condition: IsMultiInstance Properties: StackId: !Ref EnvStack InstanceType: !Ref RiceInstanceType SubnetId: !Ref AppSubnetA SshKeyName: !Ref KeyName Hostname: !Sub "rice-${HostnameA}" InstallUpdatesOnBoot: true LayerIds: - Ref: RiceApplicationLayer RootDeviceType: ebs BlockDeviceMappings: - DeviceName: ROOT_DEVICE Ebs: DeleteOnTermination: true VolumeSize: 20 VolumeType: gp2 RiceInstanceZoneB: Type: AWS::OpsWorks::Instance Condition: IsMultiInstance Properties: StackId: !Ref EnvStack InstanceType: !Ref RiceInstanceType SubnetId: !Ref AppSubnetB SshKeyName: !Ref KeyName Hostname: !Sub "rice-${HostnameB}" InstallUpdatesOnBoot: true LayerIds: - Ref: RiceApplicationLayer RootDeviceType: ebs BlockDeviceMappings: - DeviceName: ROOT_DEVICE Ebs: DeleteOnTermination: true VolumeSize: 20 VolumeType: gp2 RiceInstanceSupEnv: Type: AWS::OpsWorks::Instance Condition: IsSup Properties: StackId: !Ref EnvStack InstanceType: !Ref RiceInstanceType SubnetId: !Ref AppSubnetA SshKeyName: !Ref KeyName Hostname: !Sub "rice-${HostnameA}" InstallUpdatesOnBoot: true LayerIds: - Ref: RiceApplicationLayer RootDeviceType: ebs BlockDeviceMappings: - DeviceName: ROOT_DEVICE Ebs: DeleteOnTermination: true VolumeSize: 20 VolumeType: gp2 RiceInstancePrototypeEnv: Type: AWS::OpsWorks::Instance Condition: IsPrototypeEnv Properties: StackId: !Ref EnvStack InstanceType: !Ref RiceInstanceType SubnetId: !Ref AppSubnetA SshKeyName: !Ref KeyName Hostname: !Sub "rice-${HostnameA}" InstallUpdatesOnBoot: true LayerIds: - Ref: RiceApplicationLayer RootDeviceType: ebs BlockDeviceMappings: - DeviceName: ROOT_DEVICE Ebs: DeleteOnTermination: true VolumeSize: 20 VolumeType: gp2

KFS 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 CrossZone: true ConnectionSettings: IdleTimeout: '300' HealthCheck: HealthyThreshold: '5' Interval: '60' Target: TCP:80 Timeout: '10' UnhealthyThreshold: '3' ConnectionDrainingPolicy: Enabled: 'true' Timeout: '300' AccessLoggingPolicy: "S3BucketName": 'edu.arizona.iso.elb.logs' S3BucketPrefix: !Sub "${AppSlug}-${EnvSlug}-app" EmitInterval: 5 Enabled: True LBCookieStickinessPolicy: - CookieExpirationPeriod: '28800' PolicyName: DefaultSessionTimeout Listeners: - LoadBalancerPort: '443' Protocol: HTTPS InstancePort: '8088' InstanceProtocol: HTTP PolicyNames: - DefaultSessionTimeout SSLCertificateId: !Ref SSLCertARN - LoadBalancerPort: '80' Protocol: HTTP InstancePort: '8080' InstanceProtocol: HTTP PolicyNames: - DefaultSessionTimeout - LoadBalancerPort: '8088' Protocol: HTTP InstancePort: '8080' InstanceProtocol: HTTP PolicyNames: - DefaultSessionTimeout - LoadBalancerPort: '8006' Protocol: HTTP InstancePort: '8006' 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}-kfs-lb" - Key: environment Value: !Ref TagEnvironment - Key: createdby Value: !Ref TagCreatedBy - Key: contactnetid Value: !Ref TagContactNetId - Key: accountnumber Value: !Ref TagAccountNumber - Key: ticketnumber Value: !Ref TagTicketNumber - Key: resourcefunction Value: !Ref TagResourceFunction EnvApplicationLayerLoadBalancerAttachment: Type: AWS::OpsWorks::ElasticLoadBalancerAttachment Properties: ElasticLoadBalancerName: !Ref EnvApplicationLayerLoadBalancer LayerId: !Ref EnvApplicationLayer

Rice Elastic Load Balancer (ELB)

Defines the Load Balancer for Rice. 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)

RiceApplicationLayerLoadBalancer: Type: AWS::ElasticLoadBalancing::LoadBalancer Properties: Scheme: internal CrossZone: true ConnectionSettings: IdleTimeout: '300' HealthCheck: HealthyThreshold: '5' Interval: '60' Target: TCP:80 Timeout: '10' UnhealthyThreshold: '3' ConnectionDrainingPolicy: Enabled: 'true' Timeout: '300' AccessLoggingPolicy: "S3BucketName": 'edu.arizona.iso.elb.logs' S3BucketPrefix: !Sub "${AppSlug}-${EnvSlug}-rice" EmitInterval: 5 Enabled: True 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 - LoadBalancerPort: '8006' Protocol: HTTP InstancePort: '8006' InstanceProtocol: HTTP PolicyNames: - DefaultSessionTimeout LoadBalancerName: !Sub "rice-${EnvSlug}-app-lb" SecurityGroups: - Ref: LoadBalancerSecurityGroup Subnets: - Ref: LBSubnetA - Ref: LBSubnetB Tags: - Key: service Value: !Ref TagService - Key: Name Value: !Sub "${TagName}-rice-lb" - Key: environment Value: !Ref TagEnvironment - Key: createdby Value: !Ref TagCreatedBy - Key: contactnetid Value: !Ref TagContactNetId - Key: accountnumber Value: !Ref TagAccountNumber - Key: ticketnumber Value: !Ref TagTicketNumber - Key: resourcefunction Value: !Ref TagResourceFunction RiceApplicationLayerLoadBalancerAttachment: Type: AWS::OpsWorks::ElasticLoadBalancerAttachment Properties: ElasticLoadBalancerName: !Ref RiceApplicationLayerLoadBalancer LayerId: !Ref RiceApplicationLayer

CloudWatch Alarms

CloudWatch Alarm For ELB and Instances

AlarmUnhealthyInstanceOnKFSELB: Type: "AWS::CloudWatch::Alarm" Condition: IsPrd Properties: ActionsEnabled: True AlarmActions: - Fn::ImportValue: !Sub "${SNSStackName}-SNS-KFSPRD" AlarmName: KFSELBUnhealthyInstanceCountExceeded AlarmDescription: Trigger an alert when there at least one unhealthy KFS EC2 instance ComparisonOperator: GreaterThanThreshold Dimensions: - Name: LoadBalancerName Value: !Ref EnvApplicationLayerLoadBalancer EvaluationPeriods: 10 MetricName: UnHealthyHostCount Namespace: AWS/ELB Period: 60 Statistic: Minimum Threshold: 0 Unit: Count AlarmUnhealthyInstanceOnRiceELB: Type: "AWS::CloudWatch::Alarm" Condition: IsPrd Properties: ActionsEnabled: True AlarmActions: - Fn::ImportValue: !Sub "${SNSStackName}-SNS-RICEPRD" AlarmName: RiceELBUnhealthyInstanceCountExceeded AlarmDescription: Trigger an alert when there at least one unhealthy Rice EC2 instance ComparisonOperator: GreaterThanThreshold Dimensions: - Name: LoadBalancerName Value: !Ref RiceApplicationLayerLoadBalancer EvaluationPeriods: 10 MetricName: UnHealthyHostCount Namespace: AWS/ELB Period: 60 Statistic: Minimum Threshold: 0 Unit: Count

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: "/" ManagedPolicyArns: - arn:aws:iam::aws:policy/service-role/AmazonEC2RoleforSSM - arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly 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 - elasticloadbalancing:CreateLBCookieStickinessPolicy Resource: "*" - Sid: elbupdatepolicy Effect: Allow Action: - elasticloadbalancing:ConfigureHealthCheck - elasticloadbalancing:CreateLoadBalancerListeners - elasticloadbalancing:DeleteLoadBalancerListeners - elasticloadbalancing:ModifyLoadBalancerAttributes - elasticloadbalancing:SetLoadBalancerPoliciesForBackendServer - elasticloadbalancing:SetLoadBalancerPoliciesOfListener Resource: - !Sub "arn:aws:elasticloadbalancing:${AWS::Region}:${AWS::AccountId}:loadbalancer/${EnvApplicationLayerLoadBalancer}" - !Sub "arn:aws:elasticloadbalancing:${AWS::Region}:${AWS::AccountId}:loadbalancer/${RiceApplicationLayerLoadBalancer}" - PolicyName: !Sub "${AppSlug}-${EnvSlug}-zabbixPolicy" PolicyDocument: Version: '2012-10-17' Statement: - Sid: StmtSsmParameterAccess Effect: Allow Action: - "ssm:GetParameters" Resource: - !Sub "arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/Zabbix*" - Sid: StmtKmsAccess Effect: Allow Action: - "kms:Decrypt" Resource: - !Sub "arn:aws:kms:${AWS::Region}:${AWS::AccountId}:key/alias/aws/ssm"

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: '8080' ToPort: '8080' SourceSecurityGroupId: !Ref LoadBalancerSecurityGroup - IpProtocol: tcp FromPort: '8088' ToPort: '8088' SourceSecurityGroupId: !Ref LoadBalancerSecurityGroup

Maintenance Page port

- IpProtocol: tcp FromPort: '8888' ToPort: '8888' SourceSecurityGroupId: !Ref LoadBalancerSecurityGroup

Rice ports

- IpProtocol: tcp FromPort: '8006' ToPort: '8006' SourceSecurityGroupId: !Ref LoadBalancerSecurityGroup

Zabbix port

- IpProtocol: tcp FromPort: '10050' ToPort: '10050' CidrIp: 128.196.130.92/32 Description: "Allow zabbix-proxy02.uits.arizona.edu" 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: 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 Description: "HTTP to HTTPS Redirection" - IpProtocol: tcp FromPort: '8088' ToPort: '8088' CidrIp: 0.0.0.0/0 Description: "Handle oddball redirects from rice UAF-6819" - IpProtocol: tcp FromPort: '8080' ToPort: '8080' CidrIp: 0.0.0.0/0 - IpProtocol: tcp FromPort: '443' ToPort: '443' CidrIp: 0.0.0.0/0 Description: "Normal Application Flow" - IpProtocol: tcp FromPort: '8081' ToPort: '8081' CidrIp: 0.0.0.0/0 - IpProtocol: tcp FromPort: '8006' ToPort: '8006' 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: ticketnumber Value: !Ref TagTicketNumber - Key: resourcefunction Value: !Ref TagResourceFunction

KFS Route53 DNS Record

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

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

Rice Route53 DNS Record

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

RiceDnsRecord: Type: AWS::Route53::RecordSet Properties: HostedZoneName: !Sub "${HostedZoneName}." Name: !Sub "rice-${EnvSlug}.${HostedZoneName}." Type: CNAME TTL: '900' ResourceRecords: - !GetAtt RiceApplicationLayerLoadBalancer.DNSName #Use to add the Instance SG to the DB SG #This is needed to have the app servers connect to the DB AppInstToDB1521: Type: "AWS::EC2::SecurityGroupIngress" Properties: GroupId: !ImportValue "Kuali-DbSg-DbSecurityGroup" IpProtocol: "tcp" FromPort: "1521" ToPort: "1521" SourceSecurityGroupId: !Ref InstanceSecurityGroup Description: "App Instance"

Outputs

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

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