From 1bfc1da150afe1f8b18acc8439bed740f3cfb820 Mon Sep 17 00:00:00 2001 From: Kat Schelonka <34227334+kschelonka@users.noreply.github.com> Date: Thu, 29 Aug 2024 11:29:07 -0700 Subject: [PATCH 1/2] chore(item_id): remove mirroring code (#707) We were able to successfully execute the bigint migration on the database, so we do not need this mirroring code anymore. --- servers/list-api/src/config/index.ts | 9 +- .../src/dataService/pocketSavesService.ts | 18 +- .../src/dataService/savedItemsService.ts | 146 +----- .../integrations/writeMirror.integration.ts | 441 ------------------ 4 files changed, 13 insertions(+), 601 deletions(-) delete mode 100644 servers/list-api/src/test/integrations/writeMirror.integration.ts diff --git a/servers/list-api/src/config/index.ts b/servers/list-api/src/config/index.ts index 47a6d49f9..b9fea929c 100644 --- a/servers/list-api/src/config/index.ts +++ b/servers/list-api/src/config/index.ts @@ -104,14 +104,7 @@ export default { unleash: { clientKey: process.env.UNLEASH_KEY || 'unleash-key-fake', endpoint: process.env.UNLEASH_ENDPOINT || 'http://localhost:4242/api', - flags: { - mirrorWrites: { - name: 'temp.backend.list_table_mirror_writes_enabled', - // TODO(@kschelon): Change this before rollout - // POCKET-9216 - fallback: false, - }, - }, + flags: {}, }, snowplow: { endpoint: process.env.SNOWPLOW_ENDPOINT || 'localhost:9090', diff --git a/servers/list-api/src/dataService/pocketSavesService.ts b/servers/list-api/src/dataService/pocketSavesService.ts index 500574ba7..92e843b07 100644 --- a/servers/list-api/src/dataService/pocketSavesService.ts +++ b/servers/list-api/src/dataService/pocketSavesService.ts @@ -4,7 +4,7 @@ import { mysqlDateConvert, mysqlTimeString, setDifference } from './utils'; import { PocketSaveStatus } from '../types'; import { NotFoundError } from '@pocket-tools/apollo-utils'; import config from '../config'; -import { SavedItemDataService } from './savedItemsService'; + import { ListResult, RawListResult, @@ -84,13 +84,7 @@ export class PocketSaveDataService { this.apiId = context.apiId; this.db = context.dbClient; this.userId = context.userId; - this.flags = { - mirrorWrites: this.context.unleash.isEnabled( - config.unleash.flags.mirrorWrites.name, - undefined, - config.unleash.flags.mirrorWrites.fallback, - ), - }; + this.flags = {}; } public static convertListResult(listResult: null): null; @@ -285,14 +279,6 @@ export class PocketSaveDataService { if (updated.length !== ids.length) { throw new NotFoundError('At least one ID was not found'); } - // Mirror writes to "shadow" table for itemId overflow mitigation - if (this.flags.mirrorWrites) { - await Promise.all( - updated.map((row) => - SavedItemDataService.syncShadowTable(row, trx), - ), - ); - } }); } catch (error) { // Capture NotFoundError thrown by inner block, and use response diff --git a/servers/list-api/src/dataService/savedItemsService.ts b/servers/list-api/src/dataService/savedItemsService.ts index ed9ecac5d..64ccc1ba1 100644 --- a/servers/list-api/src/dataService/savedItemsService.ts +++ b/servers/list-api/src/dataService/savedItemsService.ts @@ -48,13 +48,7 @@ export class SavedItemDataService { this.db = context.dbClient; this.userId = context.userId; this.apiId = context.apiId; - this.flags = { - mirrorWrites: this.context.unleash.isEnabled( - config.unleash.flags.mirrorWrites.name, - undefined, - config.unleash.flags.mirrorWrites.fallback, - ), - }; + this.flags = {}; } public static convertDbResultStatus>( @@ -236,10 +230,6 @@ export class SavedItemDataService { api_id_updated: this.apiId, }) .where({ item_id: itemId, user_id: this.userId }); - const row = await this.getSavedItemByIdRaw(itemId, trx); - if (row != null && this.flags.mirrorWrites) { - await SavedItemDataService.syncShadowTable(row, trx); - } }); return await this.getSavedItemById(itemId); } @@ -273,59 +263,10 @@ export class SavedItemDataService { api_id_updated: this.apiId, }) .where({ item_id: itemId, user_id: this.userId }); - const row = await this.getSavedItemByIdRaw(itemId, trx); - if (row != null && this.flags.mirrorWrites) { - await SavedItemDataService.syncShadowTable(row, trx); - } }); return await this.getSavedItemById(itemId); } - /** - * Mirror writes from `list` table to `list_schema_update` table - * as part of item_id overflow mitigation. - * @param rows the rows to copy into the shadow table - * @param trx the transaction object to use for the copy - */ - public static async syncShadowTableBulk( - rows: RawListResult[], - trx: Knex.Transaction, - ) { - const input = rows.map((row) => - Object.keys(row).reduce((obj, key) => { - if (isNaN(row[key]) && row[key] instanceof Date) { - // Convert "Invalid Date" into the mysql zero-date - obj[key] = '0000-00-00 00:00:00'; - } else { - obj[key] = row[key]; - } - return obj; - }, {}), - ); - return trx('list_schema_update').insert(input).onConflict().merge(); - } - /** - * Mirror writes from `list` table to `list_schema_update` table - * as part of item_id overflow mitigation. - * @param rows the rows to copy into the shadow table - * @param trx the transaction object to use for the copy - */ - public static async syncShadowTable( - row: RawListResult, - trx: Knex.Transaction, - ) { - const input = Object.keys(row).reduce((obj, key) => { - if (isNaN(row[key]) && row[key] instanceof Date) { - // Convert "Invalid Date" into the mysql zero-date - obj[key] = '0000-00-00 00:00:00'; - } else { - obj[key] = row[key]; - } - return obj; - }, {}); - return trx('list_schema_update').insert(input).onConflict().merge(); - } - /** * Delete a saved item. Since we delete from multiple tables, * we perform the entire operation as a single transaction @@ -366,10 +307,7 @@ export class SavedItemDataService { }) .where({ item_id: itemId, user_id: this.userId }); - const row = await this.getSavedItemByIdRaw(itemId, transaction); - if (row != null && this.flags.mirrorWrites) { - await SavedItemDataService.syncShadowTable(row, transaction); - } + await this.getSavedItemByIdRaw(itemId, transaction); await transaction.commit(); } catch (err) { await transaction.rollback(); @@ -404,10 +342,7 @@ export class SavedItemDataService { api_id_updated: this.apiId, }) .where({ item_id: itemId, user_id: this.userId }); - const row = await this.getSavedItemByIdRaw(itemId, trx); - if (row != null && this.flags.mirrorWrites) { - await SavedItemDataService.syncShadowTable(row, trx); - } + await this.getSavedItemByIdRaw(itemId, trx); }); return await this.getSavedItemById(itemId); } @@ -437,10 +372,6 @@ export class SavedItemDataService { api_id_updated: this.apiId, }) .where({ item_id: itemId, user_id: this.userId }); - const row = await this.getSavedItemByIdRaw(itemId, trx); - if (row != null && this.flags.mirrorWrites) { - await SavedItemDataService.syncShadowTable(row, trx); - } }); return await this.getSavedItemById(itemId); } @@ -481,10 +412,6 @@ export class SavedItemDataService { }) .onConflict() .merge(); - const row = await this.getSavedItemByIdRaw(item.itemId, trx); - if (row != null && this.flags.mirrorWrites) { - await SavedItemDataService.syncShadowTable(row, trx); - } }); return await this.getSavedItemById(item.itemId.toString()); } @@ -525,19 +452,11 @@ export class SavedItemDataService { trx: Knex.Transaction, timestamp?: Date, ) { - const trxModifier = this.flags.mirrorWrites ? 2 : 1; - const itemBatches = chunk( - itemIds, - config.database.maxTransactionSize / trxModifier, - ); + const itemBatches = chunk(itemIds, config.database.maxTransactionSize); itemBatches.flatMap(async (ids) => { - if (this.flags.mirrorWrites) { - await this.mirroredListItemUpdateMany(ids, trx, timestamp); - } else { - await this.listItemUpdateBuilder(timestamp) - .whereIn('item_id', ids) - .transacting(trx); - } + await this.listItemUpdateBuilder(timestamp) + .whereIn('item_id', ids) + .transacting(trx); }); } @@ -551,13 +470,9 @@ export class SavedItemDataService { trx: Knex.Transaction, timestamp?: Date, ): Promise { - if (this.flags.mirrorWrites) { - await this.mirroredListItemUpdateOne(itemId, trx, timestamp); - } else { - await this.listItemUpdateBuilder(timestamp) - .where('item_id', itemId) - .transacting(trx); - } + await this.listItemUpdateBuilder(timestamp) + .where('item_id', itemId) + .transacting(trx); } /** @@ -592,45 +507,4 @@ export class SavedItemDataService { .andWhere('user_id', this.userId) .from('list'); } - - private async mirroredListItemUpdateOne( - itemId: string, - trx: Knex.Transaction, - timestamp?: Date, - ) { - await trx('list') - .update({ - time_updated: SavedItemDataService.formatDate(timestamp ?? new Date()), - api_id_updated: this.apiId, - }) - .where('item_id', itemId) - .andWhere('user_id', this.userId); - const updatedRow = await trx('list') - .select('*') - .from('list') - .where('item_id', itemId) - .andWhere('user_id', this.userId) - .first(); - await SavedItemDataService.syncShadowTable(updatedRow, trx); - } - - private async mirroredListItemUpdateMany( - itemIds: string[], - trx: Knex.Transaction, - timestamp?: Date, - ) { - await trx('list') - .update({ - time_updated: SavedItemDataService.formatDate(timestamp ?? new Date()), - api_id_updated: this.apiId, - }) - .whereIn('item_id', itemIds) - .andWhere('user_id', this.userId); - const updatedRows = await trx('list') - .select('*') - .from('list') - .whereIn('item_id', itemIds) - .andWhere('user_id', this.userId); - await SavedItemDataService.syncShadowTableBulk(updatedRows, trx); - } } diff --git a/servers/list-api/src/test/integrations/writeMirror.integration.ts b/servers/list-api/src/test/integrations/writeMirror.integration.ts deleted file mode 100644 index cf5a0e861..000000000 --- a/servers/list-api/src/test/integrations/writeMirror.integration.ts +++ /dev/null @@ -1,441 +0,0 @@ -import { - PocketSaveDataService, - SavedItemDataService, - TagDataService, -} from '../../dataService'; -import { writeClient } from '../../database/client'; -import { ItemResponse } from '../../externalCaller/parserCaller'; -import { SavedItemUpsertInput } from '../../types'; -import { expect } from '@jest/globals'; -import unleashClient from '../../featureFlags/mockClient'; -import config from '../../config'; -import { ContextManager } from '../../server/context'; - -function areBothNaN(a, b) { - if (isNaN(a) && isNaN(b)) { - return true; - } else if (isNaN(a) || isNaN(b)) { - return false; - } else { - return undefined; - } -} - -expect.addEqualityTesters([areBothNaN]); - -describe('List API mirroring', () => { - const db = writeClient(); - const date = new Date(); - const epochDate = date.getTime() / 1000; - const unleash = unleashClient([ - { - enabled: true, - name: config.unleash.flags.mirrorWrites.name, - stale: false, - type: 'release', - project: 'default', - variants: [], - strategies: [], - impressionData: false, - }, - ]); - const context = new ContextManager({ - request: { - headers: { userid: '1', apiid: '777', premium: 'true' }, - }, - dbClient: db, - eventEmitter: null, - unleash, - }); - const savedItemService = new SavedItemDataService(context); - const pocketSaveService = new PocketSaveDataService(context); - const tagService = new TagDataService(context, savedItemService); - - const fetchRow = (itemId: string, tableName: string) => { - return db(tableName) - .select('*') - .where({ user_id: 1, item_id: itemId }) - .first(); - }; - - beforeEach(async () => { - await db('list').truncate(); - await db('list_schema_update').truncate(); - await db('item_tags').truncate(); - const listSeed = { - item_id: 1, - status: 0, - favorite: 0, - user_id: 1, - resolved_id: 1, - given_url: 'http://1', - title: 'title 1', - time_added: date, - time_updated: date, - time_read: '0000-00-00 00:00:00', - time_favorited: '0000-00-00 00:00:00', - api_id: '777', - api_id_updated: '777', - }; - const shadowSeed = { - item_id: 999, - status: 0, - favorite: 1, - user_id: 1, - resolved_id: 999, - given_url: 'http://999', - title: 'title 999', - time_added: date, - time_updated: date, - time_read: '0000-00-00 00:00:00', - time_favorited: date, - api_id: '777', - api_id_updated: '777', - }; - const baseTag = { - user_id: 1, - status: 1, - api_id: '777', - api_id_updated: '777', - time_added: date, - time_updated: date, - }; - const tagSeed = [ - { - ...baseTag, - item_id: 1, - tag: 'the first and forsaken lion', - }, - { - ...baseTag, - item_id: 1, - tag: 'the eye and seven despairs', - }, - { - ...baseTag, - item_id: 999, - tag: 'the eye and seven despairs', - }, - ]; - await db('list').insert([shadowSeed, listSeed]); - await db('list_schema_update').insert(shadowSeed); - await db('item_tags').insert(tagSeed); - }); - afterAll(async () => { - await db('list').truncate(); - await db('list_schema_update').truncate(); - await db('item_tags').truncate(); - unleash.destroy(); - }); - it('works for fields with zero-dates', async () => { - const seedItem: ItemResponse = { - itemId: '2', - resolvedId: '2', - title: 'title 2', - }; - const seedSave: SavedItemUpsertInput = { - url: 'http://2', - isFavorite: false, - timestamp: epochDate, - }; - await savedItemService.upsertSavedItem(seedItem, seedSave); - const listResult = await fetchRow('2', 'list'); - const shadowResult = await fetchRow('2', 'list_schema_update'); - expect(listResult).not.toBeNull(); - expect(listResult).toStrictEqual(shadowResult); - }); - it('Copies new rows to shadow table on create', async () => { - const seedItem: ItemResponse = { - itemId: '2', - resolvedId: '2', - title: 'title 2', - }; - const seedSave: SavedItemUpsertInput = { - url: 'http://2', - isFavorite: true, - timestamp: epochDate, - }; - await savedItemService.upsertSavedItem(seedItem, seedSave); - const listResult = await fetchRow('2', 'list'); - const shadowResult = await fetchRow('2', 'list_schema_update'); - expect(listResult).not.toBeUndefined(); - expect(shadowResult).not.toBeUndefined(); - expect(listResult).toStrictEqual(shadowResult); - }); - it('Merges changes to shadow table for rows that already exist', async () => { - await savedItemService.updateSavedItemArchiveProperty('999', true); - const listResult = await fetchRow('999', 'list'); - const shadowResult = await fetchRow('999', 'list_schema_update'); - expect(listResult).not.toBeUndefined(); - expect(shadowResult).not.toBeUndefined(); - expect(listResult.status).toEqual(1); - expect(listResult).toStrictEqual(shadowResult); - }); - it.each([ - { - property: 'favorite - savedItem', - method: () => - savedItemService.updateSavedItemFavoriteProperty('1', true, date), - }, - { - property: 'archived - savedItem', - method: () => - savedItemService.updateSavedItemArchiveProperty('1', true, date), - }, - { - property: 'deleted - savedItem', - method: () => savedItemService.deleteSavedItem('1', date), - }, - { - property: 'undeleted - savedItem', - method: () => savedItemService.updateSavedItemUnDelete('1', date), - }, - { - property: 'favorite - pocketSave', - method: () => pocketSaveService.favoriteListRow([1], date), - }, - { - property: 'archived - pocketSave', - method: () => pocketSaveService.archiveListRow([1], date), - }, - { - property: 'tags - insert', - method: () => - tagService.insertTags( - [ - { - savedItemId: '1', - name: 'the lover clad in the raiment of tears', - }, - { savedItemId: '999', name: 'the mask of winters' }, - ], - date, - ), - }, - { - property: 'tags - delete associations', - method: () => - tagService.deleteSavedItemAssociations([ - { - savedItemId: '1', - tagName: 'the first and forsaken lion', - }, - { - savedItemId: '999', - tagName: 'the eye and seven despairs', - }, - ]), - }, - { - property: 'tags - rename tag', - method: () => - tagService.updateTagByUser( - 'the eye and seven despairs', - 'the black heron', - ['1', '999'], - ), - }, - { - property: 'tags - update/replace tags', - method: () => - tagService.updateSavedItemTags([ - { - name: 'the bishop of the chalcedony thurible', - savedItemId: '1', - }, - ]), - }, - { - property: 'tags - remove all tags', - method: () => tagService.updateSavedItemRemoveTags('1'), - }, - { - property: 'tags - replace tags', - method: () => - tagService.replaceSavedItemTags([ - { - name: 'the dowager of the irreverent vulgate in unrent veils', - savedItemId: '1', - }, - ]), - }, - { - property: 'tags - bulk update', - method: () => - tagService.batchUpdateTags( - { - deletes: [], // Deleting actually doesn't trigger the save row update... maybe a bug? - creates: [{ savedItemId: '1', name: 'the silver prince' }], - }, - date, - ), - }, - // No deleted/undeleted properties for pocketSave - ])( - 'Copies new rows to shadow table on update: $property', - async ({ method }) => { - const preOperationResult = await fetchRow('1', 'list_schema_update'); - expect(preOperationResult).toBeUndefined(); - await method(); - const listResult = await fetchRow('1', 'list'); - const shadowResult = await fetchRow('1', 'list_schema_update'); - expect(listResult).not.toBeUndefined(); - expect(shadowResult).not.toBeUndefined(); - expect(listResult).toStrictEqual(shadowResult); - }, - ); - describe('with feature flag disabled', () => { - const disabledUnleash = unleashClient([ - { - enabled: false, - name: config.unleash.flags.mirrorWrites.name, - stale: false, - type: 'release', - project: 'default', - variants: [], - strategies: [], - impressionData: false, - }, - ]); - const context = new ContextManager({ - request: { - headers: { userid: '1', apiid: '777', premium: 'true' }, - }, - dbClient: db, - eventEmitter: null, - unleash: disabledUnleash, - }); - const saveServiceNoSync = new SavedItemDataService(context); - const pocketServiceNoSync = new PocketSaveDataService(context); - const tagServiceNoSync = new TagDataService(context, saveServiceNoSync); - const upsertSeed: { item: ItemResponse; save: SavedItemUpsertInput } = { - item: { - itemId: '1', - resolvedId: '1', - title: 'title 1', - }, - save: { - url: 'http://1', - isFavorite: false, - timestamp: epochDate, - }, - }; - it.each([ - { - property: 'upsert - savedItem', - method: () => - saveServiceNoSync.upsertSavedItem(upsertSeed.item, upsertSeed.save), - }, - { - property: 'favorite - savedItem', - method: () => - saveServiceNoSync.updateSavedItemFavoriteProperty('1', true, date), - }, - { - property: 'archived - savedItem', - method: () => - saveServiceNoSync.updateSavedItemArchiveProperty('1', true, date), - }, - { - property: 'deleted - savedItem', - method: () => saveServiceNoSync.deleteSavedItem('1', date), - }, - { - property: 'undeleted - savedItem', - method: () => saveServiceNoSync.updateSavedItemUnDelete('1', date), - }, - { - property: 'favorite - pocketSave', - method: () => pocketServiceNoSync.favoriteListRow([1], date), - }, - { - property: 'archived - pocketSave', - method: () => pocketServiceNoSync.archiveListRow([1], date), - }, - { - property: 'tags - insert', - method: () => - tagServiceNoSync.insertTags( - [ - { - savedItemId: '1', - name: 'the lover clad in the raiment of tears', - }, - { savedItemId: '999', name: 'the mask of winters' }, - ], - date, - ), - }, - { - property: 'tags - delete associations', - method: () => - tagServiceNoSync.deleteSavedItemAssociations([ - { - savedItemId: '1', - tagName: 'the first and forsaken lion', - }, - { - savedItemId: '999', - tagName: 'the eye and seven despairs', - }, - ]), - }, - { - property: 'tags - rename tag', - method: () => - tagServiceNoSync.updateTagByUser( - 'the eye and seven despairs', - 'the black heron', - ['1', '999'], - ), - }, - { - property: 'tags - update/replace tags', - method: () => - tagServiceNoSync.updateSavedItemTags([ - { - name: 'the bishop of the chalcedony thurible', - savedItemId: '1', - }, - ]), - }, - { - property: 'tags - remove all tags', - method: () => tagServiceNoSync.updateSavedItemRemoveTags('1'), - }, - { - property: 'tags - replace tags', - method: () => - tagServiceNoSync.replaceSavedItemTags([ - { - name: 'the dowager of the irreverent vulgate in unrent veils', - savedItemId: '1', - }, - ]), - }, - { - property: 'tags - bulk update', - method: () => - tagServiceNoSync.batchUpdateTags( - { - deletes: [], // Deleting actually doesn't trigger the save row update... maybe a bug? - creates: [{ savedItemId: '1', name: 'the silver prince' }], - }, - date, - ), - }, - // No deleted/undeleted properties for pocketSave - ])( - 'Does not sync when feature flag is disabled: $property', - async ({ method }) => { - const preOperationResult = await fetchRow('1', 'list_schema_update'); - expect(preOperationResult).toBeUndefined(); - await method(); - const listResult = await fetchRow('1', 'list'); - const shadowResult = await fetchRow('1', 'list_schema_update'); - expect(listResult).not.toBeUndefined(); - expect(shadowResult).toBeUndefined(); - }, - ); - }); -}); From 3ff2f959c49455b02b9b6e2b23bb33aa32eee9c9 Mon Sep 17 00:00:00 2001 From: Daniel Brooks Date: Thu, 29 Aug 2024 11:57:05 -0700 Subject: [PATCH 2/2] feat(waf): adding a waf to Braze for security (#702) * feat(waf): adding a waf to Braze for security * fix(waf): adding in waf config if theres a cdn * fix(waf): updating cdn waf --- .../braze-content-proxy/src/main.ts | 88 +- infrastructure/client-api/src/main.ts | 27 +- .../src/base/ApplicationLoadBalancer.ts | 32 +- .../src/pocket/PocketALBApplication.spec.ts | 34 +- .../src/pocket/PocketALBApplication.ts | 12 +- .../PocketALBApplication.spec.ts.snap | 1889 ++++++++++++++++- 6 files changed, 2055 insertions(+), 27 deletions(-) diff --git a/infrastructure/braze-content-proxy/src/main.ts b/infrastructure/braze-content-proxy/src/main.ts index 3fec80059..63ac9beab 100644 --- a/infrastructure/braze-content-proxy/src/main.ts +++ b/infrastructure/braze-content-proxy/src/main.ts @@ -6,6 +6,8 @@ import { dataAwsRegion, dataAwsKmsAlias, dataAwsSnsTopic, + wafv2WebAcl, + wafv2IpSet, } from '@cdktf/provider-aws'; import { config } from './config'; import { PocketALBApplication } from '@pocket-tools/terraform-modules'; @@ -46,6 +48,85 @@ class BrazeContentProxy extends TerraformStack { snsTopic: this.getCodeDeploySnsTopic(), region, caller, + wafAcl: this.createWafACL(), + }); + } + + /** + * Ensure that only internal IPs from the VPN or from Braze are allowed to access + * @returns Waf ACL + */ + private createWafACL() { + // Braze IPs + // We are on US-05 + // https://www.braze.com/docs/user_guide/personalization_and_dynamic_content/connected_content/making_an_api_call/#connected-content-ip-allowlisting + const brazeIPList = [ + '23.21.118.191/32', + '34.206.23.173/32', + '50.16.249.9/32', + '52.4.160.214/32', + '54.87.8.34/32', + '54.156.35.251/32', + '52.54.89.238/32', + '18.205.178.15/32', + ]; + + const allowListIPs = new wafv2IpSet.Wafv2IpSet(this, 'AllowlistIPs', { + name: `${config.name}-${config.environment}-AllowList`, + ipAddressVersion: 'IPV4', + scope: 'CLOUDFRONT', + tags: config.tags, + addresses: brazeIPList, + }); + + const ipAllowListRule = { + name: `${config.name}-${config.environment}-ipAllowList`, + priority: 1, + action: { allow: {} }, + statement: { + ip_set_reference_statement: { + arn: allowListIPs.arn, + }, + }, + visibilityConfig: { + cloudwatchMetricsEnabled: true, + metricName: `${config.name}-${config.environment}-ipAllowList`, + sampledRequestsEnabled: true, + }, + }; + + // When we are ready to block, make the default action block and remove this. + const blockAllIps = { + name: `${config.name}-${config.environment}-ipBlockAll`, + priority: 2, + action: { count: {} }, //doing a count before we do a block. + statement: { + not_statement: { + statement: { + ip_set_reference_statement: { + arn: allowListIPs.arn, + }, + }, + }, + }, + visibilityConfig: { + cloudwatchMetricsEnabled: true, + metricName: `${config.name}-${config.environment}-ipBlockAll`, + sampledRequestsEnabled: true, + }, + }; + + return new wafv2WebAcl.Wafv2WebAcl(this, `${config.name}-waf`, { + description: `Waf for ${config.name} ${config.environment} environment`, + name: `${config.name}-waf-${config.environment}`, + scope: 'CLOUDFRONT', + defaultAction: { allow: {} }, + visibilityConfig: { + cloudwatchMetricsEnabled: true, + metricName: `${config.name}-waf-${config.environment}`, + sampledRequestsEnabled: true, + }, + rule: [ipAllowListRule, blockAllIps], }); } @@ -74,14 +155,19 @@ class BrazeContentProxy extends TerraformStack { caller: dataAwsCallerIdentity.DataAwsCallerIdentity; secretsManagerKmsAlias: dataAwsKmsAlias.DataAwsKmsAlias; snsTopic: dataAwsSnsTopic.DataAwsSnsTopic; + wafAcl: wafv2WebAcl.Wafv2WebAcl; }): PocketALBApplication { - const { region, caller, secretsManagerKmsAlias, snsTopic } = dependencies; + const { region, caller, secretsManagerKmsAlias, snsTopic, wafAcl } = + dependencies; return new PocketALBApplication(this, 'application', { internal: false, prefix: config.prefix, alb6CharacterPrefix: config.shortName, cdn: true, + wafConfig: { + aclArn: wafAcl.arn, + }, domain: config.domain, containerConfigs: [ { diff --git a/infrastructure/client-api/src/main.ts b/infrastructure/client-api/src/main.ts index c3bdd66a0..a2a2c7048 100644 --- a/infrastructure/client-api/src/main.ts +++ b/infrastructure/client-api/src/main.ts @@ -8,6 +8,8 @@ import { dataAwsKmsAlias, dataAwsSnsTopic, dataAwsSubnets, + wafv2IpSet, + wafv2WebAcl, } from '@cdktf/provider-aws'; import { provider as localProvider } from '@cdktf/provider-local'; import { provider as nullProvider } from '@cdktf/provider-null'; @@ -22,11 +24,6 @@ import { App, S3Backend, TerraformStack } from 'cdktf'; import { Construct } from 'constructs'; import fs from 'fs'; -import { Wafv2IpSet } from '@cdktf/provider-aws/lib/wafv2-ip-set'; -import { - Wafv2WebAclRule, - Wafv2WebAcl, -} from '@cdktf/provider-aws/lib/wafv2-web-acl'; class ClientAPI extends TerraformStack { constructor(scope: Construct, name: string) { super(scope, name); @@ -102,15 +99,15 @@ class ClientAPI extends TerraformStack { const ipList = config.environment === 'Prod' ? ipListProd : ipListDev; - const allowListIPs = new Wafv2IpSet(this, 'AllowlistIPs', { + const allowListIPs = new wafv2IpSet.Wafv2IpSet(this, 'AllowlistIPs', { name: `${config.name}-${config.environment}-AllowList`, ipAddressVersion: 'IPV4', - scope: 'REGIONAL', + scope: 'CLOUDFRONT', tags: config.tags, addresses: ipList, }); - const ipAllowListRule = { + const ipAllowListRule = { name: `${config.name}-${config.environment}-ipAllowList`, priority: 1, action: { allow: {} }, @@ -126,8 +123,8 @@ class ClientAPI extends TerraformStack { }, }; - const regionalRateLimitRule = { - name: `${config.name}-${config.environment}-RegionalRateLimit`, + const cloudfrontRateLimitRule = { + name: `${config.name}-${config.environment}-CloudfrontRateLimit`, priority: 2, action: { count: {} }, statement: { @@ -147,22 +144,22 @@ class ClientAPI extends TerraformStack { }, visibilityConfig: { cloudwatchMetricsEnabled: true, - metricName: `${config.name}-${config.environment}-RegionalRateLimit`, + metricName: `${config.name}-${config.environment}-CloudfrontRateLimit`, sampledRequestsEnabled: true, }, }; - return new Wafv2WebAcl(this, `${config.name}-waf`, { + return new wafv2WebAcl.Wafv2WebAcl(this, `${config.name}-waf`, { description: `Waf for client-api-proxy ${config.environment} environment`, name: `${config.name}-waf-${config.environment}`, - scope: 'REGIONAL', + scope: 'CLOUDFRONT', defaultAction: { allow: {} }, visibilityConfig: { cloudwatchMetricsEnabled: true, metricName: `${config.name}-waf-${config.environment}`, sampledRequestsEnabled: true, }, - rule: [ipAllowListRule, regionalRateLimitRule], + rule: [ipAllowListRule, cloudfrontRateLimitRule], }); } @@ -192,7 +189,7 @@ class ClientAPI extends TerraformStack { secretsManagerKmsAlias: dataAwsKmsAlias.DataAwsKmsAlias; snsTopic: dataAwsSnsTopic.DataAwsSnsTopic; cache: string; - wafAcl: Wafv2WebAcl; + wafAcl: wafv2WebAcl.Wafv2WebAcl; }): PocketALBApplication { const { region, caller, secretsManagerKmsAlias, cache, snsTopic, wafAcl } = dependencies; diff --git a/packages/terraform-modules/src/base/ApplicationLoadBalancer.ts b/packages/terraform-modules/src/base/ApplicationLoadBalancer.ts index 0662b514e..424e54471 100644 --- a/packages/terraform-modules/src/base/ApplicationLoadBalancer.ts +++ b/packages/terraform-modules/src/base/ApplicationLoadBalancer.ts @@ -6,6 +6,7 @@ import { alb, dataAwsElbServiceAccount, dataAwsS3Bucket, + dataAwsEc2ManagedPrefixList, } from '@cdktf/provider-aws'; import { TerraformMetaArguments, TerraformProvider } from 'cdktf'; import { Construct } from 'constructs'; @@ -16,6 +17,7 @@ export interface ApplicationLoadBalancerProps extends TerraformMetaArguments { vpcId: string; subnetIds: string[]; internal?: boolean; + useCloudfrontManagedPrefixList?: boolean; /** * Optional config to dump alb logs to a bucket. */ @@ -53,6 +55,28 @@ export class ApplicationLoadBalancer extends Construct { ) { super(scope, name); + let ingress: securityGroup.SecurityGroupIngress = { + fromPort: 443, + toPort: 443, + protocol: 'TCP', + cidrBlocks: ['0.0.0.0/0'], + }; + + if (config.useCloudfrontManagedPrefixList) { + const prefixList = + new dataAwsEc2ManagedPrefixList.DataAwsEc2ManagedPrefixList( + this, + 'alb_cloudfront_security', + { name: 'com.amazonaws.global.cloudfront.origin-facing' }, + ); + ingress = { + fromPort: 443, + toPort: 443, + protocol: 'TCP', + prefixListIds: [prefixList.id], + }; + } + this.securityGroup = new securityGroup.SecurityGroup( this, `alb_security_group`, @@ -61,13 +85,9 @@ export class ApplicationLoadBalancer extends Construct { description: 'External security group (Managed by Terraform)', vpcId: config.vpcId, ingress: [ + ingress, { - fromPort: 443, - toPort: 443, - protocol: 'TCP', - cidrBlocks: ['0.0.0.0/0'], - }, - { + // Allow anything on Port 80 because its always a redirect to 443 which could have blocks for Cloudfront only fromPort: 80, toPort: 80, protocol: 'TCP', diff --git a/packages/terraform-modules/src/pocket/PocketALBApplication.spec.ts b/packages/terraform-modules/src/pocket/PocketALBApplication.spec.ts index 66505f6df..5a2211a1c 100644 --- a/packages/terraform-modules/src/pocket/PocketALBApplication.spec.ts +++ b/packages/terraform-modules/src/pocket/PocketALBApplication.spec.ts @@ -38,7 +38,7 @@ describe('PocketALBApplication', () => { expect(synthed).toMatchSnapshot(); }); - it('renders an external application', () => { + it('renders an external application with a cdn', () => { const synthed = Testing.synthScope((stack) => { BASE_CONFIG.internal = false; BASE_CONFIG.cdn = true; @@ -48,6 +48,38 @@ describe('PocketALBApplication', () => { expect(synthed).toMatchSnapshot(); }); + it('renders an external application with a waf', () => { + const synthed = Testing.synthScope((stack) => { + BASE_CONFIG.internal = false; + BASE_CONFIG.cdn = false; + BASE_CONFIG.wafConfig = { aclArn: 'some-arn' }; + + new PocketALBApplication(stack, 'testPocketApp', BASE_CONFIG); + }); + expect(synthed).toMatchSnapshot(); + }); + + it('renders an external application without a cdn', () => { + const synthed = Testing.synthScope((stack) => { + BASE_CONFIG.internal = false; + BASE_CONFIG.cdn = false; + + new PocketALBApplication(stack, 'testPocketApp', BASE_CONFIG); + }); + expect(synthed).toMatchSnapshot(); + }); + + it('renders an external application with a CDN and a waf', () => { + const synthed = Testing.synthScope((stack) => { + BASE_CONFIG.internal = false; + BASE_CONFIG.cdn = true; + BASE_CONFIG.wafConfig = { aclArn: 'some-arn' }; + + new PocketALBApplication(stack, 'testPocketApp', BASE_CONFIG); + }); + expect(synthed).toMatchSnapshot(); + }); + it('renders an internal application', () => { const synthed = Testing.synthScope((stack) => { BASE_CONFIG.internal = true; diff --git a/packages/terraform-modules/src/pocket/PocketALBApplication.ts b/packages/terraform-modules/src/pocket/PocketALBApplication.ts index b45dd61a1..837678058 100644 --- a/packages/terraform-modules/src/pocket/PocketALBApplication.ts +++ b/packages/terraform-modules/src/pocket/PocketALBApplication.ts @@ -258,7 +258,8 @@ export class PocketALBApplication extends Construct { this.createCDN(albRecord); } - if (config.wafConfig) { + // If we don't have a CDN add the WAF to the ALB + if (config.wafConfig && !config.cdn) { this.createWAF(alb, config.wafConfig.aclArn); } @@ -419,6 +420,8 @@ export class PocketALBApplication extends Construct { tags: this.config.tags, accessLogs: this.config.accessLogs, provider: this.config.provider, + useCloudfrontManagedPrefixList: + this.config.cdn && this.config.wafConfig !== null, }); //When the app uses a CDN we set the ALB to be direct.app-domain @@ -468,7 +471,9 @@ export class PocketALBApplication extends Construct { * @param albRecord * @private */ - private createCDN(albRecord: route53Record.Route53Record): void { + private createCDN( + albRecord: route53Record.Route53Record, + ): cloudfrontDistribution.CloudfrontDistribution { //Create the certificate for the CDN const cdnCertificate = new ApplicationCertificate(this, `cdn_certificate`, { zoneId: this.baseDNS.zoneId, @@ -487,6 +492,7 @@ export class PocketALBApplication extends Construct { aliases: [this.config.domain], priceClass: 'PriceClass_200', tags: this.config.tags, + webAclId: this.config.wafConfig?.aclArn ?? undefined, origin: [ { domainName: albRecord.fqdn, @@ -564,6 +570,8 @@ export class PocketALBApplication extends Construct { setIdentifier: '2', provider: this.config.provider, }); + + return cdn; } /** diff --git a/packages/terraform-modules/src/pocket/__snapshots__/PocketALBApplication.spec.ts.snap b/packages/terraform-modules/src/pocket/__snapshots__/PocketALBApplication.spec.ts.snap index 07314a46f..eb30f0b4b 100644 --- a/packages/terraform-modules/src/pocket/__snapshots__/PocketALBApplication.spec.ts.snap +++ b/packages/terraform-modules/src/pocket/__snapshots__/PocketALBApplication.spec.ts.snap @@ -9023,13 +9023,18 @@ exports[`PocketALBApplication renders an application with modified container def }" `; -exports[`PocketALBApplication renders an external application 1`] = ` +exports[`PocketALBApplication renders an external application with a CDN and a waf 1`] = ` "{ "data": { "aws_caller_identity": { "testPocketApp_pocket_vpc_current_identity_06D87057": { } }, + "aws_ec2_managed_prefix_list": { + "testPocketApp_application_load_balancer_alb_cloudfront_security_A2C68D99": { + "name": "com.amazonaws.global.cloudfront.origin-facing" + } + }, "aws_iam_policy_document": { "testPocketApp_ecs_service_ecs-iam_ecs-task-assume_ABB81680": { "statement": [ @@ -9408,7 +9413,8 @@ exports[`PocketALBApplication renders an external application 1`] = ` "acm_certificate_arn": "\${aws_acm_certificate.testPocketApp_cdn_certificate_F1CBB9BB.arn}", "minimum_protocol_version": "TLSv1.1_2016", "ssl_support_method": "sni-only" - } + }, + "web_acl_id": "some-arn" } }, "aws_cloudwatch_dashboard": { @@ -9616,6 +9622,1885 @@ exports[`PocketALBApplication renders an external application 1`] = ` "name": "testing.bowling.gov" } }, + "aws_security_group": { + "testPocketApp_application_load_balancer_alb_security_group_91A27046": { + "description": "External security group (Managed by Terraform)", + "egress": [ + { + "cidr_blocks": [ + "0.0.0.0/0" + ], + "description": "required", + "from_port": 0, + "ipv6_cidr_blocks": [ + ], + "prefix_list_ids": [ + ], + "protocol": "-1", + "security_groups": [ + ], + "self": null, + "to_port": 0 + } + ], + "ingress": [ + { + "cidr_blocks": null, + "description": null, + "from_port": 443, + "ipv6_cidr_blocks": null, + "prefix_list_ids": [ + "\${data.aws_ec2_managed_prefix_list.testPocketApp_application_load_balancer_alb_cloudfront_security_A2C68D99.id}" + ], + "protocol": "TCP", + "security_groups": null, + "self": null, + "to_port": 443 + }, + { + "cidr_blocks": [ + "0.0.0.0/0" + ], + "description": null, + "from_port": 80, + "ipv6_cidr_blocks": null, + "prefix_list_ids": null, + "protocol": "TCP", + "security_groups": null, + "self": null, + "to_port": 80 + } + ], + "lifecycle": { + "create_before_destroy": true + }, + "name_prefix": "testapp-HTTP/S Security Group", + "tags": { + "Name": "testapp-HTTP/S Security Group" + }, + "vpc_id": "\${data.aws_vpc.testPocketApp_pocket_vpc_C4E157E3.id}" + }, + "testPocketApp_ecs_service_ecs_security_group_9C016FA8": { + "description": "Internal ECS Security Group (Managed by Terraform)", + "egress": [ + { + "cidr_blocks": [ + "0.0.0.0/0" + ], + "description": "required", + "from_port": 0, + "ipv6_cidr_blocks": [ + ], + "prefix_list_ids": [ + ], + "protocol": "-1", + "security_groups": [ + ], + "self": null, + "to_port": 0 + } + ], + "ingress": [ + { + "cidr_blocks": [ + ], + "description": "required", + "from_port": 80, + "ipv6_cidr_blocks": [ + ], + "prefix_list_ids": [ + ], + "protocol": "TCP", + "security_groups": [ + "\${aws_security_group.testPocketApp_application_load_balancer_alb_security_group_91A27046.id}" + ], + "self": null, + "to_port": 8675309 + } + ], + "lifecycle": { + "create_before_destroy": true + }, + "name_prefix": "testapp-ECSSecurityGroup", + "vpc_id": "\${data.aws_vpc.testPocketApp_pocket_vpc_C4E157E3.id}" + } + } + } +}" +`; + +exports[`PocketALBApplication renders an external application with a cdn 1`] = ` +"{ + "data": { + "aws_caller_identity": { + "testPocketApp_pocket_vpc_current_identity_06D87057": { + } + }, + "aws_ec2_managed_prefix_list": { + "testPocketApp_application_load_balancer_alb_cloudfront_security_A2C68D99": { + "name": "com.amazonaws.global.cloudfront.origin-facing" + } + }, + "aws_iam_policy_document": { + "testPocketApp_ecs_service_ecs-iam_ecs-task-assume_ABB81680": { + "statement": [ + { + "actions": [ + "sts:AssumeRole" + ], + "effect": "Allow", + "principals": [ + { + "identifiers": [ + "ecs-tasks.amazonaws.com" + ], + "type": "Service" + } + ] + } + ], + "version": "2012-10-17" + } + }, + "aws_kms_alias": { + "testPocketApp_pocket_vpc_secrets_manager_key_9FD2BC93": { + "name": "alias/aws/secretsmanager" + } + }, + "aws_region": { + "testPocketApp_pocket_vpc_current_region_8ED435E7": { + } + }, + "aws_route53_zone": { + "testPocketApp_base_dns_main_hosted_zone_9442EEB0": { + "name": "bowling.gov" + } + }, + "aws_security_groups": { + "testPocketApp_pocket_vpc_default_security_groups_40B4FC48": { + "filter": [ + { + "name": "group-name", + "values": [ + "default" + ] + }, + { + "name": "vpc-id", + "values": [ + "\${data.aws_vpc.testPocketApp_pocket_vpc_C4E157E3.id}" + ] + } + ] + }, + "testPocketApp_pocket_vpc_internal_security_groups_12F7F666": { + "filter": [ + { + "name": "group-name", + "values": [ + "pocket-vpc-internal" + ] + }, + { + "name": "vpc-id", + "values": [ + "\${data.aws_vpc.testPocketApp_pocket_vpc_C4E157E3.id}" + ] + } + ] + } + }, + "aws_ssm_parameter": { + "testPocketApp_pocket_vpc_private_subnets_9D449563": { + "name": "/Shared/PrivateSubnets" + }, + "testPocketApp_pocket_vpc_public_subnets_B0B3A6AB": { + "name": "/Shared/PublicSubnets" + }, + "testPocketApp_pocket_vpc_vpc_ssm_param_235525D4": { + "name": "/Shared/Vpc" + } + }, + "aws_subnets": { + "testPocketApp_pocket_vpc_private_subnet_ids_EB1E3A65": { + "filter": [ + { + "name": "subnet-id", + "values": "\${split(\\",\\", data.aws_ssm_parameter.testPocketApp_pocket_vpc_private_subnets_9D449563.value)}" + }, + { + "name": "vpc-id", + "values": [ + "\${data.aws_vpc.testPocketApp_pocket_vpc_C4E157E3.id}" + ] + } + ] + }, + "testPocketApp_pocket_vpc_public_subnet_ids_01F0B902": { + "filter": [ + { + "name": "subnet-id", + "values": "\${split(\\",\\", data.aws_ssm_parameter.testPocketApp_pocket_vpc_public_subnets_B0B3A6AB.value)}" + }, + { + "name": "vpc-id", + "values": [ + "\${data.aws_vpc.testPocketApp_pocket_vpc_C4E157E3.id}" + ] + } + ] + } + }, + "aws_vpc": { + "testPocketApp_pocket_vpc_C4E157E3": { + "filter": [ + { + "name": "vpc-id", + "values": [ + "\${data.aws_ssm_parameter.testPocketApp_pocket_vpc_vpc_ssm_param_235525D4.value}" + ] + } + ] + } + } + }, + "output": { + "ecs-application-url": { + "description": "ECS Application URL", + "value": "testing.bowling.gov" + } + }, + "resource": { + "aws_acm_certificate": { + "testPocketApp_alb_certificate_417C14FF": { + "domain_name": "direct.testing.bowling.gov", + "lifecycle": { + "create_before_destroy": true + }, + "validation_method": "DNS" + }, + "testPocketApp_cdn_certificate_F1CBB9BB": { + "domain_name": "testing.bowling.gov", + "lifecycle": { + "create_before_destroy": true + }, + "validation_method": "DNS" + } + }, + "aws_acm_certificate_validation": { + "testPocketApp_alb_certificate_certificate_validation_7D899C6A": { + "certificate_arn": "\${aws_acm_certificate.testPocketApp_alb_certificate_417C14FF.arn}", + "depends_on": [ + "aws_route53_record.testPocketApp_alb_certificate_certificate_record_3C49201F", + "aws_acm_certificate.testPocketApp_alb_certificate_417C14FF" + ], + "validation_record_fqdns": [ + "\${aws_route53_record.testPocketApp_alb_certificate_certificate_record_3C49201F.fqdn}" + ] + }, + "testPocketApp_cdn_certificate_certificate_validation_BA2932DC": { + "certificate_arn": "\${aws_acm_certificate.testPocketApp_cdn_certificate_F1CBB9BB.arn}", + "depends_on": [ + "aws_route53_record.testPocketApp_cdn_certificate_certificate_record_63BAB662", + "aws_acm_certificate.testPocketApp_cdn_certificate_F1CBB9BB" + ], + "validation_record_fqdns": [ + "\${aws_route53_record.testPocketApp_cdn_certificate_certificate_record_63BAB662.fqdn}" + ] + } + }, + "aws_alb": { + "testPocketApp_application_load_balancer_alb_D538DEEE": { + "internal": false, + "name_prefix": "TSTAPP", + "security_groups": [ + "\${aws_security_group.testPocketApp_application_load_balancer_alb_security_group_91A27046.id}" + ], + "subnets": "\${data.aws_subnets.testPocketApp_pocket_vpc_public_subnet_ids_01F0B902.ids}" + } + }, + "aws_alb_listener": { + "testPocketApp_listener_http_A9E227C2": { + "default_action": [ + { + "redirect": { + "port": "443", + "protocol": "HTTPS", + "status_code": "HTTP_301" + }, + "type": "redirect" + } + ], + "load_balancer_arn": "\${aws_alb.testPocketApp_application_load_balancer_alb_D538DEEE.arn}", + "port": 80, + "protocol": "HTTP" + }, + "testPocketApp_listener_https_7F49DE83": { + "certificate_arn": "\${aws_acm_certificate.testPocketApp_alb_certificate_417C14FF.arn}", + "default_action": [ + { + "fixed_response": { + "content_type": "text/plain", + "message_body": "", + "status_code": "503" + }, + "type": "fixed-response" + } + ], + "load_balancer_arn": "\${aws_alb.testPocketApp_application_load_balancer_alb_D538DEEE.arn}", + "port": 443, + "protocol": "HTTPS", + "ssl_policy": "ELBSecurityPolicy-TLS-1-1-2017-01" + } + }, + "aws_alb_listener_rule": { + "testPocketApp_ecs_service_listener_rule_C03BB04D": { + "action": [ + { + "target_group_arn": "\${aws_alb_target_group.testPocketApp_ecs_service_blue_target_group_ecs_target_group_A4B85885.arn}", + "type": "forward" + } + ], + "condition": [ + { + "path_pattern": { + "values": [ + "*" + ] + } + } + ], + "lifecycle": { + "ignore_changes": [ + "action" + ] + }, + "listener_arn": "\${aws_alb_listener.testPocketApp_listener_https_7F49DE83.arn}", + "priority": 1 + } + }, + "aws_alb_target_group": { + "testPocketApp_ecs_service_blue_target_group_ecs_target_group_A4B85885": { + "deregistration_delay": "120", + "health_check": { + "healthy_threshold": 5, + "interval": 15, + "path": "/test", + "protocol": "HTTP", + "unhealthy_threshold": 3 + }, + "name_prefix": "TSTAPP", + "port": 80, + "protocol": "HTTP", + "tags": { + "type": "blue" + }, + "target_type": "ip", + "vpc_id": "\${data.aws_vpc.testPocketApp_pocket_vpc_C4E157E3.id}" + } + }, + "aws_appautoscaling_policy": { + "testPocketApp_autoscaling_scale_in_policy_A163A235": { + "depends_on": [ + "aws_appautoscaling_target.testPocketApp_autoscaling_autoscaling_target_505E4EA1" + ], + "name": "testapp-ScaleInPolicy", + "policy_type": "StepScaling", + "resource_id": "\${aws_appautoscaling_target.testPocketApp_autoscaling_autoscaling_target_505E4EA1.resource_id}", + "scalable_dimension": "\${aws_appautoscaling_target.testPocketApp_autoscaling_autoscaling_target_505E4EA1.scalable_dimension}", + "service_namespace": "\${aws_appautoscaling_target.testPocketApp_autoscaling_autoscaling_target_505E4EA1.service_namespace}", + "step_scaling_policy_configuration": { + "adjustment_type": "ChangeInCapacity", + "cooldown": 60, + "metric_aggregation_type": "Average", + "step_adjustment": [ + { + "metric_interval_upper_bound": "0", + "scaling_adjustment": -1 + } + ] + }, + "target_tracking_scaling_policy_configuration": null + }, + "testPocketApp_autoscaling_scale_out_policy_075BFAC4": { + "depends_on": [ + "aws_appautoscaling_target.testPocketApp_autoscaling_autoscaling_target_505E4EA1" + ], + "name": "testapp-ScaleOutPolicy", + "policy_type": "StepScaling", + "resource_id": "\${aws_appautoscaling_target.testPocketApp_autoscaling_autoscaling_target_505E4EA1.resource_id}", + "scalable_dimension": "\${aws_appautoscaling_target.testPocketApp_autoscaling_autoscaling_target_505E4EA1.scalable_dimension}", + "service_namespace": "\${aws_appautoscaling_target.testPocketApp_autoscaling_autoscaling_target_505E4EA1.service_namespace}", + "step_scaling_policy_configuration": { + "adjustment_type": "ChangeInCapacity", + "cooldown": 60, + "metric_aggregation_type": "Average", + "step_adjustment": [ + { + "metric_interval_lower_bound": "0", + "scaling_adjustment": 2 + } + ] + }, + "target_tracking_scaling_policy_configuration": null + } + }, + "aws_appautoscaling_target": { + "testPocketApp_autoscaling_autoscaling_target_505E4EA1": { + "max_capacity": 2, + "min_capacity": 1, + "resource_id": "service/\${aws_ecs_cluster.testPocketApp_ecs_cluster_C3960066.name}/\${aws_ecs_service.testPocketApp_ecs_service_ecs-service_182DEA4C.name}", + "scalable_dimension": "ecs:service:DesiredCount", + "service_namespace": "ecs" + } + }, + "aws_cloudfront_distribution": { + "testPocketApp_cloudfront_distribution_FD7F01BF": { + "aliases": [ + "testing.bowling.gov" + ], + "comment": "CDN for direct.testing.bowling.gov", + "default_cache_behavior": { + "allowed_methods": [ + "GET", + "HEAD", + "OPTIONS", + "PUT", + "POST", + "PATCH", + "DELETE" + ], + "cached_methods": [ + "GET", + "HEAD", + "OPTIONS" + ], + "compress": true, + "default_ttl": 0, + "forwarded_values": { + "cookies": { + "forward": "none" + }, + "headers": [ + "Accept", + "Origin", + "Authorization" + ], + "query_string": true + }, + "max_ttl": 31536000, + "min_ttl": 0, + "target_origin_id": "Alb", + "viewer_protocol_policy": "redirect-to-https" + }, + "enabled": true, + "origin": [ + { + "custom_origin_config": { + "http_port": 80, + "https_port": 443, + "origin_protocol_policy": "https-only", + "origin_ssl_protocols": [ + "TLSv1.1", + "TLSv1.2" + ] + }, + "domain_name": "\${aws_route53_record.testPocketApp_alb_record_7B56ED33.fqdn}", + "origin_id": "Alb" + } + ], + "price_class": "PriceClass_200", + "restrictions": { + "geo_restriction": { + "restriction_type": "none" + } + }, + "viewer_certificate": { + "acm_certificate_arn": "\${aws_acm_certificate.testPocketApp_cdn_certificate_F1CBB9BB.arn}", + "minimum_protocol_version": "TLSv1.1_2016", + "ssl_support_method": "sni-only" + } + } + }, + "aws_cloudwatch_dashboard": { + "testPocketApp_cloudwatch-dashboard_26E9F70C": { + "dashboard_body": "{\\"widgets\\":[{\\"type\\":\\"metric\\",\\"x\\":0,\\"y\\":0,\\"width\\":12,\\"height\\":6,\\"properties\\":{\\"metrics\\":[[\\"AWS/ApplicationELB\\",\\"HTTPCode_Target_4XX_Count\\",\\"LoadBalancer\\",\\"\${aws_alb.testPocketApp_application_load_balancer_alb_D538DEEE.arn_suffix}\\",{\\"yAxis\\":\\"left\\",\\"color\\":\\"#ff7f0e\\"}],[\\".\\",\\"RequestCount\\",\\".\\",\\".\\",{\\"yAxis\\":\\"right\\",\\"color\\":\\"#1f77b4\\"}],[\\".\\",\\"HTTPCode_Target_5XX_Count\\",\\".\\",\\".\\",{\\"color\\":\\"#d62728\\"}],[\\".\\",\\"HTTPCode_Target_2XX_Count\\",\\".\\",\\".\\",{\\"yAxis\\":\\"right\\",\\"color\\":\\"#2ca02c\\"}]],\\"view\\":\\"timeSeries\\",\\"stacked\\":false,\\"region\\":\\"us-east-1\\",\\"period\\":60,\\"stat\\":\\"Sum\\",\\"title\\":\\"Target Requests\\"}},{\\"type\\":\\"metric\\",\\"x\\":12,\\"y\\":0,\\"width\\":12,\\"height\\":6,\\"properties\\":{\\"metrics\\":[[\\"AWS/ApplicationELB\\",\\"HTTPCode_ELB_4XX_Count\\",\\"LoadBalancer\\",\\"\${aws_alb.testPocketApp_application_load_balancer_alb_D538DEEE.arn_suffix}\\",{\\"yAxis\\":\\"left\\",\\"color\\":\\"#ff7f0e\\"}],[\\".\\",\\"RequestCount\\",\\".\\",\\".\\",{\\"yAxis\\":\\"right\\",\\"color\\":\\"#1f77b4\\"}],[\\".\\",\\"HTTPCode_ELB_5XX_Count\\",\\".\\",\\".\\",{\\"color\\":\\"#d62728\\"}]],\\"view\\":\\"timeSeries\\",\\"stacked\\":false,\\"region\\":\\"us-east-1\\",\\"period\\":60,\\"stat\\":\\"Sum\\",\\"title\\":\\"ALB Requests\\"}},{\\"type\\":\\"metric\\",\\"x\\":12,\\"y\\":6,\\"width\\":12,\\"height\\":6,\\"properties\\":{\\"metrics\\":[[\\"AWS/ApplicationELB\\",\\"TargetResponseTime\\",\\"LoadBalancer\\",\\"\${aws_alb.testPocketApp_application_load_balancer_alb_D538DEEE.arn_suffix}\\",{\\"label\\":\\"Average\\",\\"color\\":\\"#aec7e8\\"}],[\\"...\\",{\\"stat\\":\\"p95\\",\\"label\\":\\"p95\\",\\"color\\":\\"#ffbb78\\"}],[\\"...\\",{\\"stat\\":\\"p99\\",\\"label\\":\\"p99\\",\\"color\\":\\"#98df8a\\"}]],\\"view\\":\\"timeSeries\\",\\"stacked\\":false,\\"region\\":\\"us-east-1\\",\\"stat\\":\\"Average\\",\\"period\\":60}},{\\"type\\":\\"metric\\",\\"x\\":0,\\"y\\":6,\\"width\\":12,\\"height\\":6,\\"properties\\":{\\"metrics\\":[[\\"ECS/ContainerInsights\\",\\"RunningTaskCount\\",\\"ServiceName\\",\\"\${aws_ecs_service.testPocketApp_ecs_service_ecs-service_182DEA4C.name}\\",\\"ClusterName\\",\\"\${aws_ecs_cluster.testPocketApp_ecs_cluster_C3960066.name}\\",{\\"yAxis\\":\\"right\\",\\"color\\":\\"#c49c94\\"}],[\\"AWS/ECS\\",\\"CPUUtilization\\",\\".\\",\\".\\",\\".\\",\\".\\",{\\"color\\":\\"#f7b6d2\\"}],[\\".\\",\\"MemoryUtilization\\",\\".\\",\\".\\",\\".\\",\\".\\",{\\"color\\":\\"#c7c7c7\\"}]],\\"view\\":\\"timeSeries\\",\\"stacked\\":false,\\"region\\":\\"us-east-1\\",\\"stat\\":\\"Average\\",\\"period\\":60,\\"annotations\\":{\\"horizontal\\":[{\\"color\\":\\"#e377c2\\",\\"label\\":\\"CPU scale out\\",\\"value\\":45},{\\"color\\":\\"#c5b0d5\\",\\"label\\":\\"CPU scale in\\",\\"value\\":30}]},\\"title\\":\\"Service Load\\"}}]}", + "dashboard_name": "testapp", + "lifecycle": { + "ignore_changes": [ + "dashboard_body" + ] + } + } + }, + "aws_cloudwatch_metric_alarm": { + "testPocketApp_autoscaling_scale_in_alarm_69B5F00D": { + "alarm_actions": [ + "\${aws_appautoscaling_policy.testPocketApp_autoscaling_scale_in_policy_A163A235.arn}" + ], + "alarm_description": "Alarm to reduce capacity if container CPU is low", + "alarm_name": "testapp Service Low CPU", + "comparison_operator": "LessThanThreshold", + "dimensions": { + "ClusterName": "\${aws_ecs_cluster.testPocketApp_ecs_cluster_C3960066.name}", + "ServiceName": "\${aws_ecs_service.testPocketApp_ecs_service_ecs-service_182DEA4C.name}" + }, + "evaluation_periods": 2, + "metric_name": "CPUUtilization", + "namespace": "AWS/ECS", + "period": 60, + "statistic": "Average", + "threshold": 30, + "treat_missing_data": "notBreaching" + }, + "testPocketApp_autoscaling_scale_out_alarm_4313FBE9": { + "alarm_actions": [ + "\${aws_appautoscaling_policy.testPocketApp_autoscaling_scale_out_policy_075BFAC4.arn}" + ], + "alarm_description": "Alarm to add capacity if container CPU is high", + "alarm_name": "testapp Service High CPU", + "comparison_operator": "GreaterThanThreshold", + "dimensions": { + "ClusterName": "\${aws_ecs_cluster.testPocketApp_ecs_cluster_C3960066.name}", + "ServiceName": "\${aws_ecs_service.testPocketApp_ecs_service_ecs-service_182DEA4C.name}" + }, + "evaluation_periods": 2, + "metric_name": "CPUUtilization", + "namespace": "AWS/ECS", + "period": 60, + "statistic": "Average", + "threshold": 45, + "treat_missing_data": "notBreaching" + } + }, + "aws_ecs_cluster": { + "testPocketApp_ecs_cluster_C3960066": { + "name": "testapp", + "setting": [ + { + "name": "containerInsights", + "value": "enabled" + } + ] + } + }, + "aws_ecs_service": { + "testPocketApp_ecs_service_ecs-service_182DEA4C": { + "cluster": "\${aws_ecs_cluster.testPocketApp_ecs_cluster_C3960066.arn}", + "depends_on": [ + "aws_alb_target_group.testPocketApp_ecs_service_blue_target_group_ecs_target_group_A4B85885", + "aws_alb_listener_rule.testPocketApp_ecs_service_listener_rule_C03BB04D" + ], + "deployment_controller": { + "type": "ECS" + }, + "deployment_maximum_percent": 200, + "deployment_minimum_healthy_percent": 100, + "desired_count": 2, + "launch_type": "FARGATE", + "lifecycle": { + "ignore_changes": [ + "desired_count", + "load_balancer" + ] + }, + "load_balancer": [ + { + "container_name": "main_container", + "container_port": 8675309, + "target_group_arn": "\${aws_alb_target_group.testPocketApp_ecs_service_blue_target_group_ecs_target_group_A4B85885.arn}" + } + ], + "name": "testapp", + "network_configuration": { + "security_groups": [ + "\${aws_security_group.testPocketApp_ecs_service_ecs_security_group_9C016FA8.id}" + ], + "subnets": "\${data.aws_subnets.testPocketApp_pocket_vpc_private_subnet_ids_EB1E3A65.ids}" + }, + "propagate_tags": "SERVICE", + "task_definition": "\${aws_ecs_task_definition.testPocketApp_ecs_service_ecs-task_A7E74E45.arn}" + } + }, + "aws_ecs_task_definition": { + "testPocketApp_ecs_service_ecs-task_A7E74E45": { + "container_definitions": "[]", + "cpu": "512", + "depends_on": [ + ], + "execution_role_arn": "\${aws_iam_role.testPocketApp_ecs_service_ecs-iam_ecs-execution-role_EB461A0C.arn}", + "family": "testapp", + "memory": "2048", + "network_mode": "awsvpc", + "requires_compatibilities": [ + "FARGATE" + ], + "skip_destroy": true, + "task_role_arn": "\${aws_iam_role.testPocketApp_ecs_service_ecs-iam_ecs-task-role_7DB93963.arn}", + "volume": [ + ] + } + }, + "aws_iam_role": { + "testPocketApp_ecs_service_ecs-iam_ecs-execution-role_EB461A0C": { + "assume_role_policy": "\${data.aws_iam_policy_document.testPocketApp_ecs_service_ecs-iam_ecs-task-assume_ABB81680.json}", + "name": "testapp-TaskExecutionRole" + }, + "testPocketApp_ecs_service_ecs-iam_ecs-task-role_7DB93963": { + "assume_role_policy": "\${data.aws_iam_policy_document.testPocketApp_ecs_service_ecs-iam_ecs-task-assume_ABB81680.json}", + "name": "testapp-TaskRole" + } + }, + "aws_route53_record": { + "testPocketApp_alb_certificate_certificate_record_3C49201F": { + "depends_on": [ + "aws_acm_certificate.testPocketApp_alb_certificate_417C14FF" + ], + "name": "\${tolist(aws_acm_certificate.testPocketApp_alb_certificate_417C14FF.domain_validation_options).0.resource_record_name}", + "records": [ + "\${tolist(aws_acm_certificate.testPocketApp_alb_certificate_417C14FF.domain_validation_options).0.resource_record_value}" + ], + "ttl": 60, + "type": "\${tolist(aws_acm_certificate.testPocketApp_alb_certificate_417C14FF.domain_validation_options).0.resource_record_type}", + "zone_id": "\${aws_route53_zone.testPocketApp_base_dns_subhosted_zone_1E1E3243.zone_id}" + }, + "testPocketApp_alb_record_7B56ED33": { + "alias": { + "evaluate_target_health": true, + "name": "\${aws_alb.testPocketApp_application_load_balancer_alb_D538DEEE.dns_name}", + "zone_id": "\${aws_alb.testPocketApp_application_load_balancer_alb_D538DEEE.zone_id}" + }, + "lifecycle": { + "ignore_changes": [ + "weighted_routing_policy[0].weight" + ] + }, + "name": "direct.testing.bowling.gov", + "set_identifier": "1", + "type": "A", + "weighted_routing_policy": { + "weight": 1 + }, + "zone_id": "\${aws_route53_zone.testPocketApp_base_dns_subhosted_zone_1E1E3243.zone_id}" + }, + "testPocketApp_base_dns_subhosted_zone_ns_497FFB83": { + "name": "testing.bowling.gov", + "records": "\${aws_route53_zone.testPocketApp_base_dns_subhosted_zone_1E1E3243.name_servers}", + "ttl": 86400, + "type": "NS", + "zone_id": "\${data.aws_route53_zone.testPocketApp_base_dns_main_hosted_zone_9442EEB0.zone_id}" + }, + "testPocketApp_cdn_certificate_certificate_record_63BAB662": { + "depends_on": [ + "aws_acm_certificate.testPocketApp_cdn_certificate_F1CBB9BB" + ], + "name": "\${tolist(aws_acm_certificate.testPocketApp_cdn_certificate_F1CBB9BB.domain_validation_options).0.resource_record_name}", + "records": [ + "\${tolist(aws_acm_certificate.testPocketApp_cdn_certificate_F1CBB9BB.domain_validation_options).0.resource_record_value}" + ], + "ttl": 60, + "type": "\${tolist(aws_acm_certificate.testPocketApp_cdn_certificate_F1CBB9BB.domain_validation_options).0.resource_record_type}", + "zone_id": "\${aws_route53_zone.testPocketApp_base_dns_subhosted_zone_1E1E3243.zone_id}" + }, + "testPocketApp_cdn_record_DD10AA15": { + "alias": { + "evaluate_target_health": true, + "name": "\${aws_cloudfront_distribution.testPocketApp_cloudfront_distribution_FD7F01BF.domain_name}", + "zone_id": "\${aws_cloudfront_distribution.testPocketApp_cloudfront_distribution_FD7F01BF.hosted_zone_id}" + }, + "lifecycle": { + "ignore_changes": [ + "weighted_routing_policy[0].weight" + ] + }, + "name": "testing.bowling.gov", + "set_identifier": "2", + "type": "A", + "weighted_routing_policy": { + "weight": 1 + }, + "zone_id": "\${aws_route53_zone.testPocketApp_base_dns_subhosted_zone_1E1E3243.zone_id}" + } + }, + "aws_route53_zone": { + "testPocketApp_base_dns_subhosted_zone_1E1E3243": { + "name": "testing.bowling.gov" + } + }, + "aws_security_group": { + "testPocketApp_application_load_balancer_alb_security_group_91A27046": { + "description": "External security group (Managed by Terraform)", + "egress": [ + { + "cidr_blocks": [ + "0.0.0.0/0" + ], + "description": "required", + "from_port": 0, + "ipv6_cidr_blocks": [ + ], + "prefix_list_ids": [ + ], + "protocol": "-1", + "security_groups": [ + ], + "self": null, + "to_port": 0 + } + ], + "ingress": [ + { + "cidr_blocks": null, + "description": null, + "from_port": 443, + "ipv6_cidr_blocks": null, + "prefix_list_ids": [ + "\${data.aws_ec2_managed_prefix_list.testPocketApp_application_load_balancer_alb_cloudfront_security_A2C68D99.id}" + ], + "protocol": "TCP", + "security_groups": null, + "self": null, + "to_port": 443 + }, + { + "cidr_blocks": [ + "0.0.0.0/0" + ], + "description": null, + "from_port": 80, + "ipv6_cidr_blocks": null, + "prefix_list_ids": null, + "protocol": "TCP", + "security_groups": null, + "self": null, + "to_port": 80 + } + ], + "lifecycle": { + "create_before_destroy": true + }, + "name_prefix": "testapp-HTTP/S Security Group", + "tags": { + "Name": "testapp-HTTP/S Security Group" + }, + "vpc_id": "\${data.aws_vpc.testPocketApp_pocket_vpc_C4E157E3.id}" + }, + "testPocketApp_ecs_service_ecs_security_group_9C016FA8": { + "description": "Internal ECS Security Group (Managed by Terraform)", + "egress": [ + { + "cidr_blocks": [ + "0.0.0.0/0" + ], + "description": "required", + "from_port": 0, + "ipv6_cidr_blocks": [ + ], + "prefix_list_ids": [ + ], + "protocol": "-1", + "security_groups": [ + ], + "self": null, + "to_port": 0 + } + ], + "ingress": [ + { + "cidr_blocks": [ + ], + "description": "required", + "from_port": 80, + "ipv6_cidr_blocks": [ + ], + "prefix_list_ids": [ + ], + "protocol": "TCP", + "security_groups": [ + "\${aws_security_group.testPocketApp_application_load_balancer_alb_security_group_91A27046.id}" + ], + "self": null, + "to_port": 8675309 + } + ], + "lifecycle": { + "create_before_destroy": true + }, + "name_prefix": "testapp-ECSSecurityGroup", + "vpc_id": "\${data.aws_vpc.testPocketApp_pocket_vpc_C4E157E3.id}" + } + } + } +}" +`; + +exports[`PocketALBApplication renders an external application with a waf 1`] = ` +"{ + "data": { + "aws_caller_identity": { + "testPocketApp_pocket_vpc_current_identity_06D87057": { + } + }, + "aws_iam_policy_document": { + "testPocketApp_ecs_service_ecs-iam_ecs-task-assume_ABB81680": { + "statement": [ + { + "actions": [ + "sts:AssumeRole" + ], + "effect": "Allow", + "principals": [ + { + "identifiers": [ + "ecs-tasks.amazonaws.com" + ], + "type": "Service" + } + ] + } + ], + "version": "2012-10-17" + } + }, + "aws_kms_alias": { + "testPocketApp_pocket_vpc_secrets_manager_key_9FD2BC93": { + "name": "alias/aws/secretsmanager" + } + }, + "aws_region": { + "testPocketApp_pocket_vpc_current_region_8ED435E7": { + } + }, + "aws_route53_zone": { + "testPocketApp_base_dns_main_hosted_zone_9442EEB0": { + "name": "bowling.gov" + } + }, + "aws_security_groups": { + "testPocketApp_pocket_vpc_default_security_groups_40B4FC48": { + "filter": [ + { + "name": "group-name", + "values": [ + "default" + ] + }, + { + "name": "vpc-id", + "values": [ + "\${data.aws_vpc.testPocketApp_pocket_vpc_C4E157E3.id}" + ] + } + ] + }, + "testPocketApp_pocket_vpc_internal_security_groups_12F7F666": { + "filter": [ + { + "name": "group-name", + "values": [ + "pocket-vpc-internal" + ] + }, + { + "name": "vpc-id", + "values": [ + "\${data.aws_vpc.testPocketApp_pocket_vpc_C4E157E3.id}" + ] + } + ] + } + }, + "aws_ssm_parameter": { + "testPocketApp_pocket_vpc_private_subnets_9D449563": { + "name": "/Shared/PrivateSubnets" + }, + "testPocketApp_pocket_vpc_public_subnets_B0B3A6AB": { + "name": "/Shared/PublicSubnets" + }, + "testPocketApp_pocket_vpc_vpc_ssm_param_235525D4": { + "name": "/Shared/Vpc" + } + }, + "aws_subnets": { + "testPocketApp_pocket_vpc_private_subnet_ids_EB1E3A65": { + "filter": [ + { + "name": "subnet-id", + "values": "\${split(\\",\\", data.aws_ssm_parameter.testPocketApp_pocket_vpc_private_subnets_9D449563.value)}" + }, + { + "name": "vpc-id", + "values": [ + "\${data.aws_vpc.testPocketApp_pocket_vpc_C4E157E3.id}" + ] + } + ] + }, + "testPocketApp_pocket_vpc_public_subnet_ids_01F0B902": { + "filter": [ + { + "name": "subnet-id", + "values": "\${split(\\",\\", data.aws_ssm_parameter.testPocketApp_pocket_vpc_public_subnets_B0B3A6AB.value)}" + }, + { + "name": "vpc-id", + "values": [ + "\${data.aws_vpc.testPocketApp_pocket_vpc_C4E157E3.id}" + ] + } + ] + } + }, + "aws_vpc": { + "testPocketApp_pocket_vpc_C4E157E3": { + "filter": [ + { + "name": "vpc-id", + "values": [ + "\${data.aws_ssm_parameter.testPocketApp_pocket_vpc_vpc_ssm_param_235525D4.value}" + ] + } + ] + } + } + }, + "output": { + "ecs-application-url": { + "description": "ECS Application URL", + "value": "testing.bowling.gov" + } + }, + "resource": { + "aws_acm_certificate": { + "testPocketApp_alb_certificate_417C14FF": { + "domain_name": "testing.bowling.gov", + "lifecycle": { + "create_before_destroy": true + }, + "validation_method": "DNS" + } + }, + "aws_acm_certificate_validation": { + "testPocketApp_alb_certificate_certificate_validation_7D899C6A": { + "certificate_arn": "\${aws_acm_certificate.testPocketApp_alb_certificate_417C14FF.arn}", + "depends_on": [ + "aws_route53_record.testPocketApp_alb_certificate_certificate_record_3C49201F", + "aws_acm_certificate.testPocketApp_alb_certificate_417C14FF" + ], + "validation_record_fqdns": [ + "\${aws_route53_record.testPocketApp_alb_certificate_certificate_record_3C49201F.fqdn}" + ] + } + }, + "aws_alb": { + "testPocketApp_application_load_balancer_alb_D538DEEE": { + "internal": false, + "name_prefix": "TSTAPP", + "security_groups": [ + "\${aws_security_group.testPocketApp_application_load_balancer_alb_security_group_91A27046.id}" + ], + "subnets": "\${data.aws_subnets.testPocketApp_pocket_vpc_public_subnet_ids_01F0B902.ids}" + } + }, + "aws_alb_listener": { + "testPocketApp_listener_http_A9E227C2": { + "default_action": [ + { + "redirect": { + "port": "443", + "protocol": "HTTPS", + "status_code": "HTTP_301" + }, + "type": "redirect" + } + ], + "load_balancer_arn": "\${aws_alb.testPocketApp_application_load_balancer_alb_D538DEEE.arn}", + "port": 80, + "protocol": "HTTP" + }, + "testPocketApp_listener_https_7F49DE83": { + "certificate_arn": "\${aws_acm_certificate.testPocketApp_alb_certificate_417C14FF.arn}", + "default_action": [ + { + "fixed_response": { + "content_type": "text/plain", + "message_body": "", + "status_code": "503" + }, + "type": "fixed-response" + } + ], + "load_balancer_arn": "\${aws_alb.testPocketApp_application_load_balancer_alb_D538DEEE.arn}", + "port": 443, + "protocol": "HTTPS", + "ssl_policy": "ELBSecurityPolicy-TLS-1-1-2017-01" + } + }, + "aws_alb_listener_rule": { + "testPocketApp_ecs_service_listener_rule_C03BB04D": { + "action": [ + { + "target_group_arn": "\${aws_alb_target_group.testPocketApp_ecs_service_blue_target_group_ecs_target_group_A4B85885.arn}", + "type": "forward" + } + ], + "condition": [ + { + "path_pattern": { + "values": [ + "*" + ] + } + } + ], + "lifecycle": { + "ignore_changes": [ + "action" + ] + }, + "listener_arn": "\${aws_alb_listener.testPocketApp_listener_https_7F49DE83.arn}", + "priority": 1 + } + }, + "aws_alb_target_group": { + "testPocketApp_ecs_service_blue_target_group_ecs_target_group_A4B85885": { + "deregistration_delay": "120", + "health_check": { + "healthy_threshold": 5, + "interval": 15, + "path": "/test", + "protocol": "HTTP", + "unhealthy_threshold": 3 + }, + "name_prefix": "TSTAPP", + "port": 80, + "protocol": "HTTP", + "tags": { + "type": "blue" + }, + "target_type": "ip", + "vpc_id": "\${data.aws_vpc.testPocketApp_pocket_vpc_C4E157E3.id}" + } + }, + "aws_appautoscaling_policy": { + "testPocketApp_autoscaling_scale_in_policy_A163A235": { + "depends_on": [ + "aws_appautoscaling_target.testPocketApp_autoscaling_autoscaling_target_505E4EA1" + ], + "name": "testapp-ScaleInPolicy", + "policy_type": "StepScaling", + "resource_id": "\${aws_appautoscaling_target.testPocketApp_autoscaling_autoscaling_target_505E4EA1.resource_id}", + "scalable_dimension": "\${aws_appautoscaling_target.testPocketApp_autoscaling_autoscaling_target_505E4EA1.scalable_dimension}", + "service_namespace": "\${aws_appautoscaling_target.testPocketApp_autoscaling_autoscaling_target_505E4EA1.service_namespace}", + "step_scaling_policy_configuration": { + "adjustment_type": "ChangeInCapacity", + "cooldown": 60, + "metric_aggregation_type": "Average", + "step_adjustment": [ + { + "metric_interval_upper_bound": "0", + "scaling_adjustment": -1 + } + ] + }, + "target_tracking_scaling_policy_configuration": null + }, + "testPocketApp_autoscaling_scale_out_policy_075BFAC4": { + "depends_on": [ + "aws_appautoscaling_target.testPocketApp_autoscaling_autoscaling_target_505E4EA1" + ], + "name": "testapp-ScaleOutPolicy", + "policy_type": "StepScaling", + "resource_id": "\${aws_appautoscaling_target.testPocketApp_autoscaling_autoscaling_target_505E4EA1.resource_id}", + "scalable_dimension": "\${aws_appautoscaling_target.testPocketApp_autoscaling_autoscaling_target_505E4EA1.scalable_dimension}", + "service_namespace": "\${aws_appautoscaling_target.testPocketApp_autoscaling_autoscaling_target_505E4EA1.service_namespace}", + "step_scaling_policy_configuration": { + "adjustment_type": "ChangeInCapacity", + "cooldown": 60, + "metric_aggregation_type": "Average", + "step_adjustment": [ + { + "metric_interval_lower_bound": "0", + "scaling_adjustment": 2 + } + ] + }, + "target_tracking_scaling_policy_configuration": null + } + }, + "aws_appautoscaling_target": { + "testPocketApp_autoscaling_autoscaling_target_505E4EA1": { + "max_capacity": 2, + "min_capacity": 1, + "resource_id": "service/\${aws_ecs_cluster.testPocketApp_ecs_cluster_C3960066.name}/\${aws_ecs_service.testPocketApp_ecs_service_ecs-service_182DEA4C.name}", + "scalable_dimension": "ecs:service:DesiredCount", + "service_namespace": "ecs" + } + }, + "aws_cloudwatch_dashboard": { + "testPocketApp_cloudwatch-dashboard_26E9F70C": { + "dashboard_body": "{\\"widgets\\":[{\\"type\\":\\"metric\\",\\"x\\":0,\\"y\\":0,\\"width\\":12,\\"height\\":6,\\"properties\\":{\\"metrics\\":[[\\"AWS/ApplicationELB\\",\\"HTTPCode_Target_4XX_Count\\",\\"LoadBalancer\\",\\"\${aws_alb.testPocketApp_application_load_balancer_alb_D538DEEE.arn_suffix}\\",{\\"yAxis\\":\\"left\\",\\"color\\":\\"#ff7f0e\\"}],[\\".\\",\\"RequestCount\\",\\".\\",\\".\\",{\\"yAxis\\":\\"right\\",\\"color\\":\\"#1f77b4\\"}],[\\".\\",\\"HTTPCode_Target_5XX_Count\\",\\".\\",\\".\\",{\\"color\\":\\"#d62728\\"}],[\\".\\",\\"HTTPCode_Target_2XX_Count\\",\\".\\",\\".\\",{\\"yAxis\\":\\"right\\",\\"color\\":\\"#2ca02c\\"}]],\\"view\\":\\"timeSeries\\",\\"stacked\\":false,\\"region\\":\\"us-east-1\\",\\"period\\":60,\\"stat\\":\\"Sum\\",\\"title\\":\\"Target Requests\\"}},{\\"type\\":\\"metric\\",\\"x\\":12,\\"y\\":0,\\"width\\":12,\\"height\\":6,\\"properties\\":{\\"metrics\\":[[\\"AWS/ApplicationELB\\",\\"HTTPCode_ELB_4XX_Count\\",\\"LoadBalancer\\",\\"\${aws_alb.testPocketApp_application_load_balancer_alb_D538DEEE.arn_suffix}\\",{\\"yAxis\\":\\"left\\",\\"color\\":\\"#ff7f0e\\"}],[\\".\\",\\"RequestCount\\",\\".\\",\\".\\",{\\"yAxis\\":\\"right\\",\\"color\\":\\"#1f77b4\\"}],[\\".\\",\\"HTTPCode_ELB_5XX_Count\\",\\".\\",\\".\\",{\\"color\\":\\"#d62728\\"}]],\\"view\\":\\"timeSeries\\",\\"stacked\\":false,\\"region\\":\\"us-east-1\\",\\"period\\":60,\\"stat\\":\\"Sum\\",\\"title\\":\\"ALB Requests\\"}},{\\"type\\":\\"metric\\",\\"x\\":12,\\"y\\":6,\\"width\\":12,\\"height\\":6,\\"properties\\":{\\"metrics\\":[[\\"AWS/ApplicationELB\\",\\"TargetResponseTime\\",\\"LoadBalancer\\",\\"\${aws_alb.testPocketApp_application_load_balancer_alb_D538DEEE.arn_suffix}\\",{\\"label\\":\\"Average\\",\\"color\\":\\"#aec7e8\\"}],[\\"...\\",{\\"stat\\":\\"p95\\",\\"label\\":\\"p95\\",\\"color\\":\\"#ffbb78\\"}],[\\"...\\",{\\"stat\\":\\"p99\\",\\"label\\":\\"p99\\",\\"color\\":\\"#98df8a\\"}]],\\"view\\":\\"timeSeries\\",\\"stacked\\":false,\\"region\\":\\"us-east-1\\",\\"stat\\":\\"Average\\",\\"period\\":60}},{\\"type\\":\\"metric\\",\\"x\\":0,\\"y\\":6,\\"width\\":12,\\"height\\":6,\\"properties\\":{\\"metrics\\":[[\\"ECS/ContainerInsights\\",\\"RunningTaskCount\\",\\"ServiceName\\",\\"\${aws_ecs_service.testPocketApp_ecs_service_ecs-service_182DEA4C.name}\\",\\"ClusterName\\",\\"\${aws_ecs_cluster.testPocketApp_ecs_cluster_C3960066.name}\\",{\\"yAxis\\":\\"right\\",\\"color\\":\\"#c49c94\\"}],[\\"AWS/ECS\\",\\"CPUUtilization\\",\\".\\",\\".\\",\\".\\",\\".\\",{\\"color\\":\\"#f7b6d2\\"}],[\\".\\",\\"MemoryUtilization\\",\\".\\",\\".\\",\\".\\",\\".\\",{\\"color\\":\\"#c7c7c7\\"}]],\\"view\\":\\"timeSeries\\",\\"stacked\\":false,\\"region\\":\\"us-east-1\\",\\"stat\\":\\"Average\\",\\"period\\":60,\\"annotations\\":{\\"horizontal\\":[{\\"color\\":\\"#e377c2\\",\\"label\\":\\"CPU scale out\\",\\"value\\":45},{\\"color\\":\\"#c5b0d5\\",\\"label\\":\\"CPU scale in\\",\\"value\\":30}]},\\"title\\":\\"Service Load\\"}}]}", + "dashboard_name": "testapp", + "lifecycle": { + "ignore_changes": [ + "dashboard_body" + ] + } + } + }, + "aws_cloudwatch_metric_alarm": { + "testPocketApp_autoscaling_scale_in_alarm_69B5F00D": { + "alarm_actions": [ + "\${aws_appautoscaling_policy.testPocketApp_autoscaling_scale_in_policy_A163A235.arn}" + ], + "alarm_description": "Alarm to reduce capacity if container CPU is low", + "alarm_name": "testapp Service Low CPU", + "comparison_operator": "LessThanThreshold", + "dimensions": { + "ClusterName": "\${aws_ecs_cluster.testPocketApp_ecs_cluster_C3960066.name}", + "ServiceName": "\${aws_ecs_service.testPocketApp_ecs_service_ecs-service_182DEA4C.name}" + }, + "evaluation_periods": 2, + "metric_name": "CPUUtilization", + "namespace": "AWS/ECS", + "period": 60, + "statistic": "Average", + "threshold": 30, + "treat_missing_data": "notBreaching" + }, + "testPocketApp_autoscaling_scale_out_alarm_4313FBE9": { + "alarm_actions": [ + "\${aws_appautoscaling_policy.testPocketApp_autoscaling_scale_out_policy_075BFAC4.arn}" + ], + "alarm_description": "Alarm to add capacity if container CPU is high", + "alarm_name": "testapp Service High CPU", + "comparison_operator": "GreaterThanThreshold", + "dimensions": { + "ClusterName": "\${aws_ecs_cluster.testPocketApp_ecs_cluster_C3960066.name}", + "ServiceName": "\${aws_ecs_service.testPocketApp_ecs_service_ecs-service_182DEA4C.name}" + }, + "evaluation_periods": 2, + "metric_name": "CPUUtilization", + "namespace": "AWS/ECS", + "period": 60, + "statistic": "Average", + "threshold": 45, + "treat_missing_data": "notBreaching" + } + }, + "aws_ecs_cluster": { + "testPocketApp_ecs_cluster_C3960066": { + "name": "testapp", + "setting": [ + { + "name": "containerInsights", + "value": "enabled" + } + ] + } + }, + "aws_ecs_service": { + "testPocketApp_ecs_service_ecs-service_182DEA4C": { + "cluster": "\${aws_ecs_cluster.testPocketApp_ecs_cluster_C3960066.arn}", + "depends_on": [ + "aws_alb_target_group.testPocketApp_ecs_service_blue_target_group_ecs_target_group_A4B85885", + "aws_alb_listener_rule.testPocketApp_ecs_service_listener_rule_C03BB04D" + ], + "deployment_controller": { + "type": "ECS" + }, + "deployment_maximum_percent": 200, + "deployment_minimum_healthy_percent": 100, + "desired_count": 2, + "launch_type": "FARGATE", + "lifecycle": { + "ignore_changes": [ + "desired_count", + "load_balancer" + ] + }, + "load_balancer": [ + { + "container_name": "main_container", + "container_port": 8675309, + "target_group_arn": "\${aws_alb_target_group.testPocketApp_ecs_service_blue_target_group_ecs_target_group_A4B85885.arn}" + } + ], + "name": "testapp", + "network_configuration": { + "security_groups": [ + "\${aws_security_group.testPocketApp_ecs_service_ecs_security_group_9C016FA8.id}" + ], + "subnets": "\${data.aws_subnets.testPocketApp_pocket_vpc_private_subnet_ids_EB1E3A65.ids}" + }, + "propagate_tags": "SERVICE", + "task_definition": "\${aws_ecs_task_definition.testPocketApp_ecs_service_ecs-task_A7E74E45.arn}" + } + }, + "aws_ecs_task_definition": { + "testPocketApp_ecs_service_ecs-task_A7E74E45": { + "container_definitions": "[]", + "cpu": "512", + "depends_on": [ + ], + "execution_role_arn": "\${aws_iam_role.testPocketApp_ecs_service_ecs-iam_ecs-execution-role_EB461A0C.arn}", + "family": "testapp", + "memory": "2048", + "network_mode": "awsvpc", + "requires_compatibilities": [ + "FARGATE" + ], + "skip_destroy": true, + "task_role_arn": "\${aws_iam_role.testPocketApp_ecs_service_ecs-iam_ecs-task-role_7DB93963.arn}", + "volume": [ + ] + } + }, + "aws_iam_role": { + "testPocketApp_ecs_service_ecs-iam_ecs-execution-role_EB461A0C": { + "assume_role_policy": "\${data.aws_iam_policy_document.testPocketApp_ecs_service_ecs-iam_ecs-task-assume_ABB81680.json}", + "name": "testapp-TaskExecutionRole" + }, + "testPocketApp_ecs_service_ecs-iam_ecs-task-role_7DB93963": { + "assume_role_policy": "\${data.aws_iam_policy_document.testPocketApp_ecs_service_ecs-iam_ecs-task-assume_ABB81680.json}", + "name": "testapp-TaskRole" + } + }, + "aws_route53_record": { + "testPocketApp_alb_certificate_certificate_record_3C49201F": { + "depends_on": [ + "aws_acm_certificate.testPocketApp_alb_certificate_417C14FF" + ], + "name": "\${tolist(aws_acm_certificate.testPocketApp_alb_certificate_417C14FF.domain_validation_options).0.resource_record_name}", + "records": [ + "\${tolist(aws_acm_certificate.testPocketApp_alb_certificate_417C14FF.domain_validation_options).0.resource_record_value}" + ], + "ttl": 60, + "type": "\${tolist(aws_acm_certificate.testPocketApp_alb_certificate_417C14FF.domain_validation_options).0.resource_record_type}", + "zone_id": "\${aws_route53_zone.testPocketApp_base_dns_subhosted_zone_1E1E3243.zone_id}" + }, + "testPocketApp_alb_record_7B56ED33": { + "alias": { + "evaluate_target_health": true, + "name": "\${aws_alb.testPocketApp_application_load_balancer_alb_D538DEEE.dns_name}", + "zone_id": "\${aws_alb.testPocketApp_application_load_balancer_alb_D538DEEE.zone_id}" + }, + "lifecycle": { + "ignore_changes": [ + "weighted_routing_policy[0].weight" + ] + }, + "name": "testing.bowling.gov", + "set_identifier": "1", + "type": "A", + "weighted_routing_policy": { + "weight": 1 + }, + "zone_id": "\${aws_route53_zone.testPocketApp_base_dns_subhosted_zone_1E1E3243.zone_id}" + }, + "testPocketApp_base_dns_subhosted_zone_ns_497FFB83": { + "name": "testing.bowling.gov", + "records": "\${aws_route53_zone.testPocketApp_base_dns_subhosted_zone_1E1E3243.name_servers}", + "ttl": 86400, + "type": "NS", + "zone_id": "\${data.aws_route53_zone.testPocketApp_base_dns_main_hosted_zone_9442EEB0.zone_id}" + } + }, + "aws_route53_zone": { + "testPocketApp_base_dns_subhosted_zone_1E1E3243": { + "name": "testing.bowling.gov" + } + }, + "aws_security_group": { + "testPocketApp_application_load_balancer_alb_security_group_91A27046": { + "description": "External security group (Managed by Terraform)", + "egress": [ + { + "cidr_blocks": [ + "0.0.0.0/0" + ], + "description": "required", + "from_port": 0, + "ipv6_cidr_blocks": [ + ], + "prefix_list_ids": [ + ], + "protocol": "-1", + "security_groups": [ + ], + "self": null, + "to_port": 0 + } + ], + "ingress": [ + { + "cidr_blocks": [ + "0.0.0.0/0" + ], + "description": null, + "from_port": 443, + "ipv6_cidr_blocks": null, + "prefix_list_ids": null, + "protocol": "TCP", + "security_groups": null, + "self": null, + "to_port": 443 + }, + { + "cidr_blocks": [ + "0.0.0.0/0" + ], + "description": null, + "from_port": 80, + "ipv6_cidr_blocks": null, + "prefix_list_ids": null, + "protocol": "TCP", + "security_groups": null, + "self": null, + "to_port": 80 + } + ], + "lifecycle": { + "create_before_destroy": true + }, + "name_prefix": "testapp-HTTP/S Security Group", + "tags": { + "Name": "testapp-HTTP/S Security Group" + }, + "vpc_id": "\${data.aws_vpc.testPocketApp_pocket_vpc_C4E157E3.id}" + }, + "testPocketApp_ecs_service_ecs_security_group_9C016FA8": { + "description": "Internal ECS Security Group (Managed by Terraform)", + "egress": [ + { + "cidr_blocks": [ + "0.0.0.0/0" + ], + "description": "required", + "from_port": 0, + "ipv6_cidr_blocks": [ + ], + "prefix_list_ids": [ + ], + "protocol": "-1", + "security_groups": [ + ], + "self": null, + "to_port": 0 + } + ], + "ingress": [ + { + "cidr_blocks": [ + ], + "description": "required", + "from_port": 80, + "ipv6_cidr_blocks": [ + ], + "prefix_list_ids": [ + ], + "protocol": "TCP", + "security_groups": [ + "\${aws_security_group.testPocketApp_application_load_balancer_alb_security_group_91A27046.id}" + ], + "self": null, + "to_port": 8675309 + } + ], + "lifecycle": { + "create_before_destroy": true + }, + "name_prefix": "testapp-ECSSecurityGroup", + "vpc_id": "\${data.aws_vpc.testPocketApp_pocket_vpc_C4E157E3.id}" + } + }, + "aws_wafv2_web_acl_association": { + "testPocketApp_application_waf_association_03F7C3FB": { + "resource_arn": "\${aws_alb.testPocketApp_application_load_balancer_alb_D538DEEE.arn}", + "web_acl_arn": "some-arn" + } + } + } +}" +`; + +exports[`PocketALBApplication renders an external application without a cdn 1`] = ` +"{ + "data": { + "aws_caller_identity": { + "testPocketApp_pocket_vpc_current_identity_06D87057": { + } + }, + "aws_iam_policy_document": { + "testPocketApp_ecs_service_ecs-iam_ecs-task-assume_ABB81680": { + "statement": [ + { + "actions": [ + "sts:AssumeRole" + ], + "effect": "Allow", + "principals": [ + { + "identifiers": [ + "ecs-tasks.amazonaws.com" + ], + "type": "Service" + } + ] + } + ], + "version": "2012-10-17" + } + }, + "aws_kms_alias": { + "testPocketApp_pocket_vpc_secrets_manager_key_9FD2BC93": { + "name": "alias/aws/secretsmanager" + } + }, + "aws_region": { + "testPocketApp_pocket_vpc_current_region_8ED435E7": { + } + }, + "aws_route53_zone": { + "testPocketApp_base_dns_main_hosted_zone_9442EEB0": { + "name": "bowling.gov" + } + }, + "aws_security_groups": { + "testPocketApp_pocket_vpc_default_security_groups_40B4FC48": { + "filter": [ + { + "name": "group-name", + "values": [ + "default" + ] + }, + { + "name": "vpc-id", + "values": [ + "\${data.aws_vpc.testPocketApp_pocket_vpc_C4E157E3.id}" + ] + } + ] + }, + "testPocketApp_pocket_vpc_internal_security_groups_12F7F666": { + "filter": [ + { + "name": "group-name", + "values": [ + "pocket-vpc-internal" + ] + }, + { + "name": "vpc-id", + "values": [ + "\${data.aws_vpc.testPocketApp_pocket_vpc_C4E157E3.id}" + ] + } + ] + } + }, + "aws_ssm_parameter": { + "testPocketApp_pocket_vpc_private_subnets_9D449563": { + "name": "/Shared/PrivateSubnets" + }, + "testPocketApp_pocket_vpc_public_subnets_B0B3A6AB": { + "name": "/Shared/PublicSubnets" + }, + "testPocketApp_pocket_vpc_vpc_ssm_param_235525D4": { + "name": "/Shared/Vpc" + } + }, + "aws_subnets": { + "testPocketApp_pocket_vpc_private_subnet_ids_EB1E3A65": { + "filter": [ + { + "name": "subnet-id", + "values": "\${split(\\",\\", data.aws_ssm_parameter.testPocketApp_pocket_vpc_private_subnets_9D449563.value)}" + }, + { + "name": "vpc-id", + "values": [ + "\${data.aws_vpc.testPocketApp_pocket_vpc_C4E157E3.id}" + ] + } + ] + }, + "testPocketApp_pocket_vpc_public_subnet_ids_01F0B902": { + "filter": [ + { + "name": "subnet-id", + "values": "\${split(\\",\\", data.aws_ssm_parameter.testPocketApp_pocket_vpc_public_subnets_B0B3A6AB.value)}" + }, + { + "name": "vpc-id", + "values": [ + "\${data.aws_vpc.testPocketApp_pocket_vpc_C4E157E3.id}" + ] + } + ] + } + }, + "aws_vpc": { + "testPocketApp_pocket_vpc_C4E157E3": { + "filter": [ + { + "name": "vpc-id", + "values": [ + "\${data.aws_ssm_parameter.testPocketApp_pocket_vpc_vpc_ssm_param_235525D4.value}" + ] + } + ] + } + } + }, + "output": { + "ecs-application-url": { + "description": "ECS Application URL", + "value": "testing.bowling.gov" + } + }, + "resource": { + "aws_acm_certificate": { + "testPocketApp_alb_certificate_417C14FF": { + "domain_name": "testing.bowling.gov", + "lifecycle": { + "create_before_destroy": true + }, + "validation_method": "DNS" + } + }, + "aws_acm_certificate_validation": { + "testPocketApp_alb_certificate_certificate_validation_7D899C6A": { + "certificate_arn": "\${aws_acm_certificate.testPocketApp_alb_certificate_417C14FF.arn}", + "depends_on": [ + "aws_route53_record.testPocketApp_alb_certificate_certificate_record_3C49201F", + "aws_acm_certificate.testPocketApp_alb_certificate_417C14FF" + ], + "validation_record_fqdns": [ + "\${aws_route53_record.testPocketApp_alb_certificate_certificate_record_3C49201F.fqdn}" + ] + } + }, + "aws_alb": { + "testPocketApp_application_load_balancer_alb_D538DEEE": { + "internal": false, + "name_prefix": "TSTAPP", + "security_groups": [ + "\${aws_security_group.testPocketApp_application_load_balancer_alb_security_group_91A27046.id}" + ], + "subnets": "\${data.aws_subnets.testPocketApp_pocket_vpc_public_subnet_ids_01F0B902.ids}" + } + }, + "aws_alb_listener": { + "testPocketApp_listener_http_A9E227C2": { + "default_action": [ + { + "redirect": { + "port": "443", + "protocol": "HTTPS", + "status_code": "HTTP_301" + }, + "type": "redirect" + } + ], + "load_balancer_arn": "\${aws_alb.testPocketApp_application_load_balancer_alb_D538DEEE.arn}", + "port": 80, + "protocol": "HTTP" + }, + "testPocketApp_listener_https_7F49DE83": { + "certificate_arn": "\${aws_acm_certificate.testPocketApp_alb_certificate_417C14FF.arn}", + "default_action": [ + { + "fixed_response": { + "content_type": "text/plain", + "message_body": "", + "status_code": "503" + }, + "type": "fixed-response" + } + ], + "load_balancer_arn": "\${aws_alb.testPocketApp_application_load_balancer_alb_D538DEEE.arn}", + "port": 443, + "protocol": "HTTPS", + "ssl_policy": "ELBSecurityPolicy-TLS-1-1-2017-01" + } + }, + "aws_alb_listener_rule": { + "testPocketApp_ecs_service_listener_rule_C03BB04D": { + "action": [ + { + "target_group_arn": "\${aws_alb_target_group.testPocketApp_ecs_service_blue_target_group_ecs_target_group_A4B85885.arn}", + "type": "forward" + } + ], + "condition": [ + { + "path_pattern": { + "values": [ + "*" + ] + } + } + ], + "lifecycle": { + "ignore_changes": [ + "action" + ] + }, + "listener_arn": "\${aws_alb_listener.testPocketApp_listener_https_7F49DE83.arn}", + "priority": 1 + } + }, + "aws_alb_target_group": { + "testPocketApp_ecs_service_blue_target_group_ecs_target_group_A4B85885": { + "deregistration_delay": "120", + "health_check": { + "healthy_threshold": 5, + "interval": 15, + "path": "/test", + "protocol": "HTTP", + "unhealthy_threshold": 3 + }, + "name_prefix": "TSTAPP", + "port": 80, + "protocol": "HTTP", + "tags": { + "type": "blue" + }, + "target_type": "ip", + "vpc_id": "\${data.aws_vpc.testPocketApp_pocket_vpc_C4E157E3.id}" + } + }, + "aws_appautoscaling_policy": { + "testPocketApp_autoscaling_scale_in_policy_A163A235": { + "depends_on": [ + "aws_appautoscaling_target.testPocketApp_autoscaling_autoscaling_target_505E4EA1" + ], + "name": "testapp-ScaleInPolicy", + "policy_type": "StepScaling", + "resource_id": "\${aws_appautoscaling_target.testPocketApp_autoscaling_autoscaling_target_505E4EA1.resource_id}", + "scalable_dimension": "\${aws_appautoscaling_target.testPocketApp_autoscaling_autoscaling_target_505E4EA1.scalable_dimension}", + "service_namespace": "\${aws_appautoscaling_target.testPocketApp_autoscaling_autoscaling_target_505E4EA1.service_namespace}", + "step_scaling_policy_configuration": { + "adjustment_type": "ChangeInCapacity", + "cooldown": 60, + "metric_aggregation_type": "Average", + "step_adjustment": [ + { + "metric_interval_upper_bound": "0", + "scaling_adjustment": -1 + } + ] + }, + "target_tracking_scaling_policy_configuration": null + }, + "testPocketApp_autoscaling_scale_out_policy_075BFAC4": { + "depends_on": [ + "aws_appautoscaling_target.testPocketApp_autoscaling_autoscaling_target_505E4EA1" + ], + "name": "testapp-ScaleOutPolicy", + "policy_type": "StepScaling", + "resource_id": "\${aws_appautoscaling_target.testPocketApp_autoscaling_autoscaling_target_505E4EA1.resource_id}", + "scalable_dimension": "\${aws_appautoscaling_target.testPocketApp_autoscaling_autoscaling_target_505E4EA1.scalable_dimension}", + "service_namespace": "\${aws_appautoscaling_target.testPocketApp_autoscaling_autoscaling_target_505E4EA1.service_namespace}", + "step_scaling_policy_configuration": { + "adjustment_type": "ChangeInCapacity", + "cooldown": 60, + "metric_aggregation_type": "Average", + "step_adjustment": [ + { + "metric_interval_lower_bound": "0", + "scaling_adjustment": 2 + } + ] + }, + "target_tracking_scaling_policy_configuration": null + } + }, + "aws_appautoscaling_target": { + "testPocketApp_autoscaling_autoscaling_target_505E4EA1": { + "max_capacity": 2, + "min_capacity": 1, + "resource_id": "service/\${aws_ecs_cluster.testPocketApp_ecs_cluster_C3960066.name}/\${aws_ecs_service.testPocketApp_ecs_service_ecs-service_182DEA4C.name}", + "scalable_dimension": "ecs:service:DesiredCount", + "service_namespace": "ecs" + } + }, + "aws_cloudwatch_dashboard": { + "testPocketApp_cloudwatch-dashboard_26E9F70C": { + "dashboard_body": "{\\"widgets\\":[{\\"type\\":\\"metric\\",\\"x\\":0,\\"y\\":0,\\"width\\":12,\\"height\\":6,\\"properties\\":{\\"metrics\\":[[\\"AWS/ApplicationELB\\",\\"HTTPCode_Target_4XX_Count\\",\\"LoadBalancer\\",\\"\${aws_alb.testPocketApp_application_load_balancer_alb_D538DEEE.arn_suffix}\\",{\\"yAxis\\":\\"left\\",\\"color\\":\\"#ff7f0e\\"}],[\\".\\",\\"RequestCount\\",\\".\\",\\".\\",{\\"yAxis\\":\\"right\\",\\"color\\":\\"#1f77b4\\"}],[\\".\\",\\"HTTPCode_Target_5XX_Count\\",\\".\\",\\".\\",{\\"color\\":\\"#d62728\\"}],[\\".\\",\\"HTTPCode_Target_2XX_Count\\",\\".\\",\\".\\",{\\"yAxis\\":\\"right\\",\\"color\\":\\"#2ca02c\\"}]],\\"view\\":\\"timeSeries\\",\\"stacked\\":false,\\"region\\":\\"us-east-1\\",\\"period\\":60,\\"stat\\":\\"Sum\\",\\"title\\":\\"Target Requests\\"}},{\\"type\\":\\"metric\\",\\"x\\":12,\\"y\\":0,\\"width\\":12,\\"height\\":6,\\"properties\\":{\\"metrics\\":[[\\"AWS/ApplicationELB\\",\\"HTTPCode_ELB_4XX_Count\\",\\"LoadBalancer\\",\\"\${aws_alb.testPocketApp_application_load_balancer_alb_D538DEEE.arn_suffix}\\",{\\"yAxis\\":\\"left\\",\\"color\\":\\"#ff7f0e\\"}],[\\".\\",\\"RequestCount\\",\\".\\",\\".\\",{\\"yAxis\\":\\"right\\",\\"color\\":\\"#1f77b4\\"}],[\\".\\",\\"HTTPCode_ELB_5XX_Count\\",\\".\\",\\".\\",{\\"color\\":\\"#d62728\\"}]],\\"view\\":\\"timeSeries\\",\\"stacked\\":false,\\"region\\":\\"us-east-1\\",\\"period\\":60,\\"stat\\":\\"Sum\\",\\"title\\":\\"ALB Requests\\"}},{\\"type\\":\\"metric\\",\\"x\\":12,\\"y\\":6,\\"width\\":12,\\"height\\":6,\\"properties\\":{\\"metrics\\":[[\\"AWS/ApplicationELB\\",\\"TargetResponseTime\\",\\"LoadBalancer\\",\\"\${aws_alb.testPocketApp_application_load_balancer_alb_D538DEEE.arn_suffix}\\",{\\"label\\":\\"Average\\",\\"color\\":\\"#aec7e8\\"}],[\\"...\\",{\\"stat\\":\\"p95\\",\\"label\\":\\"p95\\",\\"color\\":\\"#ffbb78\\"}],[\\"...\\",{\\"stat\\":\\"p99\\",\\"label\\":\\"p99\\",\\"color\\":\\"#98df8a\\"}]],\\"view\\":\\"timeSeries\\",\\"stacked\\":false,\\"region\\":\\"us-east-1\\",\\"stat\\":\\"Average\\",\\"period\\":60}},{\\"type\\":\\"metric\\",\\"x\\":0,\\"y\\":6,\\"width\\":12,\\"height\\":6,\\"properties\\":{\\"metrics\\":[[\\"ECS/ContainerInsights\\",\\"RunningTaskCount\\",\\"ServiceName\\",\\"\${aws_ecs_service.testPocketApp_ecs_service_ecs-service_182DEA4C.name}\\",\\"ClusterName\\",\\"\${aws_ecs_cluster.testPocketApp_ecs_cluster_C3960066.name}\\",{\\"yAxis\\":\\"right\\",\\"color\\":\\"#c49c94\\"}],[\\"AWS/ECS\\",\\"CPUUtilization\\",\\".\\",\\".\\",\\".\\",\\".\\",{\\"color\\":\\"#f7b6d2\\"}],[\\".\\",\\"MemoryUtilization\\",\\".\\",\\".\\",\\".\\",\\".\\",{\\"color\\":\\"#c7c7c7\\"}]],\\"view\\":\\"timeSeries\\",\\"stacked\\":false,\\"region\\":\\"us-east-1\\",\\"stat\\":\\"Average\\",\\"period\\":60,\\"annotations\\":{\\"horizontal\\":[{\\"color\\":\\"#e377c2\\",\\"label\\":\\"CPU scale out\\",\\"value\\":45},{\\"color\\":\\"#c5b0d5\\",\\"label\\":\\"CPU scale in\\",\\"value\\":30}]},\\"title\\":\\"Service Load\\"}}]}", + "dashboard_name": "testapp", + "lifecycle": { + "ignore_changes": [ + "dashboard_body" + ] + } + } + }, + "aws_cloudwatch_metric_alarm": { + "testPocketApp_autoscaling_scale_in_alarm_69B5F00D": { + "alarm_actions": [ + "\${aws_appautoscaling_policy.testPocketApp_autoscaling_scale_in_policy_A163A235.arn}" + ], + "alarm_description": "Alarm to reduce capacity if container CPU is low", + "alarm_name": "testapp Service Low CPU", + "comparison_operator": "LessThanThreshold", + "dimensions": { + "ClusterName": "\${aws_ecs_cluster.testPocketApp_ecs_cluster_C3960066.name}", + "ServiceName": "\${aws_ecs_service.testPocketApp_ecs_service_ecs-service_182DEA4C.name}" + }, + "evaluation_periods": 2, + "metric_name": "CPUUtilization", + "namespace": "AWS/ECS", + "period": 60, + "statistic": "Average", + "threshold": 30, + "treat_missing_data": "notBreaching" + }, + "testPocketApp_autoscaling_scale_out_alarm_4313FBE9": { + "alarm_actions": [ + "\${aws_appautoscaling_policy.testPocketApp_autoscaling_scale_out_policy_075BFAC4.arn}" + ], + "alarm_description": "Alarm to add capacity if container CPU is high", + "alarm_name": "testapp Service High CPU", + "comparison_operator": "GreaterThanThreshold", + "dimensions": { + "ClusterName": "\${aws_ecs_cluster.testPocketApp_ecs_cluster_C3960066.name}", + "ServiceName": "\${aws_ecs_service.testPocketApp_ecs_service_ecs-service_182DEA4C.name}" + }, + "evaluation_periods": 2, + "metric_name": "CPUUtilization", + "namespace": "AWS/ECS", + "period": 60, + "statistic": "Average", + "threshold": 45, + "treat_missing_data": "notBreaching" + } + }, + "aws_ecs_cluster": { + "testPocketApp_ecs_cluster_C3960066": { + "name": "testapp", + "setting": [ + { + "name": "containerInsights", + "value": "enabled" + } + ] + } + }, + "aws_ecs_service": { + "testPocketApp_ecs_service_ecs-service_182DEA4C": { + "cluster": "\${aws_ecs_cluster.testPocketApp_ecs_cluster_C3960066.arn}", + "depends_on": [ + "aws_alb_target_group.testPocketApp_ecs_service_blue_target_group_ecs_target_group_A4B85885", + "aws_alb_listener_rule.testPocketApp_ecs_service_listener_rule_C03BB04D" + ], + "deployment_controller": { + "type": "ECS" + }, + "deployment_maximum_percent": 200, + "deployment_minimum_healthy_percent": 100, + "desired_count": 2, + "launch_type": "FARGATE", + "lifecycle": { + "ignore_changes": [ + "desired_count", + "load_balancer" + ] + }, + "load_balancer": [ + { + "container_name": "main_container", + "container_port": 8675309, + "target_group_arn": "\${aws_alb_target_group.testPocketApp_ecs_service_blue_target_group_ecs_target_group_A4B85885.arn}" + } + ], + "name": "testapp", + "network_configuration": { + "security_groups": [ + "\${aws_security_group.testPocketApp_ecs_service_ecs_security_group_9C016FA8.id}" + ], + "subnets": "\${data.aws_subnets.testPocketApp_pocket_vpc_private_subnet_ids_EB1E3A65.ids}" + }, + "propagate_tags": "SERVICE", + "task_definition": "\${aws_ecs_task_definition.testPocketApp_ecs_service_ecs-task_A7E74E45.arn}" + } + }, + "aws_ecs_task_definition": { + "testPocketApp_ecs_service_ecs-task_A7E74E45": { + "container_definitions": "[]", + "cpu": "512", + "depends_on": [ + ], + "execution_role_arn": "\${aws_iam_role.testPocketApp_ecs_service_ecs-iam_ecs-execution-role_EB461A0C.arn}", + "family": "testapp", + "memory": "2048", + "network_mode": "awsvpc", + "requires_compatibilities": [ + "FARGATE" + ], + "skip_destroy": true, + "task_role_arn": "\${aws_iam_role.testPocketApp_ecs_service_ecs-iam_ecs-task-role_7DB93963.arn}", + "volume": [ + ] + } + }, + "aws_iam_role": { + "testPocketApp_ecs_service_ecs-iam_ecs-execution-role_EB461A0C": { + "assume_role_policy": "\${data.aws_iam_policy_document.testPocketApp_ecs_service_ecs-iam_ecs-task-assume_ABB81680.json}", + "name": "testapp-TaskExecutionRole" + }, + "testPocketApp_ecs_service_ecs-iam_ecs-task-role_7DB93963": { + "assume_role_policy": "\${data.aws_iam_policy_document.testPocketApp_ecs_service_ecs-iam_ecs-task-assume_ABB81680.json}", + "name": "testapp-TaskRole" + } + }, + "aws_route53_record": { + "testPocketApp_alb_certificate_certificate_record_3C49201F": { + "depends_on": [ + "aws_acm_certificate.testPocketApp_alb_certificate_417C14FF" + ], + "name": "\${tolist(aws_acm_certificate.testPocketApp_alb_certificate_417C14FF.domain_validation_options).0.resource_record_name}", + "records": [ + "\${tolist(aws_acm_certificate.testPocketApp_alb_certificate_417C14FF.domain_validation_options).0.resource_record_value}" + ], + "ttl": 60, + "type": "\${tolist(aws_acm_certificate.testPocketApp_alb_certificate_417C14FF.domain_validation_options).0.resource_record_type}", + "zone_id": "\${aws_route53_zone.testPocketApp_base_dns_subhosted_zone_1E1E3243.zone_id}" + }, + "testPocketApp_alb_record_7B56ED33": { + "alias": { + "evaluate_target_health": true, + "name": "\${aws_alb.testPocketApp_application_load_balancer_alb_D538DEEE.dns_name}", + "zone_id": "\${aws_alb.testPocketApp_application_load_balancer_alb_D538DEEE.zone_id}" + }, + "lifecycle": { + "ignore_changes": [ + "weighted_routing_policy[0].weight" + ] + }, + "name": "testing.bowling.gov", + "set_identifier": "1", + "type": "A", + "weighted_routing_policy": { + "weight": 1 + }, + "zone_id": "\${aws_route53_zone.testPocketApp_base_dns_subhosted_zone_1E1E3243.zone_id}" + }, + "testPocketApp_base_dns_subhosted_zone_ns_497FFB83": { + "name": "testing.bowling.gov", + "records": "\${aws_route53_zone.testPocketApp_base_dns_subhosted_zone_1E1E3243.name_servers}", + "ttl": 86400, + "type": "NS", + "zone_id": "\${data.aws_route53_zone.testPocketApp_base_dns_main_hosted_zone_9442EEB0.zone_id}" + } + }, + "aws_route53_zone": { + "testPocketApp_base_dns_subhosted_zone_1E1E3243": { + "name": "testing.bowling.gov" + } + }, "aws_security_group": { "testPocketApp_application_load_balancer_alb_security_group_91A27046": { "description": "External security group (Managed by Terraform)",