From 5bcd2554f5f60a6c33879e459dc0eace6ccaf047 Mon Sep 17 00:00:00 2001 From: ea-mtenhoor <42071768+ea-mtenhoor@users.noreply.github.com> Date: Fri, 30 May 2025 15:15:14 -0500 Subject: [PATCH] Add updates for 7.x --- .github/workflows/linting.yml | 35 ++ .vscode/extensions.json | 6 + README.md | 88 ++--- database/README.md | 11 + docker/README.md | 10 + docs/sbe-architecture.md | 30 ++ docs/sbe-contribution-guidelines.md | 12 +- docs/sbe-deployment-steps.md | 35 -- docs/sbe-functions.md | 16 +- docs/sbe-parameter-values.md | 69 ++-- templates/.checkov.yaml | 2 + templates/1-StartingBlocks-Main-Template.yml | 329 +++++++++++++------ templates/x-aurora-postgres-db.yml | 26 +- templates/x-beanstalk-admin-api.yml | 38 ++- templates/x-beanstalk-web-api.yml | 51 ++- templates/x-cloudwatch-dashboard.yml | 28 +- templates/x-lambda-core-functions.yml | 249 ++++++++++++-- templates/x-restore-databases.yml | 20 +- templates/x-shared-resources.yml | 80 ++++- templates/x-state-machines.yml | 105 ++++++ templates/x-swagger.yml | 69 +++- 21 files changed, 1015 insertions(+), 294 deletions(-) create mode 100644 .github/workflows/linting.yml create mode 100644 .vscode/extensions.json create mode 100644 database/README.md create mode 100644 docker/README.md create mode 100644 docs/sbe-architecture.md delete mode 100644 docs/sbe-deployment-steps.md create mode 100644 templates/.checkov.yaml mode change 100755 => 100644 templates/x-beanstalk-web-api.yml create mode 100644 templates/x-state-machines.yml diff --git a/.github/workflows/linting.yml b/.github/workflows/linting.yml new file mode 100644 index 0000000..ca098d0 --- /dev/null +++ b/.github/workflows/linting.yml @@ -0,0 +1,35 @@ +name: CFN Lint + +on: + pull_request: + branches: + - '7.x' + +permissions: read-all + +jobs: + security-scan: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ["3.12"] + steps: + - name: Checkout Repository + uses: actions/checkout@v4 + with: + ref: ${{ github.head_ref }} + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v3 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install cfn-lint + pip install checkov + - name: CFN Lint Templates + run: | + cfn-lint $(git ls-files './templates/*.yml') --include-checks I + - name: Checkov + run: | + checkov --directory templates/ --quiet \ No newline at end of file diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..001edaf --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,6 @@ +{ + "recommendations": [ + "kddejong.vscode-cfn-lint", + "xargsuk.checkov-prismaless" + ] +} diff --git a/README.md b/README.md index 5a72146..f478c3b 100644 --- a/README.md +++ b/README.md @@ -2,41 +2,55 @@ Welcome to the StartingBlocks Open Source repository! The artifacts in this repository will allow users to deploy a cloud native Ed-Fi ODS/API implementation using AWS resources. StartingBlocks is additionally inclusive of a suite of management functions that allow users to manage resources to configure their Ed-Fi deployment appropraitely for a variety of use cases. -[Deployment Steps](./docs/sbe-deployment-steps.md) +>[!TIP] +>Documentation is available in the [docs](./docs/) folder in this repo, and at [docs.startingblocks.org](https://docs.startingblocks.org). + +# Getting Started + +## Prerequisites +1. You must have an AWS account +2. You must have deployed a custom VPC, 2 public subnets, and 2 private subnets before deploying the StartingBlocks templates. It is not advisable for users to use the default VPC initially deployed by AWS. +3. You must have created a Hosted Zone and NS record for the domain at which you intend to deploy your StartingBlocks environment(s). + +If you would like more guidance and support with deploying StartingBlocks, [please reach out to our team using this form.](https://edanalytics.atlassian.net/helpcenter/products-and-services/portal/15/group/45/create/300) + +## Deployment Steps +Steps to deploy StartingBlocks templates. + +1. In your AWS account, create an S3 bucket that will be used to hold the StartingBlocks templates. +Example bucket name: + - `{orgname}-{environment label}-{version}-cloudformation` + - `EducationAnalytics-Prod2425-7.1-cloudformation` +##### + aws s3api create-bucket --bucket my-cfn-bucket +2. Clone the StartingBlocks OSS repository to your local workstation and checkout the relevant version branch. + - All Ed-Fi ODS/API 7.x versions can be deployed from the StartingBlocks `7.x` branch. Use this branch to deploy Ed-Fi API 7.1, 7.2, or 7.3. +##### + git clone https://github.com/edanalytics/startingblocks_oss.git + cd startingblocks_oss + git checkout 7.x +3. Upload the contents of the repository to your S3 bucket location. You can do this via AWS CLI or dragging the folders from your file explorer to the S3 console for your bucket. +##### + aws s3 sync . s3://my-cfn-bucket/ --exclude ".git/*" +4. Copy the S3 URL for the `templates > 1-StartingBlocks-Main-Template.yml` file. +##### + https://my-cfn-bucket.s3.REGION.amazonaws.com/templates/1-StartingBlocks-Main-Template.yml +1. Navigate to the [CloudFormation console](https://us-east-2.console.aws.amazon.com/cloudformation/) in AWS. Create a new stack with new resources. +2. Select `Template is ready > Amazon S3 URL` and paste your copied URL for the `1-StartingBlocks-Main-Template.yml` file into the field. +3. Enter stack parameter values. [Please read the doc here for more information on parameter values.](./docs/sbe-parameter-values.md) +4. Navigate through the various screens until you are able to start the deployment. + +## Post Deploymnet Configuration + +After a successful CloudFormation deployment of the StartingBlocks stack: +1. Use the [TenantManagement](./docs/sbe-functions.md#variable-requirements) function to create Ed-Fi Tenants. +2. Use the [ODSManagement](./docs/sbe-functions.md#variable-requirements-1) function to create ODSs within Tenants. +3. Use the [EdOrgManagement](./docs/sbe-functions.md#variable-requirements-2) function to create preliminary EdOrg records within ODSs. + - The EdOrg records created using this function are meant to be updated with full information via integrating systems. It is not meant to create comprehensive records, but rather populate enough information to create API credentials. +4. Use the [TenantManagement](./docs/sbe-functions.md#keygen) KeyGen action to create AdminAPI credentials for a given Tenant. +5. Use the AdminAPI to create application (Ed-Fi API) credentials for the Tenants. + - [Please read the AdminAPI documentation published by the Alliance for detailed information on usage.](https://github.com/Ed-Fi-Alliance-OSS/AdminAPI-2.x) + +>[!TIP] +>Education Analytics offers StartingBlocks as a managed service. [Inquire here](https://www.edanalytics.org/products/starting-blocks)! -[Management and Reporting Functions Usage](./docs/sbe-functions.md) - -# Overall Architecture -Below is a high level architecture diagram and inventory of some AWS resources deployed when using these templates. It's important to note, that once these resources are deployed, users are immediately responsible for any incurred costs, even if the environment is not being used. Please also note that there are some pre-requisite resources that are included in this diagram for informational purposes. Specifically, public subnets, VPC and private subnets must already exist prior to deploying StartingBlocks. For deployment steps [please click here](./docs/sbe-deployment-steps.md) - - -![](./docs/imgs/StartingBlocks-OSS-diagram.svg) - ---- - -**NOTE:** - -Currently we do not support deployments of the Ed-Fi Admin App for StartingBlocks v7.1. There is no Admin App currently supported by the alliance that is compatible with Ed-Fi v7.x. When there is a release of the Admin App for v7.x, we are commited to supporting it in StartingBlocks. - ---- - -# Lambda Functions -The diagram highlights that there is a suite of AWS Lambda functions used in StartingBlocks. There are Ed-Fi environment management functions, but there are also general utility functions or functions created as custom CloudFormation resources. Below is a complete inventory of Lambda functions deployed with StartingBlocks. There is also more detail specifically on the environment management functions [in our docs folder here.](./docs/sbe-functions.md) All Lambda functions deployed by CloudFormation are prefixed by the `EnvLabel` parameter value to make them easy to find in the Lambda console. - -## Utility Functions -- DbRestore - Restores template databases on initial StartingBlocks deployments. -- EdFiBeanstalkSNSToSlack - Forwards SNS messages sent from Beanstalk Env and RDS instance to Slack. -- East1Alarm - Creates a Route53 Healthcheck Alarm in us-east-1. -- API-Publisher-getmaxchangeversion - Optionally deployed if publisher is also deployed. Lambda Function to replace the getmaxchangeversion ODS function. -## Custom CloudFormation Resource Functions -- EncryptionKeyGenerator - CloudFormation Custom Resource provider. Creates and stores a base64 encoded 256-bit key. -- SetCloudWatchRetention - CloudFormation Custom Resource provider. Sets retention on CloudWatch Log Groups. -- ODSDerivatives - CloudFormation Custom Resource provider. Adds/removes ODS instance derivative in admin db when ODS instance derivative is created/deleted. -## Management and Reporting Functions -- TenantManagement - Used to manage Tenants in Ed-Fi 7.x environments. -- ODSManagement - Used to manage ODSs in Ed-Fi 7.x environments. -- EdOrgManagement - Used to managed Education Organizations in Ed-Fi 7.x environments. -- SbeMetadata - Optionally Deployed if SBAA admin interface is chosen. Provides ARNs for all Lambda Ed-Fi resource management functions. -- TenantResourceTree - Provides tree structure of resources in a tenant. -- DataFreshnessJson - Provides a JSON output for resource counts and dates per table within a given ODS and Tenant. -- ODSUserPermissions - Grants permissions to users in the ODS managed by database groups. \ No newline at end of file diff --git a/database/README.md b/database/README.md new file mode 100644 index 0000000..425a056 --- /dev/null +++ b/database/README.md @@ -0,0 +1,11 @@ +# Artifact Storage Guide + +Artifacts are stored in Amazon S3, and the **`ArtifactsS3SourceBucket`** parameter determines whether to use **default artifacts** from the central S3 bucket or **custom artifacts** stored in the same directory as this README.md file. + +## Choosing the Artifact Source + +- If the **`ArtifactsS3SourceBucket`** parameter is set to **Use artifacts from EA's central artifact store**, artifacts will be retrieved from the **central S3 bucket**, which will be used in StartingBlocks CloudFromation stack + +- If the **`ArtifactsS3SourceBucket`** parameter is set to **Use my own custom artifacts in the CloudFormation bucket**, make sure to have the artifacts saved in the same directory as this README file to be used by the StartingBlocks CloudFormation stack. + + diff --git a/docker/README.md b/docker/README.md new file mode 100644 index 0000000..0d3a373 --- /dev/null +++ b/docker/README.md @@ -0,0 +1,10 @@ +# Artifact Storage Guide + +Artifacts are stored in Amazon S3, and the **`ArtifactsS3SourceBucket`** parameter determines whether to use **default artifacts** from the central S3 bucket or **custom artifacts** stored in the same directory as this README.md file. + +## Choosing the Artifact Source + +- If the **`ArtifactsS3SourceBucket`** parameter is set to **Use artifacts from EA's central artifact store**, artifacts will be retrieved from the **central S3 bucket**, which will be used in StartingBlocks CloudFromation stack + +- If the **`ArtifactsS3SourceBucket`** parameter is set to **Use my own custom artifacts in the CloudFormation bucket**, make sure to have the artifacts saved in the same directory as this README file to be used by the StartingBlocks CloudFormation stack. + \ No newline at end of file diff --git a/docs/sbe-architecture.md b/docs/sbe-architecture.md new file mode 100644 index 0000000..0054886 --- /dev/null +++ b/docs/sbe-architecture.md @@ -0,0 +1,30 @@ +# Overall Architecture +Below is a high level architecture diagram and inventory of some AWS resources deployed when using these templates. It's important to note that once these resources are deployed, users are immediately responsible for any incurred costs, even if the environment is not being used. Please also note that there are some pre-requisite resources that are included in this diagram for informational purposes. Specifically, public subnets, VPC and private subnets must already exist prior to deploying StartingBlocks. For deployment steps [please click here](../README.md) + + +![](imgs/StartingBlocks-OSS-diagram.svg) + +>[!NOTE] +Currently we do not support deployments of the Ed-Fi Admin App for StartingBlocks v7.1. There is no Admin App currently supported by the Alliance that is compatible with Ed-Fi v7.x. When there is a release of the Admin App for v7.x, we are commited to supporting it in StartingBlocks. + + +# Lambda Functions +The diagram highlights that there is a suite of AWS Lambda functions used in StartingBlocks. There are Ed-Fi environment management functions, but there are also general utility functions or functions created as custom CloudFormation resources. Below is a complete inventory of Lambda functions deployed with StartingBlocks. There is also more detail specifically on the environment management functions [in our docs folder here.](sbe-functions.md) All Lambda functions deployed by CloudFormation are prefixed by the `EnvLabel` parameter value to make them easy to find in the Lambda console. + +## Utility Functions +- DbRestore - Restores template databases on initial StartingBlocks deployments. +- EdFiBeanstalkSNSToSlack - Forwards SNS messages sent from Beanstalk Env and RDS instance to Slack. +- East1Alarm - Creates a Route53 Healthcheck Alarm in us-east-1. +- API-Publisher-getmaxchangeversion - Optionally deployed if publisher is also deployed. Lambda Function to replace the getmaxchangeversion ODS function. +## Custom CloudFormation Resource Functions +- EncryptionKeyGenerator - CloudFormation Custom Resource provider. Creates and stores a base64 encoded 256-bit key. +- SetCloudWatchRetention - CloudFormation Custom Resource provider. Sets retention on CloudWatch Log Groups. +- ODSDerivatives - CloudFormation Custom Resource provider. Adds/removes ODS instance derivative in admin db when ODS instance derivative is created/deleted. +## Management and Reporting Functions +- TenantManagement - Used to manage Tenants in Ed-Fi 7.x environments. +- ODSManagement - Used to manage ODSs in Ed-Fi 7.x environments. +- EdOrgManagement - Used to managed Education Organizations in Ed-Fi 7.x environments. +- SbeMetadata - Optionally Deployed if SBAA admin interface is chosen. Provides ARNs for all Lambda Ed-Fi resource management functions. +- TenantResourceTree - Provides tree structure of resources in a tenant. +- DataFreshnessJson - Provides a JSON output for resource counts and dates per table within a given ODS and Tenant. +- ODSUserPermissions - Grants permissions to users in the ODS managed by database groups. \ No newline at end of file diff --git a/docs/sbe-contribution-guidelines.md b/docs/sbe-contribution-guidelines.md index 040a9a6..14fb45b 100644 --- a/docs/sbe-contribution-guidelines.md +++ b/docs/sbe-contribution-guidelines.md @@ -10,14 +10,10 @@ Any feature requests or questions should be raised using [GitHub Issues on the O 5. Community Feature Addition Pull Requests 6. StartingBlocks Product Team Feature Addition Pull Requests ---- +>[!NOTE] +>Security releases, documentation updates, and updates to the Ed-Fi application bundles will be released in special priority as deemed appropriate. -**NOTE:** -Security releases, documentation updates, and updates to the Ed-Fi application bundles will be released in special priority as deemed appropriate. - ---- - -If you'd like to recieve up to date notifications for updates you can change your `watch` settings on the repository. +If you'd like to receive up to date notifications for updates you can change your `watch` settings on the repository. ![](./imgs/watch_screenshot.png). -For most recent release please [visit the release page.](https://github.com/edanalytics/startingblocks_oss/releases/) \ No newline at end of file +For the most recent release please [visit the release page.](https://github.com/edanalytics/startingblocks_oss/releases/) \ No newline at end of file diff --git a/docs/sbe-deployment-steps.md b/docs/sbe-deployment-steps.md deleted file mode 100644 index ac83b82..0000000 --- a/docs/sbe-deployment-steps.md +++ /dev/null @@ -1,35 +0,0 @@ -# StartingBlocks Deployment - -## Prerequisites -1. You must have an AWS account -2. You must have deployed a custom VPC, 2 public subnets, and 2 private subnets before deploying the StartingBlocks templates. It is not advisable for users to use the default VPC initially deployed by AWS. -3. You must have created a Hosted Zone and NS record for the domain at which you intend to deploy your StartingBlocks environment(s). - -If you would like more guidance and support with deploying StartingBlocks, [please reach out to our team using this form.](https://support.startingblocks.org/) - -## Deployment Steps -Steps to deploy StartingBlocks templates. - -1. In your AWS account, create an S3 bucket that will be used to hold the StartingBlocks templates. The StartingBlocks team generally creates a new S3 bucket per environment deployed to ensure that we have some type of version control between environments if different Ed-Fi API/ODS versions are used year over year. S3 bucket names must be globally unique, i.e. you cannot have the same S3 bucket names in 2 different AWS accounts. - - An example bucket name - `{orgname}-{environment label}-{version}-cloudformation` - `EducationAnalytics-Prod2425-7.1-cloudformation` -2. Clone the StartingBlocks OSS repository to your local work machine and checkout the relevant version branch. - - StartingBlocks versioning aligns with versioning as its published by the Ed-Fi Alliance. e.g. StartingBlocksv7.1 deploys Ed-Fi APIv7.1 -3. Upload the contents of the repository to your S3 bucket location. You can do this via AWS CLI or dragging the folders from your file explorer to the S3 console for your bucket. -4. Copy the S3 URL for the `templates > 1-StartingBlocks-Main-Template.yml` file. -5. Navigate to the CloudFormation service in AWS. Create a new stack with new resources. -6. Select `Template is ready > Amazon S3 URL` and paste your copied URL for the `1-StartingBlocks-Main-Template.yml` file into the field. -7. Enter stack parameter values. [Please read the doc here for more information on parameter values.](sbe-parameter-values.md) -8. Navigate through the various screens until you are able to start the deployment. - -## StartingBlocks Environment Configuration - -After infrastructure is deployed, there is some environment configuration that will need to happen before the Ed-Fi API/ODS is ready for usage. Below are some high level steps that administrators can take to ensure the environment is configured correctly. All management functions are meant to be called per `Tenant`, as such it might be a good idea to create tooling and scripts as wrappers to interact with the management functions. e.g. If your environment has multiple tenants, scripting the invocation of the AWS Lambda functions to happen in a loop might be helpful to expedite configuration. - -After a successful CloudFormation deployment of the StartingBlocks stack: -1. Use the [TenantManagement](./sbe-functions.md#variable-requirements) function to create Ed-Fi Tenants. -2. Use the [ODSManagement](./sbe-functions.md#variable-requirements-1) function to create ODSs within Tenants. -3. Use the [EdOrgManagement](./sbe-functions.md#variable-requirements-2) function to create preliminary EdOrg records within ODSs. - - The EdOrg records created using this function are meant to be updated with full information via integrating systems. It is not meant to create comprehensive records, but rather populate enough information to create API credentials. -4. Use the [TenantManagement](./sbe-functions.md#keygen) KeyGen action to create AdminAPI credentials for a given Tenant. -5. Use the AdminAPI to create application (Ed-Fi API) credentials for the Tenants. - - [Please read the AdminAPI documentation published by the Alliance for detailed information on usage.](https://edfi.atlassian.net/wiki/spaces/ADMINAPI/pages/21300700/Technical+Information) \ No newline at end of file diff --git a/docs/sbe-functions.md b/docs/sbe-functions.md index 0147863..a7f5449 100644 --- a/docs/sbe-functions.md +++ b/docs/sbe-functions.md @@ -64,7 +64,7 @@ Example output: ### Remove -This action will remove entry tied to the given Tenant from the DynamoDB tables and then delete the Admin and Security databases. +This action will remove the entry tied to the given Tenant from the DynamoDB tables and then delete the Admin and Security databases. Example input: @@ -153,9 +153,15 @@ Example input: Example output: + { + + "statusCode": 200, + "body": "Command Pending" + + } ### Keygen -This action generates the keys for use with the AdminAPI. The generated keys have been loaded into the AdminAPI database are ready to use. +This action generates the keys for use with the AdminAPI. The generated keys have been loaded into the AdminAPI database and are ready to use. --- @@ -199,7 +205,7 @@ Example output:
ODSManagement -`ODSManagement` is a lambda function that is deployed via CloudFormation within a Startingblocks v7+ environment. It takes input in the form of JSON with expected elements and will output both print statements and return of JSON with a status code. +`ODSManagement` is a lambda function that is deployed via CloudFormation within a StartingBlocks v7.x environment. It takes input in the form of JSON with expected elements and will output both print statements and return of JSON with a status code. This function allows admins of the environment a way to add and remove ODSs from specific tenants within the environment. This function interacts directly with the DB instance. @@ -245,7 +251,7 @@ This is an optional list of edorgs that limit which edorgs are picked up by the ### Add -This action first checks for an existing ODS by the given information concatenated into the correct naming scheme. [Example below: ods_examplename_prod] Assuming one does not exist it will clone a given template into a new ODS and name the resulting ODS with the correct naming scheme within a Startingblocks 7.0+ environment. A connection string will be generated and encrypted and the linking information will be added to the odsinstances table inside the Admin database of the parent tenant. +This action first checks for an existing ODS by the given information concatenated into the correct naming scheme. [Example below: ods_examplename_prod] Assuming one does not exist it will clone a given template into a new ODS and name the resulting ODS with the correct naming scheme within a StartingBlocks v7.x environment. A connection string will be generated and encrypted and the linking information will be added to the odsinstances table inside the Admin database of the parent tenant. Example input: @@ -345,7 +351,7 @@ Example output:
EdOrgManagement -Ed Org Management is a lambda function that is deployed via Cloud Formation within a StartingBlocks v7+ environment. It takes input in the form of JSON with expected elements and will output both print statements and return of JSON with a status code. +Ed Org Management is a lambda function that is deployed via Cloud Formation within a StartingBlocks v7.x environment. It takes input in the form of JSON with expected elements and will output both print statements and return of JSON with a status code. This function allows admins to add and remove edorg records for environments that require some pre-seeding of edorgs in order to generate application credentials. The function interacts directly with the DB instance. diff --git a/docs/sbe-parameter-values.md b/docs/sbe-parameter-values.md index 83518dc..7e40910 100644 --- a/docs/sbe-parameter-values.md +++ b/docs/sbe-parameter-values.md @@ -3,42 +3,67 @@ There are a large amount of parameter values needed in order to deploy a properl ## Parameters - Stack name - Name your stack something unique and related to your environment. + +#### General Information - EnvLabel - Unique name for your environment. Generally will align with the `Stack name` and be included in your S3 source bucket name. -- S3SourceBucket - The name of the S3 bucket in which StartingBlocks templates are sourced. This is the bucket you created in [Step 1](sbe-deployment-steps.md#deployment-steps) of the deployment steps. - HostedZoneId - The route 53 Hosted Zone that was created as a prerequisite to deploying StaritingBlocks. - DomainName - Fully qualified domain name to create wild card certificate for. StartingBlocks uses a wildcard certificate in order to better track api clients in logs. - Partner - The StartingBlocks product team uses this parameter to keep track of partner deployments. It is used to generate unique resource names for deployed resources. -- WebApiZipFile - To use the default Api build as offered through the OSS repository, leave value as `default`. If you have a custom build of the Ed-Fi Api, construct the application zip file, and drop it in the `docker` folder in the S3 bucket created in `Step 1`. Place the name of the custom build zip file in this field if you want to deploy the custom Api. -- DatabaseArtifact - To use the default database artifacts as offered through the OSS repository, leave the value as `default`. If you have custom database artifacts, upload custom artifacts to the `database` folder in the S3 bucket created in `Step 1`. Place the unique identifier for the database artifacts here. ---- -**NOTE:** +#### Versions and Features +- EdFiApiVersion - The version of the Ed-Fi API to be deployed. +- DataStandardVersion - The version of the Ed-Fi Data Standard to be deployed. Note that not all data standard versions are supported by all Ed-Fi Api versions. + | EdFiApiVersion | Supported DataStandardVersion | + | -------------- | ----------------------------- | + | 7.1 | DS4, DS5 | + | 7.2 | DS4, DS5.1 | + | 7.3 | DS4, DS5.2 | +- PostgresVersion - Choose the version of Postgres to use for Aurora RDS. These versions have been tested and used by the StartingBlocks team. Versions outside of this list have not been tested. +- AdminInterface - Currently only two options are available. `Ed-Fi Admin Api` or `None`. +- DeploySwagger - Optionally will deploy a swagger site for the StartingBlocks environment. +- APIPublisher - If users intend for the environment to be used for publishing with the API publisher, set this to `true` to support snapshots with Aurora clones. -Each database artifact in the `database` folder is prefixed with which database it is. i.e. EdFi_Ods_Minimal/Populated_Template for the ODS, EdFi_Security for the security database, and EdFi_Admin for the admin database. If uploading a custom build, the prefixes must stay the same, but the suffix can change, and that is what should be used as the `DatabaseArtifact` parameter value if using custom database builds. i.e. EdFi_Ods_Minimal_Template_71_patch2_20240505.sql. The value to be populated for `DatabaseArtifact` would be `71_patch2_20240505`. Note that users must have the same suffix for all database artifacts, i.e. the ods, the security and admin databases that are to be deployed must all use the same suffix. +#### Deployment Files +- S3SourceBucket - The name of the S3 bucket in which StartingBlocks templates are sourced. This is the bucket you created in [Step 1](../README.md#deployment-steps) of the deployment steps. +- ArtifactsS3SourceBucket - Determines if the deployment should use EA's central artifact store (available in all US regions), or look for custom artifacts in the cloudformation bucket named above. +- WebApiZipFile - To use the default Api build as offered through EA's central artifact store, leave value as `default`. If you have a custom build of the Ed-Fi Api, construct the application zip file, and drop it in the `docker` folder in the S3 bucket created in [Step 1](../README.md#deployment-steps). Place the name of the custom build zip file in this field if you want to deploy the custom Api. +- DatabaseArtifact - To use the default database artifacts as offered through EA's central artifact store, leave the value as `default`. If you have custom database artifacts, upload custom artifacts to the `database` folder in the S3 bucket created in [Step 1](../README.md#deployment-steps). Place the unique identifier for the database artifacts here. ---- -- EnvironmentSize - Choose the size of your resources. This parameter informs both the EC2 instance types deployed in the AutoScalig group and the database instance type for RDS. -- PostgresVersion - Choose the version of Postgres to use for Aurora RDS. These versions have been tested and used by the StartingBlocks team. Versions outside of this list have not been tested. -- DatabaseType - Provisioned or Serverless Aurora. Serverless will provide real time scaling, but costs more to host. -- DeployReplica - Will deploy a read replica in RDS. If users anticipate a large amount of reads on the system, it might be advisable to deploy a read replica. +>[!NOTE] +>Each database artifact in the `database` folder is prefixed with which database it is. i.e. EdFi_Ods_Minimal_Template and EdFi Ods_Populated_Template for the ODS, EdFi_Security for the security database, and EdFi_Admin for the admin database. If uploading a custom build, the prefixes must stay the same, but the suffix can change, and that is what should be used as the `DatabaseArtifact` parameter value if using custom database builds. i.e. EdFi_Ods_Minimal_Template_71_patch2_20240505.sql. The value to be populated for `DatabaseArtifact` would be `71_patch2_20240505`. Note that users must have the same suffix for all database artifacts, i.e. the ods, the security and admin databases that are to be deployed must all use the same suffix. + +#### Ed-Fi Options - EdFiTenancyMode - Defines configuration for Ed-Fi API/ODS. `SingleTenant` or `MultiTenant`. -- DatabaseData - Minimal or Populated. Minimal deploys an ODS with only the Ed-Fi default descriptors and nothing else. Populated deploys an ODS with the Grand Bend test data set. Minimal is what should be used for production deployments. -- DataStandardVersion - There are 2 options for 7.1 `DS5` or `DS4`. This parameter will inform which default artifacts are deployed by StartingBlocks. -- APIPublisher - If users intend for the environment to be used for publishing with the API publisher, set this to `true` else `false`. -- AdminInterface - Users of StartingBlocks OSS will set this to `Ed-Fi Admin Api` or `None`. -- AdminApiCIDRs - List of CIDRs to allow access to call Admin API. It's advisable to add the CIDR for a user's office or locations where administration of the environment will need to be acccessible. -- AdminAccountIds - StartingBlocks utilizes many management functions deployed as AWS Lambda functions. If users would like to reach these functions from another AWS account, users can input their AWS account IDs here. This will allow the management Lambda functions to be called cross account. +- DatabaseData - Minimal or Populated. Minimal deploys an ODS with only the Ed-Fi default descriptors and nothing else. Populated deploys an ODS with the Grand Bend test data set. Minimal is what should be used for production deployments. This parameter only affects SingleTenant mode deployments. In SingleTenat deployments a default ODS is created as part of the CloudFormation process, and this parameters selects the template to use. In MultiTenant deployments both templates are imported and available to create ODSs later as needed. +- BearerTokenPerClientLimit - Introduced in Ed-Fi 7.3, this setting controls the maxium number of tokens that can be issued per client, within the span of a token lifetime. + +#### Capacity and Scaling +- EnvironmentSize - Choose the size of your resources. This parameter informs both the EC2 instance types deployed in the Auto Scaling Group and the database instance type for RDS. - WebAPIMinInstances - The minimum number of EC2 instances managed by the Auto Scaling Group. - WebAPIMaxInstances - The maximum number of EC2 instances managed by the Auto Scaling Group. -- SpotFleetPercentage - The percentage of On-Demand Instances as part of additional capacity that your Auto Scaling group provisions. +- SpotFleetPercentage - The percentage of On-Demand Instances as part of additional capacity that your Auto Scaling Group provisions. +- DatabaseType - Provisioned or Serverless Aurora. Serverless will provide real time scaling, but costs more to host. +- DeployReplica - Will deploy a read replica in RDS. If users anticipate a large amount of reads on the system, it might be advisable to deploy a read replica. - WebAPIMaxPoolSize - Maximun pool size for connections to the Ed-Fi ODS database. This is per database. - WebAPIConnectionIdleLifetime - Connection idle lifetime for connections to the Ed-Fi ODS database. -- WebACLArn - Optionally attach a WAF (deployed separately) via ARN to the API load balancer. + +#### Security Options - SSLPolicy - Policy to determine which ssl/tls ciphers are accepted. -- SNSTopicArn - Optionally add ARN of SNS topic to publish Route53 HealthCheck Alarms. SNS topic is deployed separately. -- DeploySwagger - Optionally will deploy a swagger site for the StartingBlocks environment. -- SlackWebhookUrl - Optionally add a slack webhook URL to surface alerts from beanstalk and rds environments. +- AdminApiCIDRs - List of CIDRs to allow access to call Admin API. It's advisable to add the CIDR for a user's office or locations where administration of the environment will need to be acccessible. +- AdminAccountIds - StartingBlocks utilizes many management functions deployed as AWS Lambda functions. If users would like to reach these functions from another AWS account, users can input their AWS account IDs here. This will allow the management Lambda functions to be called cross account. +- WebACLArn - Optionally attach a WAF (deployed separately) via ARN to the API load balancer. - SSHServerParentStack - Optionally associate SSH server to StartingBlocks resources. SSH server is deployed separately. + +#### Monitoring and Alerting +- S3AccessLogBucket - Optionally store Elastic Load Balancer access logs in the S3 bucket named here. +- SNSTopicArn - Optionally add ARN of SNS topic to publish Route53 HealthCheck Alarms. SNS topic is deployed separately. +- SlackWebhookUrl - Optionally add a slack webhook URL to surface alerts from Beanstalk and RDS environments. + +#### Maintenance Options +- DeploymentStrategy - RollingWithAdditionalBatch will deploy new code and platform updates by adding new servers one at a time. This option avoids downtime and is recommended for production deployments. AllAtOnce will deploy new code and platform updates to every server at the same time. This option is faster but the environment will be unavailable until the deployment is complete. +- BeanstalkPlatformUpdateTime - If provided, this will determine the day of week and time of day that Beanstalk will automatically update the platform version. Updating the platform version is important to ensure the system is running with the latest OS patches. + +#### VPC Network Configuration - VpcId - The VPC ID for where to deploy StartingBlocks resources. Must be deployed before StartingBlocks. - PublicSubnet1Id - The Subnet ID of a Public Subnet in one of the Availability Zones. Must be configured before StartingBlocks deployment. - PublicSubnet2Id - The Subnet ID of a Public Subnet in one of the Availability Zones. Must be configured before StartingBlocks deployment. diff --git a/templates/.checkov.yaml b/templates/.checkov.yaml new file mode 100644 index 0000000..86e1351 --- /dev/null +++ b/templates/.checkov.yaml @@ -0,0 +1,2 @@ +framework: + - cloudformation \ No newline at end of file diff --git a/templates/1-StartingBlocks-Main-Template.yml b/templates/1-StartingBlocks-Main-Template.yml index b32abe5..8704a1c 100644 --- a/templates/1-StartingBlocks-Main-Template.yml +++ b/templates/1-StartingBlocks-Main-Template.yml @@ -8,46 +8,66 @@ Metadata: AWS::CloudFormation::Interface: ParameterGroups: - Label: - default: Template information + default: General Information Parameters: - EnvLabel - - S3SourceBucket - HostedZoneId - DomainName - Partner - Label: - default: EdFi ODS API Environment Information + default: Versions and Features Parameters: + - EdFiApiVersion + - DataStandardVersion + - PostgresVersion + - AdminInterface + - DeploySwagger + - APIPublisher + - Label: + default: Deployment Files + Parameters: + - S3SourceBucket + - ArtifactsS3SourceBucket - WebApiZipFile - DatabaseArtifact - - EnvironmentSize - - PostgresVersion - - DatabaseType - - DeployReplica + - Label: + default: Ed-Fi Options + Parameters: - EdFiTenancyMode - DatabaseData - - DataStandardVersion - - APIPublisher - - AdminInterface - - AdminApiCIDRs - - AdminAccountIds - - DeploymentStrategy + - BearerTokenPerClientLimit + - Label: + default: Capacity and Scaling + Parameters: + - EnvironmentSize - WebAPIMinInstances - WebAPIMaxInstances - SpotFleetPercentage + - DatabaseType + - DeployReplica - WebAPIMaxPoolSize - WebAPIConnectionIdleLifetime - - WebACLArn + - Label: + default: Security Options + Parameters: - SSLPolicy + - AdminApiCIDRs + - AdminAccountIds + - WebACLArn + - SSHServerParentStack + - Label: + default: Monitoring and Alerting + Parameters: + - S3AccessLogBucket - SNSTopicArn - - DeploySwagger - SlackWebhookUrl - Label: - default: SSH Server Options + default: Maintenance Options Parameters: - - SSHServerParentStack + - DeploymentStrategy + - BeanstalkPlatformUpdateTime - Label: - default: Provide Your Own Existing VPC Network Configuration + default: VPC Network Configuration Parameters: - VpcId - PublicSubnet1Id @@ -58,6 +78,8 @@ Metadata: ParameterLabels: S3SourceBucket: default: S3 Bucket name containing this template + ArtifactsS3SourceBucket: + default: Artifact source VpcId: default: VPC ID PublicSubnet1Id: @@ -75,9 +97,9 @@ Metadata: WebAPIMaxInstances: default: Maximum Web API Instances SpotFleetPercentage: - default: The percentage of On-Demand Instances. + default: Percentage of On-Demand Instances WebAPIMaxPoolSize: - default: Maximun Pool Size per Application Server + default: Database connection pool size WebAPIConnectionIdleLifetime: default: Connection Idle Lifetime WebACLArn: @@ -86,14 +108,16 @@ Metadata: default: Initial ODS API Database Data Set DataStandardVersion: default: Data Standard Version + EdFiApiVersion: + default: Ed-Fi API Version DeployReplica: default: Deploy Read Replica PostgresVersion: default: PostgreSQL Version EdFiTenancyMode: - default: EdFi Tenancy Mode + default: Ed-Fi Tenancy Mode WebApiZipFile: - default: Name of the zip file containing the EdFi WebApi packaged for Elastic Beanstalk + default: Elastic Beanstalk zip file for Ed-Fi WebApi DatabaseArtifact: default: Database Artifact EnvironmentSize: @@ -101,35 +125,47 @@ Metadata: EnvLabel: default: Label your environment SSHServerParentStack: - default: Existing SSH server stack (optional) + default: SSH server stack DatabaseType: - default: The database type for your environment + default: Aurora compute capacity mode APIPublisher: default: API Publisher Support AdminApiCIDRs: - default: Network Address ranges (CIDR) to allow connections to the Admin Api + default: Admin Api allowed CIDR ranges AdminAccountIds: - default: AWS Account ID's to allow to invoke admin Lambda functions + default: AWS Account ID's allowed to invoke admin Lambda functions AdminInterface: default: Admin Interface DomainName: - default: Domain Name to generate a wildcard SSL certificate for. DO NOT include '*.' + default: Domain Name HostedZoneId: - default: Route53 zone to create the records in + default: Route53 zone Partner: - default: Partner name for which environment will be deployed. + default: Partner name SSLPolicy: - default: SSL Policy for ALB. + default: SSL Policy SNSTopicArn: - default: Centralized SNS topic + default: SNS topic DeploySwagger: default: Deploy Swagger UI SlackWebhookUrl: - default: The URL of the Slack webhook to send notifications to. + default: Slack webhook URL + S3AccessLogBucket: + default: S3 bucket for ELB Access Logs + BeanstalkPlatformUpdateTime: + default: Elastic Beanstalk automatic platform update schedule + BearerTokenPerClientLimit: + default: Bearer Token Per Client Limit Parameters: S3SourceBucket: Type: String - Description: This bucket contains the source files for CF, Lambda, Docker, etc. MUST be in the same region as the CF Stack. + Description: This bucket contains the source files for CloudFormation and Lambda layers. MUST be in the same region as the CF Stack. + ArtifactsS3SourceBucket: + Type: String + Description: EA provides a central artifact store for standard Ed-Fi deployments. If you use extensions or wish to customize the deployment in another way, you must place your artifacts in the database/ and docker/ folders in the S3 bucket named above. + AllowedValues: + - Use artifacts from EA's central artifact store + - Use my own custom artifacts in the CloudFormation bucket EnvironmentSize: AllowedValues: - small @@ -137,19 +173,21 @@ Parameters: - large - xlarge - 2xlarge + - 4xlarge + - 8xlarge Default: small Description: Select the size of the environment to launch. Smaller is cheaper but less performant. Type: String WebApiZipFile: Default: 'default' - Description: If using a custom Ed-Fi build, provide the zip file name here. Use 'default' to deploy stock Ed-Fi build for the chosen DataStandard version. + Description: If using a custom Ed-Fi build, provide the zip file name here. Use 'default' to deploy the stock Ed-Fi build for the chosen Ed-Fi API and DataStandard versions. Type: String DatabaseArtifact: Default: 'default' - Description: If using custom databases, provide the database artifact name here. Use 'default' to deploy stock Ed-Fi databases for the chosen DataStandard version. + Description: If using custom databases, provide the database artifact name here. Use 'default' to deploy stock Ed-Fi databases for the chosen Ed-Fi API and DataStandard versions. Do not change this parameter after initial stack deployment. Type: String EnvLabel: - Description: Provide a unique label for your environment to identify resources. You cannot use an label name that is already in use. + Description: Provide a unique label for your environment to identify resources. You cannot use a label that is already in use. Type: String DomainName: Description: The fully qualified domain name to be used for this ODS @@ -179,22 +217,22 @@ Parameters: Description: The Subnet ID of a Private Subnet in the other Availability Zone above ConstraintDescription: You must select an existing Private Subnet ID in the VPC selected for this deployment SSHServerParentStack: - Description: Stack name for the existing SSH server parent stack + Description: Stack name for the existing SSH server parent stack based on EA's standard SSH server template. (optional) Type: String APIPublisher: AllowedValues: - 'true' - 'false' Default: 'false' - Description: Would you like to deploy resources to support API Publisher? + Description: Deploys resources to enable the 'snapshot' ODS derivative type, and fix ChangeVersions in cloned databases. Type: String AdminApiCIDRs: - AllowedPattern: ^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/([0-9]|[1-2][0-9]|3[0-2]))(,(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/([0-9]|[1-2][0-9]|3[0-2])))*$ + AllowedPattern: '^$|^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/([0-9]|[1-2][0-9]|3[0-2]))(,(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/([0-9]|[1-2][0-9]|3[0-2])))*$' Description: | For security reasons we recommend that you only enable trusted IPs for external access to the Admin Api. This should be a comma separated list of trusted networks in CIDR notation (X.X.X.X/X). The StartingBlocks Admin App IP's, and the Education Analytics office IP range are as follows. - (18.119.27.161/32,3.135.55.248/32,172.102.6.120/29) + (18.119.27.161/32,3.135.55.248/32,172.102.6.120/29). (optional) Type: String AdminInterface: Description: The interface that will provide management functions for this environment. @@ -206,32 +244,34 @@ Parameters: AdminAccountIds: Description: | When using StartingBlocks Admin App, these (comma separated) AWS Account ID's will be given permission to invoke the management Lambda Functions. - Account ID 179685256884 will allow the Education Analytics StartingBlocks Admin App. + Account ID 179685256884 will allow the Education Analytics StartingBlocks Admin App. (optional) Type: String - AllowedPattern: "^[0-9]{12}(,[0-9]{12})*$" + AllowedPattern: '^$|^[0-9]{12}(,[0-9]{12})*$' EdFiTenancyMode: - Description: The tenancy mode that Ed-Fi will run in. + Description: The tenancy mode that Ed-Fi will run in. Note that 'SingleTenant' is not compatible with StartingBlocks Admin App. AllowedValues: - 'SingleTenant' - 'MultiTenant' Default: 'MultiTenant' Type: String PostgresVersion: - Description: Ed-Fi Alliance officially supports Postgres 13 with Ed-Fi 7.x + Description: Ed-Fi Alliance officially supports Postgres 16 with Ed-Fi 7.x Type: String AllowedValues: - - 'Thirteen' - - 'Fourteen' - 'Fifteen' - Default: 'Fifteen' + - 'Sixteen' + Default: 'Sixteen' DatabaseType: - Description: Aurora compute capacity mode. + Description: | + Provisioned capacity provides a database server of fixed size, and does not auto-scale. Provisioned capacity is cheaper for development environments, and may be eligible for some AWS savings plans. + Serverless mode allows for rapid auto scaling of the database compute. Serverless mode is often more expensive, but can provide improved performance in unpredictable production environments. Type: String Default: "Provisioned" AllowedValues: - "Provisioned" - "Serverless" DatabaseData: + Description: For SingleTenant deployments, pick the template that will be used to create the default ODS. For MultiTenant deployments, a default ODS is not created in CloudFormation and both templates are imported for later use. Type: String Default: minimal AllowedValues: @@ -244,46 +284,63 @@ Parameters: AllowedValues: - DS4 - DS5 + - DS5.1 + - DS5.2 + EdFiApiVersion: + Type: String + Description: Which version of the Ed-Fi API should be deployed. This can NOT be changed later. + Default: "7.1" + AllowedValues: + - "7.1" + - "7.2" + - "7.3" DeployReplica: AllowedValues: - 'true' - 'false' Default: 'false' - Description: Would you like your database to use a read replica? This will increase the cost of the environment, but imporve performance and provide for an immediate failover database. + Description: This will double the cost of the database, but should improve read performance and provide faster failover. Type: String DeploymentStrategy: Type: String - Description: Beanstalk deployment strategy for platform updates and application versions. + Description: | + RollingWithAdditionalBatch will deploy new code and platform updates by adding new servers one at a time. This option avoids downtime and is recommended for production deployments. + AllAtOnce will deploy new code and platform updates to every server at the same time. This option is faster but the environment will be unavailable until the deployment is complete. AllowedValues: - 'RollingWithAdditionalBatch' - 'AllAtOnce' Default: 'RollingWithAdditionalBatch' + BeanstalkPlatformUpdateTime: + Description: Specify the update day and time in UTC using the format 'Day:HH:MM' (e.g., Wed:15:30). Platform updates will be disabled if no value is specified. (optional) + Type: String + AllowedPattern: '^$|^(Mon|Tue|Wed|Thu|Fri|Sat|Sun):(0[0-9]|1[0-9]|2[0-3]):[0-5][0-9]$' + Default: "Wed:15:30" WebAPIMinInstances: Type: Number - Description: Minimum Application Server Capacity for Web API Stack. + Description: Minimum number of Ed-Fi Web API servers. There will always be this number of servers available. Default: 1 WebAPIMaxInstances: Type: Number - Description: Maximum Application Server Capacity for Web API Stack. + Description: Maximum number of Ed-Fi Web API servers. The environment will scale up 1 instance at a time up to this limit. Default: 3 SpotFleetPercentage: Type: Number - Description: The percentage of On-Demand Instances as part of additional capacity that your Auto Scaling group provisions beyond the SpotOnDemandBase instances. + Description: While scaling up, the next 2 Ed-Fi Web API servers will be On-Demand. After that, this percentage will also be On-Demand and the remaining will be Spot Instances. Default: 70 WebAPIMaxPoolSize: Type: Number - Description: Maximun pool size for connections to the Ed-Fi ODS database + Description: Each Ed-Fi Web Api server will be able to make this many connections to each ODS database. Default: 200 WebAPIConnectionIdleLifetime: Type: Number - Description: Connection idle lifetime for connections to the Ed-Fi ODS database + Description: Connection idle lifetime (seconds) for connections to the Ed-Fi ODS database. Default: 120 WebACLArn: Type: String Description: Provide the ARN of a WAF to attach to the Web API (optional) Partner: Type: String - Description: Enter partner for which Starting Blocks environment will be deployed. + Description: Enter the acronym for the organization using this environment. Used for additional uniqueness in S3 bucket names, and additional context in alerts. Default: 'ea' SSLPolicy: Description: Policy to determine which ssl/tls ciphers are accepted. Aeries Districts use ELBSecurityPolicy-TLS13-1-2-2021-06. @@ -297,7 +354,7 @@ Parameters: Type: String DeploySwagger: Type: String - Description: Will deploy Swagger UI to swagger-ui.{DomainName} + Description: If enabled, Swagger UI will be deployed to swagger-ui.{DomainName} AllowedValues: - 'True' - 'False' @@ -305,12 +362,20 @@ Parameters: SlackWebhookUrl: Type: String Description: The URL of the Slack webhook to send notifications to (optional). + S3AccessLogBucket: + Type: String + Description: Name of S3 bucket to store access logs for ELB. Must be in the same region as the CF Stack. (optional) + BearerTokenPerClientLimit: + Type: Number + Description: The maximum number of bearer tokens that can be issued to a single client in 30 minutes. (Version 7.3+) + Default: 15 Mappings: PostgreSQLInformation: EngineVersion: - "Thirteen": "13.12" - "Fourteen": "14.9" - "Fifteen": "15.3" + "Thirteen": "13.18" + "Fourteen": "14.15" + "Fifteen": "15.10" + "Sixteen": "16.6" EngineEdition: "Standard": "postgres" AccountInfo: @@ -321,36 +386,47 @@ Mappings: "large": "db.r6g.large" "xlarge": "db.r6g.xlarge" "2xlarge": "db.r6g.2xlarge" + "4xlarge": "db.r7g.4xlarge" + "8xlarge": "db.r7g.8xlarge" MinServerlessCapacity: "small": 0.5 "medium": 1 "large": 2 "xlarge": 4 "2xlarge": 8 + "4xlarge": 16 + "8xlarge": 32 MaxServerlessCapacity: - "small": 2 - "medium": 4 - "large": 8 - "xlarge": 16 - "2xlarge": 32 + "small": 3 + "medium": 6 + "large": 12 + "xlarge": 24 + "2xlarge": 48 + "4xlarge": 96 + "8xlarge": 192 AppServerSize: "small": "t3a.medium,t3.medium" "medium": "t3.large,t3a.large" "large": "t3.large,t3a.large" - "xlarge": "m6i.large,m6a.large" - "2xlarge": "m6i.xlarge,m6a.xlarge" + "xlarge": "m7a.large,m7i.large,m6a.large,m6i.large" + "2xlarge": "c7a.xlarge,m7a.xlarge,c7i.xlarge,m7i.xlarge" + "4xlarge": "c7a.2xlarge,m7a.2xlarge,c7i.2xlarge,m7i.2xlarge" + "8xlarge": "c7a.2xlarge,m7a.2xlarge,c7i.2xlarge,m7i.2xlarge" AdminServerSize: "small": "t3a.small,t3.small" "medium": "t3a.small,t3.small" "large": "t3a.small,t3.small" "xlarge": "t3a.medium,t3.medium" "2xlarge": "t3a.medium,t3.medium" - SSHServerSize: - "small": "t4g.micro" - "medium": "t4g.micro" - "large": "t4g.small" - "xlarge": "t4g.small" - "2xlarge": "t4g.small" + "4xlarge": "t3a.medium,t3.medium" + "8xlarge": "t3a.medium,t3.medium" + EdFiApiVersionPatch: + "7.1": + "Patch": "6238" # Patch 4 + "7.2": + "Patch": "6237" # Patch 2 + "7.3": + "Patch": "6252" # Patch 1 Conditions: UsePopulatedData: !Equals - !Ref 'DatabaseData' @@ -369,6 +445,9 @@ Conditions: - !Ref 'DeploySwagger' - 'True' EnableSlackNotifications: !Not [!Equals [!Ref SlackWebhookUrl, '']] + UseDefaultArtifactsS3Bucket: !Equals + - !Ref ArtifactsS3SourceBucket + - "Use artifacts from EA's central artifact store" Rules: SubnetsInVPC: Assertions: @@ -378,11 +457,32 @@ Rules: - VpcId - !RefAll 'AWS::EC2::VPC::Id' AssertDescription: All subnets provided must be in the VPC selected for use + EdFiApiVersionToDataStandard: + Assertions: + - Assert: !Or + - !And + - !Equals [!Ref EdFiApiVersion, "7.1"] + - !Or + - !Equals [!Ref DataStandardVersion, "DS4"] + - !Equals [!Ref DataStandardVersion, "DS5"] + - !And + - !Equals [!Ref EdFiApiVersion, "7.2"] + - !Or + - !Equals [!Ref DataStandardVersion, "DS4"] + - !Equals [!Ref DataStandardVersion, "DS5.1"] + - !And + - !Equals [!Ref EdFiApiVersion, "7.3"] + - !Or + - !Equals [!Ref DataStandardVersion, "DS4"] + - !Equals [!Ref DataStandardVersion, "DS5.2"] + AssertDescription: DataStandardVersion must be compatible with Ed-Fi API version selected. Resources: # Shared Resources stack deploys IAM, KMS, Security Groups, Secrets, Certificate, ALB, DynamoDB and Route53 resources SharedResourcesStack: Type: AWS::CloudFormation::Stack + DeletionPolicy: Delete + UpdateReplacePolicy: Delete Properties: TemplateURL: !Sub 'https://${S3SourceBucket}.s3.amazonaws.com/templates/x-shared-resources.yml' Parameters: @@ -397,15 +497,22 @@ Resources: DomainName: !Ref 'DomainName' HostedZoneId: !Ref 'HostedZoneId' SSLPolicy: !Ref SSLPolicy + S3AccessLogBucket: !Ref S3AccessLogBucket LambdaCoreStack: Type: AWS::CloudFormation::Stack + DeletionPolicy: Delete + UpdateReplacePolicy: Delete Properties: TemplateURL: !Sub 'https://${S3SourceBucket}.s3.amazonaws.com/templates/x-lambda-core-functions.yml' Parameters: AdminInterface: !Ref AdminInterface AdminAccountIds: !Ref AdminAccountIds S3BucketSourceCode: !Ref 'S3SourceBucket' + ArtifactsS3SourceBucket: !If + - UseDefaultArtifactsS3Bucket + - !Sub 'startingblocks-edfi-artifacts-${AWS::Region}' + - !Ref 'S3SourceBucket' S3KeySourceCode: lambdas PrivateSubnet1Id: !Ref 'PrivateSubnet1Id' PrivateSubnet2Id: !Ref 'PrivateSubnet2Id' @@ -422,9 +529,12 @@ Resources: APIPublisher: !Ref APIPublisher SNSTopicArn: !Ref SNSTopicArn SlackWebhookUrl: !Ref SlackWebhookUrl - + KmsKeyId: !GetAtt 'SharedResourcesStack.Outputs.KeyId' + DatabaseStack: Type: AWS::CloudFormation::Stack + DeletionPolicy: Delete + UpdateReplacePolicy: Delete Properties: TemplateURL: !Sub 'https://${S3SourceBucket}.s3.amazonaws.com/templates/x-aurora-postgres-db.yml' Parameters: @@ -463,9 +573,11 @@ Resources: CWRetentionLambdaArn: !GetAtt 'LambdaCoreStack.Outputs.SetCloudWatchRetentionFunctionArn' LambdaRDSRestoreStack: + Type: AWS::CloudFormation::Stack + DeletionPolicy: Delete + UpdateReplacePolicy: Delete DependsOn: - DatabaseStack - Type: AWS::CloudFormation::Stack Properties: TemplateURL: !Sub 'https://${S3SourceBucket}.s3.amazonaws.com/templates/x-restore-databases.yml' Parameters: @@ -473,24 +585,27 @@ Resources: RestoreMinimalDB: !If [UsePopulatedData, 'no', 'yes'] RestorePopulatedDB: !If [UsePopulatedData, 'yes', 'no'] EdFiTenancyMode: !Ref EdFiTenancyMode - S3SourceBucket: !Ref 'S3SourceBucket' + S3SourceBucket: !If + - UseDefaultArtifactsS3Bucket + - !Sub 'startingblocks-edfi-artifacts-${AWS::Region}' + - !Ref 'S3SourceBucket' S3SourceKeyName: 'database' AdminDbS3Name: !If - UseDefaultDatabase - - !Sub 'EdFi_Admin_7.1_${DataStandardVersion}' + - !Sub 'EdFi_Admin_${EdFiApiVersion}_${DataStandardVersion}' - !Sub 'EdFi_Admin_${DatabaseArtifact}' - AdminApiDbS3Name: 'EdFi_Admin_Api_2.0' + AdminApiDbS3Name: 'EdFi_Admin_Api_2.2' SecurityDbS3Name: !If - UseDefaultDatabase - - !Sub 'EdFi_Security_7.1_${DataStandardVersion}' + - !Sub 'EdFi_Security_${EdFiApiVersion}_${DataStandardVersion}' - !Sub 'EdFi_Security_${DatabaseArtifact}' MinimalDbS3Name: !If - UseDefaultDatabase - - !Sub 'EdFi_Ods_Minimal_Template_TPDM_Core_7.1_${DataStandardVersion}' + - !Sub 'EdFi_Ods_Minimal_Template_TPDM_Core_${EdFiApiVersion}_${DataStandardVersion}' - !Sub 'EdFi_Ods_Minimal_Template_${DatabaseArtifact}' PopulatedDbS3Name: !If - UseDefaultDatabase - - !Sub 'EdFi_Ods_Populated_Template_TPDM_Core_7.1_${DataStandardVersion}' + - !Sub 'EdFi_Ods_Populated_Template_TPDM_Core_${EdFiApiVersion}_${DataStandardVersion}' - !Sub 'EdFi_Ods_Populated_Template_${DatabaseArtifact}' LambdaFunctionName: !GetAtt 'LambdaCoreStack.Outputs.DatabaseRestoreLambdaFunctionName' DeployReplica: !Ref DeployReplica @@ -498,8 +613,10 @@ Resources: APIPublisher: !Ref APIPublisher WebApiBeanstalkStack: - DependsOn: LambdaRDSRestoreStack Type: AWS::CloudFormation::Stack + DeletionPolicy: Delete + UpdateReplacePolicy: Delete + DependsOn: LambdaRDSRestoreStack Properties: TemplateURL: !Sub 'https://${S3SourceBucket}.s3.amazonaws.com/templates/x-beanstalk-web-api.yml' Parameters: @@ -511,7 +628,10 @@ Resources: PrivateSubnet1Id: !Ref 'PrivateSubnet1Id' PrivateSubnet2Id: !Ref 'PrivateSubnet2Id' EC2SecurityGroup: !GetAtt 'SharedResourcesStack.Outputs.WebApiServerSecurityGroupID' - S3BucketName: !Ref 'S3SourceBucket' + S3BucketName: !If + - UseDefaultArtifactsS3Bucket + - !Sub 'startingblocks-edfi-artifacts-${AWS::Region}' + - !Ref 'S3SourceBucket' InstanceTypes: !FindInMap - PostgreSQLInformation - AppServerSize @@ -520,9 +640,11 @@ Resources: IamInstanceProfile: !GetAtt 'SharedResourcesStack.Outputs.ElasticBeanstalkEC2InstanceProfileName' RDSSecret: !GetAtt 'SharedResourcesStack.Outputs.AuroraMasterSecret' EdFiTenancyMode: !Ref 'EdFiTenancyMode' - WebApiZipFile: !If + WebApiZipFile: !If - UseDefaultWebApiZip - - !Sub 'WebApi-7.1.5125c-${DataStandardVersion}.zip' + - !Sub + - "WebApi-${EdFiApiVersion}.${PatchVersion}-${DataStandardVersion}.zip" + - { PatchVersion: !FindInMap [EdFiApiVersionPatch, !Ref EdFiApiVersion, Patch] } - !Ref 'WebApiZipFile' SnsToSlackLambdaArn: !If - EnableSlackNotifications @@ -532,6 +654,7 @@ Resources: DomainName: !Ref 'DomainName' AccessRestriction: '*.DomainName' DeploymentStrategy: !Ref DeploymentStrategy + BeanstalkPlatformUpdateTime: !Ref BeanstalkPlatformUpdateTime WebAPIMaxPoolSize: !Ref WebAPIMaxPoolSize WebAPIConnectionIdleLifetime: !Ref WebAPIConnectionIdleLifetime SharedAlbArn: !GetAtt 'SharedResourcesStack.Outputs.SharedAlbArn' @@ -540,11 +663,14 @@ Resources: East1AlarmLambdaArn: !If [EnableHealthCheck, !GetAtt 'LambdaCoreStack.Outputs.East1AlarmLambdaArn', ''] UploadAndDeployArn: !GetAtt 'LambdaCoreStack.Outputs.BeanstalkUploadAndDeployFunctionArn' Partner: !Ref 'Partner' + BearerTokenPerClientLimit: !Ref BearerTokenPerClientLimit AdminApiStack: + Type: AWS::CloudFormation::Stack + DeletionPolicy: Delete + UpdateReplacePolicy: Delete Condition: UseAdminApi DependsOn: LambdaRDSRestoreStack - Type: AWS::CloudFormation::Stack Properties: TemplateURL: !Sub 'https://${S3SourceBucket}.s3.amazonaws.com/templates/x-beanstalk-admin-api.yml' Parameters: @@ -556,19 +682,26 @@ Resources: - PostgreSQLInformation - AdminServerSize - !Ref EnvironmentSize + BeanstalkServiceRole: !GetAtt 'SharedResourcesStack.Outputs.ElasticBeanstalkServiceRoleName' EC2SecurityGroup: !GetAtt 'SharedResourcesStack.Outputs.AdminApiServerSecurityGroupID' IamInstanceProfile: !GetAtt 'SharedResourcesStack.Outputs.ElasticBeanstalkEC2InstanceProfileName' RDSSecret: !GetAtt 'SharedResourcesStack.Outputs.AuroraMasterSecret' DomainName: !Ref 'DomainName' - S3BucketName: !Ref 'S3SourceBucket' + S3BucketName: !If + - UseDefaultArtifactsS3Bucket + - !Sub 'startingblocks-edfi-artifacts-${AWS::Region}' + - !Ref 'S3SourceBucket' EdFiTenancyMode: !Ref 'EdFiTenancyMode' DeploymentStrategy: !Ref DeploymentStrategy + BeanstalkPlatformUpdateTime: !Ref BeanstalkPlatformUpdateTime UploadAndDeployArn: !GetAtt 'LambdaCoreStack.Outputs.BeanstalkUploadAndDeployFunctionArn' SharedAlbArn: !GetAtt 'SharedResourcesStack.Outputs.SharedAlbArn' SharedAlbAdminApiListenerArn: !GetAtt 'SharedResourcesStack.Outputs.SharedAlbAdminApiListenerArn' CloudWatchDashboardStack: Type: AWS::CloudFormation::Stack + DeletionPolicy: Delete + UpdateReplacePolicy: Delete Properties: TemplateURL: !Sub 'https://${S3SourceBucket}.s3.amazonaws.com/templates/x-cloudwatch-dashboard.yml' Parameters: @@ -581,8 +714,10 @@ Resources: TargetGroupArn: !GetAtt 'WebApiBeanstalkStack.Outputs.TargetGroupArn' SwaggerStack: - Condition: DeploySwaggerTemplate Type: AWS::CloudFormation::Stack + DeletionPolicy: Delete + UpdateReplacePolicy: Delete + Condition: DeploySwaggerTemplate Properties: TemplateURL: !Sub 'https://${S3SourceBucket}.s3.amazonaws.com/templates/x-swagger.yml' Parameters: @@ -593,6 +728,19 @@ Resources: SwaggerDefaultURL: !Sub 'https://swaggerui.${DomainName}/metadata/data/v3/resources/swagger.json' CRHelperLambdaLayer: !GetAtt 'LambdaCoreStack.Outputs.CRHelperLambdaLayer' Partner: !Ref 'Partner' + LambdaDefaultSGID: !GetAtt 'SharedResourcesStack.Outputs.LambdaDefaultSGID' + PrivateSubnet1Id: !Ref 'PrivateSubnet1Id' + PrivateSubnet2Id: !Ref 'PrivateSubnet2Id' + + StateMachineStack: + Type: AWS::CloudFormation::Stack + DeletionPolicy: Delete + UpdateReplacePolicy: Delete + Properties: + TemplateURL: !Sub 'https://${S3SourceBucket}.s3.amazonaws.com/templates/x-state-machines.yml' + Parameters: + EnvLabel: !Ref 'EnvLabel' + QueryRunnerLambdaArn: !GetAtt 'LambdaCoreStack.Outputs.QueryRunnerLambdaArn' Outputs: WebApiDnsName: @@ -601,10 +749,7 @@ Outputs: Value: !GetAtt 'WebApiBeanstalkStack.Outputs.EndpointURL' WebApiProdURL: Description: The URL of the ODS/API endpoint, after DNS is updated - Value: !Join - - '' - - - https:// - - !Ref 'DomainName' + Value: !Sub 'https://${DomainName}' AdminApiProdURL: Condition: UseAdminApi Description: The URL of the Admin Api. diff --git a/templates/x-aurora-postgres-db.yml b/templates/x-aurora-postgres-db.yml index 3614a6e..14bb2bd 100755 --- a/templates/x-aurora-postgres-db.yml +++ b/templates/x-aurora-postgres-db.yml @@ -39,14 +39,13 @@ Parameters: # NoEcho: 'true' KmsKeyId: - Default: '' Description: The KMS Key ID used to encrypt the database. Type: String DBEngineVersion: Description: Select Database Engine Version Type: String - Default: "13.12" + Default: "16.6" DatabaseType: Description: The database type for your environment (Only change to Serverless if DBEngineVersion is 13.7). @@ -68,14 +67,6 @@ Parameters: Default: db.t3.medium Description: Database Instance Class. db.r5 instance classes are supported for Aurora PostgreSQL 10.6 or later. db.t3.medium instance class is supported for Aurora PostgreSQL 10.7 or later. Type: String - AllowedValues: - - db.t3.medium - - db.t3.large - - db.t4g.medium - - db.t4g.large - - db.r6g.large - - db.r6g.xlarge - - db.r6g.2xlarge DeployReplica: Description: Deploy two Aurora instances. @@ -178,6 +169,8 @@ Resources: DBSNSTopic: Type: AWS::SNS::Topic + # checkov:skip=CKV_AWS_26: Ensure all data stored in SNS topic is encrypted + # KMS key is used, but not picked up by checkov Properties: KmsMasterKeyId: !Ref KmsKeyId Subscription: !If @@ -237,7 +230,6 @@ Resources: Type: 'AWS::KMS::Alias' DeletionPolicy: Retain UpdateReplacePolicy: Retain - DependsOn: AuroraDBCluster Properties: AliasName: !Sub 'alias/${AuroraDBCluster}' TargetKeyId: !Ref KmsKeyId @@ -259,6 +251,8 @@ Resources: AuroraDBCluster: Type: AWS::RDS::DBCluster + # Test case to be skipped by Checkov + # checkov:skip=CKV_AWS_162:Ensure RDS cluster has IAM authentication enabled DeletionPolicy: Snapshot UpdateReplacePolicy: Snapshot Properties: @@ -272,9 +266,9 @@ Resources: - !Ref 'AWS::NoValue' Port: 5432 MasterUsername: - !Join ['', ['{{resolve:secretsmanager:', !Ref RDSSecret, ':SecretString:username}}' ]] + !Join [':', ['{{resolve:secretsmanager', !Ref RDSSecret, 'SecretString:username}}' ]] MasterUserPassword: - !Join ['', ['{{resolve:secretsmanager:', !Ref RDSSecret, ':SecretString:password}}' ]] + !Join [':', ['{{resolve:secretsmanager', !Ref RDSSecret, 'SecretString:password}}' ]] DBSubnetGroupName: !Ref DBSubnetGroup VpcSecurityGroupIds: - !Ref RDSSecurityGroupID @@ -292,6 +286,8 @@ Resources: AuroraDBFirstInstance: Type: AWS::RDS::DBInstance + DeletionPolicy: Delete + UpdateReplacePolicy: Delete Properties: DBInstanceClass: !If - IsServerless @@ -316,8 +312,10 @@ Resources: Value: !Ref EnvLabel AuroraDBSecondInstance: - Condition: IsReplica Type: AWS::RDS::DBInstance + DeletionPolicy: Delete + UpdateReplacePolicy: Delete + Condition: IsReplica DependsOn: - AuroraDBFirstInstance Properties: diff --git a/templates/x-beanstalk-admin-api.yml b/templates/x-beanstalk-admin-api.yml index 374979f..b045494 100644 --- a/templates/x-beanstalk-admin-api.yml +++ b/templates/x-beanstalk-admin-api.yml @@ -25,6 +25,9 @@ Parameters: Type: String Description: Size of the EC2 machines to host the Docker containers Default: t3a.small,t3.small + BeanstalkServiceRole: + Type: String + Description: ARN of the service Role for Elastic Beanstalk VpcId: Type: AWS::EC2::VPC::Id Description: This provides the VPC to deploy resources in. @@ -57,6 +60,9 @@ Parameters: - 'RollingWithAdditionalBatch' - 'AllAtOnce' Default: 'RollingWithAdditionalBatch' + BeanstalkPlatformUpdateTime: + Description: Preferred time for Elastic Beanstalk automatic platform updates. + Type: String UploadAndDeployArn: Type: String Description: ARN of the Upload and Deploy Function @@ -68,6 +74,7 @@ Conditions: UseRollingUpdates: !Equals - !Ref 'DeploymentStrategy' - 'RollingWithAdditionalBatch' + EnableBeanstalkPlatformUpdate: !Not [!Equals [!Ref BeanstalkPlatformUpdateTime, '']] Resources: SharedAlbAdminApiRuleCustom: @@ -198,6 +205,9 @@ Resources: - Namespace: aws:ec2:instances OptionName: SpotFleetOnDemandBase Value: 1 + - Namespace: aws:elasticbeanstalk:environment + OptionName: ServiceRole + Value: !Ref BeanstalkServiceRole - Namespace: aws:autoscaling:asg OptionName: EnableCapacityRebalancing Value: true @@ -210,6 +220,32 @@ Resources: - ',' - - !Ref PrivateSubnet1Id - !Ref PrivateSubnet2Id + - !If + - EnableBeanstalkPlatformUpdate + - Namespace: aws:elasticbeanstalk:managedactions + OptionName: ManagedActionsEnabled + Value: 'true' + - Namespace: aws:elasticbeanstalk:managedactions + OptionName: ManagedActionsEnabled + Value: 'false' + - !If + - EnableBeanstalkPlatformUpdate + - Namespace: aws:elasticbeanstalk:managedactions:platformupdate + OptionName: UpdateLevel + Value: 'minor' + - !Ref 'AWS::NoValue' + - !If + - EnableBeanstalkPlatformUpdate + - Namespace: aws:elasticbeanstalk:managedactions:platformupdate + OptionName: InstanceRefreshEnabled + Value: 'false' + - !Ref 'AWS::NoValue' + - !If + - EnableBeanstalkPlatformUpdate + - Namespace: aws:elasticbeanstalk:managedactions + OptionName: PreferredStartTime + Value: !Ref BeanstalkPlatformUpdateTime + - !Ref 'AWS::NoValue' - Namespace: aws:elasticbeanstalk:application:environment OptionName: AURORA_MASTER_SECRET Value: !Ref RDSSecret @@ -249,7 +285,7 @@ Resources: - Namespace: aws:elasticbeanstalk:cloudwatch:logs OptionName: RetentionInDays Value: 365 - SolutionStackName: !Sub 'arn:aws:elasticbeanstalk:${AWS::Region}::platform/Docker running on 64bit Amazon Linux 2023' + SolutionStackName: !Sub 'arn:${AWS::Partition}:elasticbeanstalk:${AWS::Region}::platform/Docker running on 64bit Amazon Linux 2023' Tier: Name: 'WebServer' Type: 'Standard' diff --git a/templates/x-beanstalk-web-api.yml b/templates/x-beanstalk-web-api.yml old mode 100755 new mode 100644 index 1d6d502..a330724 --- a/templates/x-beanstalk-web-api.yml +++ b/templates/x-beanstalk-web-api.yml @@ -81,6 +81,9 @@ Parameters: - 'RollingWithAdditionalBatch' - 'AllAtOnce' Default: 'RollingWithAdditionalBatch' + BeanstalkPlatformUpdateTime: + Description: Preferred time for Elastic Beanstalk automatic platform updates. + Type: String WebAPIMaxPoolSize: Type: Number Description: Maximun Pool Size per Application Server @@ -106,6 +109,9 @@ Parameters: Type: String Description: Select partner for which Starting Blocks environment will be deployed. Default: 'ea' + BearerTokenPerClientLimit: + Type: Number + Description: The maximum number of bearer tokens per client. Conditions: UseWildcardSubdomains: !Equals @@ -124,6 +130,7 @@ Conditions: - MultiTenant EnableHealthCheck: !Not [!Equals [!Ref SNSTopicArn, '']] + EnableBeanstalkPlatformUpdate: !Not [!Equals [!Ref BeanstalkPlatformUpdateTime, '']] Resources: SharedAlbHTTPSRule: DependsOn: @@ -186,7 +193,6 @@ Resources: - SNS:Subscribe - SNS:ListSubscriptionsByTopic - SNS:Publish - - SNS:Receive Resource: !Ref 'BeanstalkWebApiSNSTopic' Condition: StringEquals: @@ -198,12 +204,12 @@ Resources: Description: 'Beanstalk environment for the EdFi Web API' EnvironmentName: !Sub '${EnvLabel}-WebApi-Prod' OptionSettings: - - !If - - EnableHealthNotifications - - Namespace: aws:elasticbeanstalk:sns:topics - OptionName: Notification Topic ARN - Value: !Ref 'BeanstalkWebApiSNSTopic' - - !Ref 'AWS::NoValue' + - Namespace: aws:elasticbeanstalk:sns:topics + OptionName: Notification Topic ARN + Value: !If + - EnableHealthNotifications + - !Ref 'BeanstalkWebApiSNSTopic' + - !Ref 'AWS::NoValue' - Namespace: aws:elasticbeanstalk:healthreporting:system OptionName: ConfigDocument Value: | @@ -342,6 +348,32 @@ Resources: - ',' - - !Ref PrivateSubnet1Id - !Ref PrivateSubnet2Id + - !If + - EnableBeanstalkPlatformUpdate + - Namespace: aws:elasticbeanstalk:managedactions + OptionName: ManagedActionsEnabled + Value: 'true' + - Namespace: aws:elasticbeanstalk:managedactions + OptionName: ManagedActionsEnabled + Value: 'false' + - !If + - EnableBeanstalkPlatformUpdate + - Namespace: aws:elasticbeanstalk:managedactions:platformupdate + OptionName: UpdateLevel + Value: 'minor' + - !Ref 'AWS::NoValue' + - !If + - EnableBeanstalkPlatformUpdate + - Namespace: aws:elasticbeanstalk:managedactions:platformupdate + OptionName: InstanceRefreshEnabled + Value: 'false' + - !Ref 'AWS::NoValue' + - !If + - EnableBeanstalkPlatformUpdate + - Namespace: aws:elasticbeanstalk:managedactions + OptionName: PreferredStartTime + Value: !Ref BeanstalkPlatformUpdateTime + - !Ref 'AWS::NoValue' - Namespace: aws:elasticbeanstalk:application:environment OptionName: AURORA_MASTER_SECRET Value: !Ref RDSSecret @@ -357,6 +389,9 @@ Resources: - Namespace: aws:elasticbeanstalk:application:environment OptionName: ApiSettings__DefaultPageSizeLimit Value: 5000 + - Namespace: aws:elasticbeanstalk:application:environment + OptionName: ApiSettings__BearerTokenPerClientLimit + Value: !Ref BearerTokenPerClientLimit - Namespace: aws:elasticbeanstalk:application:environment OptionName: Logging__LogLevel__Default Value: Warning @@ -381,7 +416,7 @@ Resources: - Namespace: aws:elasticbeanstalk:cloudwatch:logs OptionName: RetentionInDays Value: 365 - SolutionStackName: !Sub 'arn:aws:elasticbeanstalk:${AWS::Region}::platform/Docker running on 64bit Amazon Linux 2023' + SolutionStackName: !Sub 'arn:${AWS::Partition}:elasticbeanstalk:${AWS::Region}::platform/Docker running on 64bit Amazon Linux 2023' Tier: Name: 'WebServer' Type: 'Standard' diff --git a/templates/x-cloudwatch-dashboard.yml b/templates/x-cloudwatch-dashboard.yml index 1d8acb5..1157e77 100755 --- a/templates/x-cloudwatch-dashboard.yml +++ b/templates/x-cloudwatch-dashboard.yml @@ -53,7 +53,7 @@ Resources: "x": 6, "type": "log", "properties": { - "query": "SOURCE '/aws/elasticbeanstalk/${BeanstalkEnvName}/var/log/nginx/access.log' | fields @timestamp, @message\n| parse @message '[*] \"* * *\" * * \"*\" \"*\"' as @datetime, @method, @longrequest, @protocol, @responsecode, @size, @httpreferer, @agent\n| parse @longrequest '/*/*' as @tenant, @shortrequest\n| fields concat('/', @shortrequest) as @request\n| filter @agent not like \"ELB-HealthChecker\"\n| filter @responsecode not like /2\\d\\d/\n| parse @request /(?^[^?]+)/\n| parse base /(?^(\\/[^\\/]+){1,4})/\n| fields replace(match, \"/data/v3/ed-fi/\", \"\") as resource\n| stats count(*) as total by concat(@method, \" \", resource, \" \", @responsecode)\n| sort total desc", + "query": "SOURCE '/aws/elasticbeanstalk/${BeanstalkEnvName}/var/log/nginx/access.log' | fields @timestamp, @message\n| parse @message '[*] \"* * *\" * * \"*\" \"*\"' as @datetime, @method, @longrequest, @protocol, @responsecode, @size, @httpreferer, @agent\n| parse @longrequest '/*/*' as @tenant, @shortrequest\n| fields concat('/', @shortrequest) as @request\n| filter @agent not like \"ELB-HealthChecker\"\n| filter @agent not like \"Amazon-Route53-Health-Check-Service\"\n| filter @responsecode not like /2\\d\\d/\n| parse @request /(?^[^?]+)/\n| parse base /(?^(\\/[^\\/]+){1,4})/\n| fields replace(match, \"/data/v3/ed-fi/\", \"\") as resource\n| stats count(*) as total by concat(@method, \" \", resource, \" \", @responsecode)\n| sort total desc", "region": "${AWS::Region}", "stacked": false, "title": "Method/Resource/Response Distribution (Errors Only)", @@ -89,7 +89,7 @@ Resources: "x": 0, "type": "log", "properties": { - "query": "SOURCE '/aws/elasticbeanstalk/${BeanstalkEnvName}/var/log/nginx/access.log' | fields @timestamp, @message\n| parse @message '[*] \"* * *\" * * \"*\" \"*\" \"*\" * [*]' as @datetime, @method, @longrequest, @protocol, @responsecode, @size, @httpreferer, @agent, @xforwardedfor, @host, @responsetime\n| parse @longrequest '/*/*' as @tenant, @shortrequest\n| fields concat('/', @shortrequest) as @request\n#| filter @agent not like \"ELB-HealthChecker\"\n| filter @responsecode not like /2\\d\\d/\n| filter @host like /${DomainName}/\n| parse @request /(?^[^?]+)/\n| parse base /(?^(\\/[^\\/]+){1,4})/\n| fields replace(longresource, \"/data/v3/ed-fi/\", \"\") as resource\n| fields replace(@host, \".${DomainName}\", \"\") as application\n| stats count(*) as count by application, @tenant, @method, resource, @responsecode\n#| limit 20\n| sort by application asc, count desc", + "query": "SOURCE '/aws/elasticbeanstalk/${BeanstalkEnvName}/var/log/nginx/access.log' | fields @timestamp, @message\n| parse @message '[*] \"* * *\" * * \"*\" \"*\" \"*\" * [*]' as @datetime, @method, @longrequest, @protocol, @responsecode, @size, @httpreferer, @agent, @xforwardedfor, @host, @responsetime\n| parse @longrequest '/*/*' as @tenant, @shortrequest\n| fields concat('/', @shortrequest) as @request\n#| filter @agent not like \"ELB-HealthChecker\"\n| filter @agent not like \"Amazon-Route53-Health-Check-Service\"\n| filter @responsecode not like /2\\d\\d/\n| filter @host like /${DomainName}/\n| parse @request /(?^[^?]+)/\n| parse base /(?^(\\/[^\\/]+){1,4})/\n| fields replace(longresource, \"/data/v3/ed-fi/\", \"\") as resource\n| fields replace(@host, \".${DomainName}\", \"\") as application\n| stats count(*) as count by application, @tenant, @method, resource, @responsecode\n#| limit 20\n| sort by application asc, count desc", "region": "${AWS::Region}", "stacked": false, "title": "Errors Dashboard", @@ -103,7 +103,7 @@ Resources: "x": 16, "type": "log", "properties": { - "query": "SOURCE '/aws/elasticbeanstalk/${BeanstalkEnvName}/var/log/nginx/access.log' | fields @timestamp, @message\n| parse @message '[*] \"* * *\" * * \"*\" \"*\" \"*\" * [*]' as @datetime, @method, @longrequest, @protocol, @responsecode, @size, @httpreferer, @agent, @xforwardedfor, @host, @responsetime\n| parse @longrequest '/*/*' as @tenant, @request\n| filter @agent not like \"ELB-HealthChecker\"\n| filter @responsecode not like /2\\d\\d/\n| fields replace(@host, \".${DomainName}\", \"\") as application\n| stats count(*) as total by application, @tenant\n| sort total desc\n#| limit 10", + "query": "SOURCE '/aws/elasticbeanstalk/${BeanstalkEnvName}/var/log/nginx/access.log' | fields @timestamp, @message\n| parse @message '[*] \"* * *\" * * \"*\" \"*\" \"*\" * [*]' as @datetime, @method, @longrequest, @protocol, @responsecode, @size, @httpreferer, @agent, @xforwardedfor, @host, @responsetime\n| parse @longrequest '/*/*' as @tenant, @request\n| filter @agent not like \"ELB-HealthChecker\"\n| filter @agent not like \"Amazon-Route53-Health-Check-Service\"\n| filter @responsecode not like /2\\d\\d/\n| fields replace(@host, \".${DomainName}\", \"\") as application\n| stats count(*) as total by application, @tenant\n| sort total desc\n#| limit 10", "region": "${AWS::Region}", "stacked": false, "title": "Total errors by application and tenant", @@ -117,7 +117,7 @@ Resources: "width": 24, "height": 6, "properties": { - "query": "SOURCE '/aws/elasticbeanstalk/${BeanstalkEnvName}/var/log/nginx/access.log' | fields @timestamp, @message, @logStream, @log\n| parse @message '[*] \"* * *\" * * \"*\" \"*\" \"*\" * [*] *' as @datetime, @method, @longrequest, @protocol, @responsecode, @size, @httpreferer, @agent, @xforwardedfor, @host, @responsetime, @correlationid\n| parse @longrequest '/*/*' as @tenant, @shortrequest\n| fields concat('/', @shortrequest) as @request\n| filter @agent not like \"ELB-HealthChecker\"\n| sort @responsetime desc\n| display @datetime, @host, @tenant, @method, @request, @responsecode, @responsetime, @correlationid\n| limit 10", + "query": "SOURCE '/aws/elasticbeanstalk/${BeanstalkEnvName}/var/log/nginx/access.log' | fields @timestamp, @message, @logStream, @log\n| parse @message '[*] \"* * *\" * * \"*\" \"*\" \"*\" * [*] *' as @datetime, @method, @longrequest, @protocol, @responsecode, @size, @httpreferer, @agent, @xforwardedfor, @host, @responsetime, @correlationid\n| parse @longrequest '/*/*' as @tenant, @shortrequest\n| fields concat('/', @shortrequest) as @request\n| filter @agent not like \"ELB-HealthChecker\"\n| filter @agent not like \"Amazon-Route53-Health-Check-Service\"\n| sort @responsetime desc\n| display @datetime, @host, @tenant, @method, @request, @responsecode, @responsetime, @correlationid\n| limit 10", "region": "${AWS::Region}", "stacked": false, "view": "table", @@ -384,7 +384,7 @@ Resources: "x": 0, "type": "log", "properties": { - "query": "SOURCE '/aws/elasticbeanstalk/${BeanstalkEnvName}/var/log/eb-docker/containers/eb-current-app/stdouterr.log' | fields @timestamp, @message | sort @timestamp desc | limit 100", + "query": "SOURCE '/aws/elasticbeanstalk/${BeanstalkEnvName}/var/log/eb-docker/containers/eb-current-app/stdouterr.log' | fields @timestamp, @message \n| sort @timestamp desc \n| limit 100", "region": "${AWS::Region}", "stacked": false, "view": "table" @@ -435,7 +435,7 @@ Resources: "x": 0, "type": "log", "properties": { - "query": "SOURCE '/aws/elasticbeanstalk/${BeanstalkEnvName}/var/log/nginx/access.log' | fields @timestamp, @message\n| parse @message '[*] \"* * *\" * * \"-\" \"*\"' as @datetime, @method, @request, @protocol, @responsecode, @size, @agent\n| filter @agent not like \"ELB-HealthChecker\"\n| parse @method \"GE*\" as MGET\n| parse @method \"POS*\" as MPOST\n| parse @method \"PU*\" as MPUT\n| parse @method \"DELET*\" as MDELETE\n| stats count(MGET) as GET, count(MPOST) as POST, count(MPUT) as PUT, count(MDELETE) as DELETE by bin(1m)", + "query": "SOURCE '/aws/elasticbeanstalk/${BeanstalkEnvName}/var/log/nginx/access.log' | fields @timestamp, @message\n| parse @message '[*] \"* * *\" * * \"-\" \"*\"' as @datetime, @method, @request, @protocol, @responsecode, @size, @agent\n| filter @agent not like \"ELB-HealthChecker\"\n| filter @agent not like \"Amazon-Route53-Health-Check-Service\"\n| parse @method \"GE*\" as MGET\n| parse @method \"POS*\" as MPOST\n| parse @method \"PU*\" as MPUT\n| parse @method \"DELET*\" as MDELETE\n| stats count(MGET) as GET, count(MPOST) as POST, count(MPUT) as PUT, count(MDELETE) as DELETE by bin(1m)", "region": "${AWS::Region}", "stacked": false, "title": "HTTP Methods", @@ -449,7 +449,7 @@ Resources: "x": 8, "type": "log", "properties": { - "query": "SOURCE '/aws/elasticbeanstalk/${BeanstalkEnvName}/var/log/nginx/access.log' | fields @timestamp, @message\n| parse @message '[*] \"* * *\" * * \"-\" \"*\"' as @datetime, @method, @longrequest, @protocol, @responsecode, @size, @agent\n| parse @longrequest '/*/*' as @tenant, @shortrequest\n| fields concat('/', @shortrequest) as @request\n| filter @agent not like \"ELB-HealthChecker\"\n| parse @request /(?^[^?]+)/\n| parse base /(?^(\\/[^\\/]+){1,4})/\n| stats count(*) as total by concat(@method, \" \", match, \" \", @responsecode)\n| sort total desc", + "query": "SOURCE '/aws/elasticbeanstalk/${BeanstalkEnvName}/var/log/nginx/access.log' | fields @timestamp, @message\n| parse @message '[*] \"* * *\" * * \"-\" \"*\"' as @datetime, @method, @longrequest, @protocol, @responsecode, @size, @agent\n| parse @longrequest '/*/*' as @tenant, @shortrequest\n| fields concat('/', @shortrequest) as @request\n| filter @agent not like \"ELB-HealthChecker\"\n| filter @agent not like \"Amazon-Route53-Health-Check-Service\"\n| parse @request /(?^[^?]+)/\n| parse base /(?^(\\/[^\\/]+){1,4})/\n| stats count(*) as total by concat(@method, \" \", match, \" \", @responsecode)\n| sort total desc", "region": "${AWS::Region}", "stacked": false, "title": "Method/Resource/Response Distribution", @@ -463,7 +463,7 @@ Resources: "x": 0, "type": "log", "properties": { - "query": "SOURCE '/aws/elasticbeanstalk/${BeanstalkEnvName}/var/log/nginx/access.log' | fields @timestamp, @message\n| parse @message '[*] \"* * *\" * * \"*\" \"*\" \"*\" * [*]' as @datetime, @method, @longrequest, @protocol, @responsecode, @size, @httpreferer, @agent, @xforwardedfor, @host, @responsetime\n| parse @longrequest '/*/*' as @tenant, @request\n| filter @agent not like \"ELB-HealthChecker\"\n| stats count(*) as total by @host, @tenant\n| sort total desc\n| limit 10", + "query": "SOURCE '/aws/elasticbeanstalk/${BeanstalkEnvName}/var/log/nginx/access.log' | fields @timestamp, @message\n| parse @message '[*] \"* * *\" * * \"*\" \"*\" \"*\" * [*]' as @datetime, @method, @longrequest, @protocol, @responsecode, @size, @httpreferer, @agent, @xforwardedfor, @host, @responsetime\n| parse @longrequest '/*/*' as @tenant, @request\n| filter @agent not like \"ELB-HealthChecker\"\n| filter @agent not like \"Amazon-Route53-Health-Check-Service\"\n| stats count(*) as total by @host, @tenant\n| sort total desc\n| limit 10", "region": "${AWS::Region}", "stacked": false, "title": "Requests by host and tenant", @@ -477,7 +477,7 @@ Resources: "x": 7, "type": "log", "properties": { - "query": "SOURCE '/aws/elasticbeanstalk/${BeanstalkEnvName}/var/log/nginx/access.log' | fields @timestamp, @message\n| parse @message '[*] \"* * *\" * * \"*\" \"*\" \"*\" * [*] *' as @datetime, @method, @longrequest, @protocol, @responsecode, @size, @httpreferer, @agent, @xforwardedfor, @host, @responsetime, @correlationid\n| parse @longrequest '/*/*' as @tenant, @shortrequest\n| fields concat('/', @shortrequest) as @request\n| filter @agent not like \"ELB-HealthChecker\"\n| filter @request not like \"/oauth/token\"\n| sort @timestamp desc\n| limit 10\n| display @timestamp, @method, @request, @responsecode, @host, @tenant, @responsetime, @size, @agent, @xforwardedfor, @correlationid", + "query": "SOURCE '/aws/elasticbeanstalk/${BeanstalkEnvName}/var/log/nginx/access.log' | fields @timestamp, @message\n| parse @message '[*] \"* * *\" * * \"*\" \"*\" \"*\" * [*] *' as @datetime, @method, @longrequest, @protocol, @responsecode, @size, @httpreferer, @agent, @xforwardedfor, @host, @responsetime, @correlationid\n| parse @longrequest '/*/*' as @tenant, @shortrequest\n| fields concat('/', @shortrequest) as @request\n| filter @agent not like \"ELB-HealthChecker\"\n| filter @agent not like \"Amazon-Route53-Health-Check-Service\"\n| sort @timestamp desc\n| limit 10\n| display @timestamp, @method, @request, @responsecode, @host, @tenant, @responsetime, @size, @agent, @xforwardedfor, @correlationid", "region": "${AWS::Region}", "stacked": false, "title": "Log group: /aws/elasticbeanstalk/${BeanstalkEnvName}/var/log/nginx/access.log", @@ -549,7 +549,7 @@ Resources: "x": 0, "type": "log", "properties": { - "query": "SOURCE '/aws/elasticbeanstalk/${BeanstalkEnvName}/var/log/nginx/access.log' | fields @timestamp, @message\n| parse @message '[*] \"* * *\" * * \"-\" \"*\"' as @datetime, @method, @longrequest, @protocol, @responsecode, @size, @agent\n| parse @longrequest '/*/*' as @tenant, @shortrequest\n| fields concat('/', @shortrequest) as @request\n| filter @agent not like \"ELB-HealthChecker\"\n| parse @request /(?^[^?]+)/\n| parse base /(?^(\\/[^\\/]+){1,4})/\n| stats count(*) as total by concat(@method, \" \", match, \" \", @responsecode)\n| sort total desc", + "query": "SOURCE '/aws/elasticbeanstalk/${BeanstalkEnvName}/var/log/nginx/access.log' | fields @timestamp, @message\n| parse @message '[*] \"* * *\" * * \"-\" \"*\"' as @datetime, @method, @longrequest, @protocol, @responsecode, @size, @agent\n| parse @longrequest '/*/*' as @tenant, @shortrequest\n| fields concat('/', @shortrequest) as @request\n| filter @agent not like \"ELB-HealthChecker\"\n| filter @agent not like \"Amazon-Route53-Health-Check-Service\"\n| filter @request not like \"/oauth/token\"\n| parse @request /(?^[^?]+)/\n| parse base /(?^(\\/[^\\/]+){1,4})/\n| stats count(*) as total by concat(@method, \" \", match, \" \", @responsecode)\n| sort total desc", "region": "${AWS::Region}", "stacked": false, "title": "Method/Resource/Response Distribution", @@ -563,7 +563,7 @@ Resources: "x": 12, "type": "log", "properties": { - "query": "SOURCE '/aws/elasticbeanstalk/${BeanstalkEnvName}/var/log/nginx/access.log' | fields @timestamp, @message\n| parse @message '[*] \"* * *\" * * \"-\" \"*\"' as @datetime, @method, @request, @protocol, @responsecode, @size, @agent\n| filter @agent not like \"ELB-HealthChecker\"\n| parse @method \"GE*\" as MGET\n| parse @method \"POS*\" as MPOST\n| parse @method \"PU*\" as MPUT\n| parse @method \"DELET*\" as MDELETE\n| stats count(MGET) as GET, count(MPOST) as POST, count(MPUT) as PUT, count(MDELETE) as DELETE by bin(1m)", + "query": "SOURCE '/aws/elasticbeanstalk/${BeanstalkEnvName}/var/log/nginx/access.log' | fields @timestamp, @message\n| parse @message '[*] \"* * *\" * * \"-\" \"*\"' as @datetime, @method, @request, @protocol, @responsecode, @size, @agent\n| filter @agent not like \"ELB-HealthChecker\"\n| filter @agent not like \"Amazon-Route53-Health-Check-Service\"\n| filter @request not like \"/oauth/token\"\n| parse @method \"GE*\" as MGET\n| parse @method \"POS*\" as MPOST\n| parse @method \"PU*\" as MPUT\n| parse @method \"DELET*\" as MDELETE\n| stats count(MGET) as GET, count(MPOST) as POST, count(MPUT) as PUT, count(MDELETE) as DELETE by bin(1m)", "region": "${AWS::Region}", "stacked": false, "title": "HTTP Methods", @@ -577,7 +577,7 @@ Resources: "x": 0, "type": "log", "properties": { - "query": "SOURCE '/aws/elasticbeanstalk/${BeanstalkEnvName}/var/log/nginx/access.log' | fields @timestamp, @message\n| parse @message '[*] \"* * *\" * * \"*\" \"*\" \"*\" * [*]' as @datetime, @method, @longrequest, @protocol, @responsecode, @size, @httpreferer, @agent, @xforwardedfor, @host, @responsetime\n| parse @longrequest '/*/*' as @tenant, @request\n| filter @agent not like \"ELB-HealthChecker\"\n| stats count(*) as total by @host, @tenant\n| sort total desc\n| limit 10", + "query": "SOURCE '/aws/elasticbeanstalk/${BeanstalkEnvName}/var/log/nginx/access.log' | fields @timestamp, @message\n| parse @message '[*] \"* * *\" * * \"*\" \"*\" \"*\" * [*]' as @datetime, @method, @longrequest, @protocol, @responsecode, @size, @httpreferer, @agent, @xforwardedfor, @host, @responsetime\n| parse @longrequest '/*/*' as @tenant, @request\n| filter @agent not like \"ELB-HealthChecker\"\n| filter @agent not like \"Amazon-Route53-Health-Check-Service\"\n| filter @request not like \"/oauth/token\"\n| stats count(*) as total by @host, @tenant\n| sort total desc\n| limit 10", "region": "${AWS::Region}", "stacked": false, "title": "Requests by host and tenant", @@ -591,7 +591,7 @@ Resources: "x": 12, "type": "log", "properties": { - "query": "SOURCE '/aws/elasticbeanstalk/${BeanstalkEnvName}/var/log/nginx/access.log' | fields @timestamp, @message \n| parse @message '[*] \"* * *\" * * \"*\" \"*\" \"*\" * [*] *' as @datetime, @method, @longrequest, @protocol, @responsecode, @size, @httpreferer, @agent, @xforwardedfor, @host, @responsetime \n| parse @longrequest '/*/*/*/*/*' as @tenant, @data, @version, @namespace, @longresource \n| filter @agent not like \"ELB-HealthChecker\" \n| filter @longrequest not like \"/oauth/token\" \n| filter @longrequest not like \"swagger.json\" \n| filter @longresource like \"\" and @tenant like \"\" \n| parse @longresource /(?<@resource>^[^?]+)/ \n| stats count(*) as total by @tenant, @method, @resource \n| sort total desc \n| limit 100", + "query": "SOURCE '/aws/elasticbeanstalk/${BeanstalkEnvName}/var/log/nginx/access.log' | fields @timestamp, @message \n| parse @message '[*] \"* * *\" * * \"*\" \"*\" \"*\" * [*] *' as @datetime, @method, @longrequest, @protocol, @responsecode, @size, @httpreferer, @agent, @xforwardedfor, @host, @responsetime \n| parse @longrequest '/*/*/*/*/*' as @tenant, @data, @version, @namespace, @longresource \n| filter @agent not like \"ELB-HealthChecker\" \n| filter @agent not like \"Amazon-Route53-Health-Check-Service\"\n| filter @longrequest not like \"/oauth/token\" \n| filter @longrequest not like \"swagger.json\" \n| filter @longresource like \"\" and @tenant like \"\" \n| parse @longresource /(?<@resource>^[^?]+)/ \n| stats count(*) as total by @tenant, @method, @resource \n| sort total desc \n| limit 100", "region": "${AWS::Region}", "stacked": false, "title": "Requests by tenant, method & resource", @@ -605,7 +605,7 @@ Resources: "x": 0, "type": "log", "properties": { - "query": "SOURCE '/aws/elasticbeanstalk/${BeanstalkEnvName}/var/log/nginx/access.log' | fields @timestamp, @message\n| parse @message '[*] \"* * *\" * * \"*\" \"*\" \"*\" * [*] *' as @datetime, @method, @longrequest, @protocol, @responsecode, @size, @httpreferer, @agent, @xforwardedfor, @host, @responsetime, @correlationid\n| parse @longrequest '/*/*' as @tenant, @shortrequest\n| fields concat('/', @shortrequest) as @request\n| filter @agent not like \"ELB-HealthChecker\"\n| filter @request not like \"/oauth/token\"\n| sort @timestamp desc\n| limit 100\n| display @timestamp, @method, @request, @responsecode, @host, @tenant, @responsetime, @size, @agent, @xforwardedfor, @correlationid", + "query": "SOURCE '/aws/elasticbeanstalk/${BeanstalkEnvName}/var/log/nginx/access.log' | fields @timestamp, @message\n| parse @message '[*] \"* * *\" * * \"*\" \"*\" \"*\" * [*] *' as @datetime, @method, @longrequest, @protocol, @responsecode, @size, @httpreferer, @agent, @xforwardedfor, @host, @responsetime, @correlationid\n| parse @longrequest '/*/*' as @tenant, @shortrequest\n| fields concat('/', @shortrequest) as @request\n| filter @agent not like \"ELB-HealthChecker\"\n| filter @agent not like \"Amazon-Route53-Health-Check-Service\"\n| filter @request not like \"/oauth/token\"\n| sort @timestamp desc\n| limit 100\n| display @timestamp, @method, @request, @responsecode, @host, @tenant, @responsetime, @size, @agent, @xforwardedfor, @correlationid", "region": "${AWS::Region}", "stacked": false, "title": "Log group: /aws/elasticbeanstalk/${BeanstalkEnvName}/var/log/nginx/access.log", diff --git a/templates/x-lambda-core-functions.yml b/templates/x-lambda-core-functions.yml index 794ba4b..8cd8dd5 100755 --- a/templates/x-lambda-core-functions.yml +++ b/templates/x-lambda-core-functions.yml @@ -31,6 +31,9 @@ Parameters: Default: edfi-aws-quick-deploy Description: This provides the name of the S3 bucket where the Lambda source code resides. + ArtifactsS3SourceBucket: + Type: String + Description: This bucket contains the source files for database/ and docker/ folders. MUST be in the same region as the CF Stack. S3KeySourceCode: Type: String Default: lambdas @@ -86,6 +89,9 @@ Parameters: SNSTopicArn: Description: ARN of SNS topic to publish Route53 HealthCheck Alarms Type: String + KmsKeyId: + Description: The KMS Key ID used to encrypt resources in this deployment. + Type: String Conditions: UseAdminApi: !Equals [!Ref 'AdminInterface', "Ed-Fi Admin API"] @@ -93,6 +99,8 @@ Conditions: !Not [!Equals [!Ref SNSTopicArn, '']] UsePublisher: !Equals [!Ref APIPublisher, 'true'] EnableSlackNotifications: !Not [!Equals [!Ref SlackWebhookUrl, '']] + EnableCrossAccountInvoke: !Not [!Equals [!Join [',', !Ref AdminAccountIds], '']] + UseAdminApiAndCrossAccountInvoke: !And [!Condition UseAdminApi, !Not [!Equals [!Join [',', !Ref AdminAccountIds], '']]] Resources: @@ -181,19 +189,20 @@ Resources: Action: - s3:GetBucketLocation Resource: - - !Sub 'arn:aws:s3:::${S3BucketSourceCode}' + - !Sub 'arn:${AWS::Partition}:s3:::${S3BucketSourceCode}' - Effect: Allow Action: - s3:GetObject Resource: - - !Sub 'arn:aws:s3:::${S3BucketSourceCode}/*' + - !Sub 'arn:${AWS::Partition}:s3:::${S3BucketSourceCode}/*' + - !Sub 'arn:${AWS::Partition}:s3:::${ArtifactsS3SourceBucket}/*' - Effect: Allow Action: - kms:Decrypt - ssm:GetParameter Resource: - - !Sub 'arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/*' - - !Sub 'arn:aws:kms:${AWS::Region}:${AWS::AccountId}:key/*' + - !Sub 'arn:${AWS::Partition}:ssm:${AWS::Region}:${AWS::AccountId}:parameter/*' + - !Sub 'arn:${AWS::Partition}:kms:${AWS::Region}:${AWS::AccountId}:key/*' - Effect: Allow Action: ssm:DescribeParameters Resource: '*' @@ -203,6 +212,8 @@ Resources: DatabaseRestoreLambdaFunction: Type: AWS::Lambda::Function + # checkov:skip=CKV_AWS_116:Ensure that AWS Lambda function is configured for a Dead Letter Queue(DLQ) + # checkov:skip=CKV_AWS_173:Check encryption settings for Lambda environment variable Properties: Handler: index.lambda_handler Layers: @@ -254,7 +265,6 @@ Resources: ## Download SQL file from S3 download_s3_file_to_client_local_directory(bucket, bucket_key, "/tmp/import_file.sql") - print('SQL file downloaded') ## Set the .pgpass values and perms that we will use to connect to a database @@ -284,7 +294,6 @@ Resources: print('Not altering database connection flag') def download_s3_file_to_client_local_directory(bucket_name,key,filename): - try: s3 = boto3.client('s3') print("Downloading file s3://" + bucket_name + "/" + key) @@ -301,12 +310,12 @@ Resources: client = boto3.client('secretsmanager') get_secret_value_response = client.get_secret_value(SecretId=secret_arn) return json.loads(get_secret_value_response['SecretString']) - + @helper.update @helper.delete def no_op(_, __): pass - + def timeout_handler(_signal, _frame): '''Handle SIGALRM''' raise Exception('Time exceeded') @@ -343,6 +352,7 @@ Resources: Path: / EncryptionKeyGeneratorFunction: Type: AWS::Lambda::Function + # checkov:skip=CKV_AWS_116:Ensure that AWS Lambda function is configured for a Dead Letter Queue(DLQ) Properties: Handler: index.lambda_handler Layers: @@ -479,6 +489,8 @@ Resources: SNSToSlackFunction: Condition: EnableSlackNotifications Type: AWS::Lambda::Function + # checkov:skip=CKV_AWS_116:Ensure that AWS Lambda function is configured for a Dead Letter Queue(DLQ) + # checkov:skip=CKV_AWS_173:Check encryption settings for Lambda environment variable Properties: Runtime: python3.11 Role: !GetAtt 'SNSToSlackRole.Arn' @@ -649,6 +661,7 @@ Resources: Action: 'lambda:InvokeFunction' FunctionName: !Ref 'SNSToSlackFunction' Principal: sns.amazonaws.com + SourceAccount: !Ref 'AWS::AccountId' SNSToSlackRetention: Condition: EnableSlackNotifications DependsOn: SetCloudWatchRetentionVersion @@ -701,17 +714,25 @@ Resources: Action: - ssm:SendCommand Resource: - - 'arn:aws:ssm:*::document/AWS-RunShellScript' + - !Sub 'arn:${AWS::Partition}:ssm:*::document/AWS-RunShellScript' - Effect: Allow Action: - ssm:SendCommand Resource: - - !Sub 'arn:aws:ec2:*:${AWS::AccountId}:instance/*' + - !Sub 'arn:${AWS::Partition}:ec2:*:${AWS::AccountId}:instance/*' Condition: StringEquals: "aws:ResourceTag/Environment": !Ref EnvLabel + - Effect: Allow + Action: + - kms:Decrypt + Resource: + - !Sub 'arn:${AWS::Partition}:kms:${AWS::Region}:${AWS::AccountId}:key/${KmsKeyId}' + TenantManagementFunction: Type: AWS::Lambda::Function + # checkov:skip=CKV_AWS_116:Ensure that AWS Lambda function is configured for a Dead Letter Queue(DLQ) + # checkov:skip=CKV_AWS_173:Check encryption settings for Lambda environment variable Properties: Runtime: python3.11 Role: !GetAtt 'TenantManagementRole.Arn' @@ -1194,6 +1215,7 @@ Resources: - !Ref AdminAccountIds - 'TenantManagementPermission${AccountId}': Type: 'AWS::Lambda::Permission' + Condition: EnableCrossAccountInvoke Properties: Action: 'lambda:InvokeFunction' FunctionName: !Ref 'TenantManagementFunction' @@ -1262,10 +1284,10 @@ Resources: - logs:DescribeLogGroups - logs:PutRetentionPolicy - logs:CreateLogGroup - Resource: - - '*' + Resource: !Sub 'arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/*' SetCloudWatchRetentionLambdaFunction: Type: 'AWS::Lambda::Function' + # checkov:skip=CKV_AWS_116:Ensure that AWS Lambda function is configured for a Dead Letter Queue(DLQ) DeletionPolicy: Delete Properties: FunctionName: !Sub '${EnvLabel}-SetCloudWatchRetention' @@ -1376,6 +1398,7 @@ Resources: - s3:GetObject Resource: - !Sub 'arn:${AWS::Partition}:s3:::${S3BucketSourceCode}/*' + - !Sub 'arn:${AWS::Partition}:s3:::${ArtifactsS3SourceBucket}/*' - Effect: Allow Action: - sns:GetTopicAttributes @@ -1387,6 +1410,7 @@ Resources: BeanstalkUploadAndDeployLambdaFunction: Type: 'AWS::Lambda::Function' + # checkov:skip=CKV_AWS_116:Ensure that AWS Lambda function is configured for a Dead Letter Queue(DLQ) DeletionPolicy: Delete Properties: FunctionName: !Sub '${EnvLabel}-BeanstalkUploadAndDeploy' @@ -1546,9 +1570,11 @@ Resources: - secretsmanager:GetSecretValue Resource: - !Ref RDSSecret - - !Sub 'arn:aws:secretsmanager:${AWS::Region}:${AWS::AccountId}:secret:${EnvLabel}-WebApiSecret-*' + - !Sub 'arn:${AWS::Partition}:secretsmanager:${AWS::Region}:${AWS::AccountId}:secret:${EnvLabel}-WebApiSecret-*' ODSManagementFunction: Type: AWS::Lambda::Function + # checkov:skip=CKV_AWS_116:Ensure that AWS Lambda function is configured for a Dead Letter Queue(DLQ) + # checkov:skip=CKV_AWS_173:Check encryption settings for Lambda environment variable Properties: Runtime: python3.11 Role: !GetAtt 'ODSManagementRole.Arn' @@ -1979,7 +2005,7 @@ Resources: host = host.replace('.cluster-', '.cluster-ro-') name = secret_values['username'] password = secret_values['password'] - connectionstring = (f"host={host}; database={ods_DB_name}; port=5432; username={name}; password={password}; Application Name=EdFi.Ods.WebApi; SSL Mode=Require; Trust Server Certificate=true; Maximum Pool Size={os.environ['MAXIMUM_POOL_SIZE']}; Connection Idle Lifetime={os.environ['CONNECTION_IDLE']};") + connectionstring = (f"host={host}; database={ods_DB_name}; port=5432; username={name}; password={password}; Application Name=EdFi.Ods.WebApi; SSL Mode=Require; Trust Server Certificate=true; Maximum Pool Size={os.environ['MAXIMUM_POOL_SIZE']}; Connection Idle Lifetime={os.environ['CONNECTION_IDLE']}; Command Timeout=60;") client = boto3.client('secretsmanager') response = client.get_secret_value(SecretId=f'{env_label}-WebApiSecret') base64_key = response['SecretString'] @@ -2005,6 +2031,7 @@ Resources: - !Ref AdminAccountIds - 'ODSManagementPermission${AccountId}': Type: 'AWS::Lambda::Permission' + Condition: EnableCrossAccountInvoke Properties: Action: 'lambda:InvokeFunction' FunctionName: !Ref 'ODSManagementFunction' @@ -2047,9 +2074,17 @@ Resources: Action: - dynamodb:GetItem Resource: - - !Sub 'arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${EnvLabel}-tenants' + - !Sub 'arn:${AWS::Partition}:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${EnvLabel}-tenants' + - Effect: Allow + Action: + - kms:Decrypt + Resource: + - !Sub 'arn:${AWS::Partition}:kms:${AWS::Region}:${AWS::AccountId}:key/${KmsKeyId}' + EdOrgManagementFunction: Type: AWS::Lambda::Function + # checkov:skip=CKV_AWS_116:Ensure that AWS Lambda function is configured for a Dead Letter Queue(DLQ) + # checkov:skip=CKV_AWS_173:Check encryption settings for Lambda environment variable Properties: Runtime: python3.11 Role: !GetAtt 'EdOrgManagementRole.Arn' @@ -2384,6 +2419,7 @@ Resources: - !Ref AdminAccountIds - 'EdOrgManagementPermission${AccountId}': Type: 'AWS::Lambda::Permission' + Condition: EnableCrossAccountInvoke Properties: Action: 'lambda:InvokeFunction' FunctionName: !Ref 'EdOrgManagementFunction' @@ -2430,10 +2466,17 @@ Resources: Action: - dynamodb:Scan Resource: !Sub 'arn:${AWS::Partition}:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${EnvLabel}-tenants' - + - Effect: Allow + Action: + - kms:Decrypt + Resource: + - !Sub 'arn:${AWS::Partition}:kms:${AWS::Region}:${AWS::AccountId}:key/${KmsKeyId}' + SBEMetadataLambdaFunction: Condition: UseAdminApi Type: AWS::Lambda::Function + # checkov:skip=CKV_AWS_116:Ensure that AWS Lambda function is configured for a Dead Letter Queue(DLQ) + # checkov:skip=CKV_AWS_173:Check encryption settings for Lambda environment variable Properties: Handler: index.handler Layers: @@ -2455,7 +2498,6 @@ Resources: SECRET_ARN: !Ref RDSSecret MODE: !Ref EdFiTenancyMode ENVLABEL: !Ref EnvLabel - PARTNER: !Ref Partner DOMAIN_NAME: !Ref DomainName TENANT_RESOURCE_TREE_ARN: !GetAtt 'TenantResourceTreeFunction.Arn' TENENT_MANAGEMENT_ARN: !GetAtt 'TenantManagementFunction.Arn' @@ -2488,7 +2530,7 @@ Resources: - !Ref AdminAccountIds - 'SBEMetadataLambdaPermission${AccountId}': Type: 'AWS::Lambda::Permission' - Condition: UseAdminApi + Condition: UseAdminApiAndCrossAccountInvoke Properties: Action: 'lambda:InvokeFunction' FunctionName: !Ref 'SBEMetadataLambdaFunction' @@ -2536,10 +2578,17 @@ Resources: Action: - dynamodb:Query Resource: !Sub 'arn:${AWS::Partition}:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${EnvLabel}-tenants' - + - Effect: Allow + Action: + - kms:Decrypt + Resource: + - !Sub 'arn:${AWS::Partition}:kms:${AWS::Region}:${AWS::AccountId}:key/${KmsKeyId}' + TenantResourceTreeFunction: Condition: UseAdminApi Type: AWS::Lambda::Function + # checkov:skip=CKV_AWS_116:Ensure that AWS Lambda function is configured for a Dead Letter Queue(DLQ) + # checkov:skip=CKV_AWS_173:Check encryption settings for Lambda environment variable Properties: Handler: index.handler Layers: @@ -2579,7 +2628,7 @@ Resources: - !Ref AdminAccountIds - 'TenantResourceTreePermission${AccountId}': Type: 'AWS::Lambda::Permission' - Condition: UseAdminApi + Condition: UseAdminApiAndCrossAccountInvoke Properties: Action: 'lambda:InvokeFunction' FunctionName: !Ref 'TenantResourceTreeFunction' @@ -2620,6 +2669,8 @@ Resources: Resource: !Ref RDSSecret DataFreshnessFunction: Type: AWS::Lambda::Function + # checkov:skip=CKV_AWS_116:Ensure that AWS Lambda function is configured for a Dead Letter Queue(DLQ) + # checkov:skip=CKV_AWS_173:Check encryption settings for Lambda environment variable Properties: Description: Will get Data Freshness JSON for SBAA Handler: index.lambda_handler @@ -2756,6 +2807,7 @@ Resources: - !Ref AdminAccountIds - 'DataFreshnessPermission${AccountId}': Type: 'AWS::Lambda::Permission' + Condition: EnableCrossAccountInvoke Properties: Action: 'lambda:InvokeFunction' FunctionName: !Ref 'DataFreshnessFunction' @@ -2796,14 +2848,16 @@ Resources: - cloudwatch:PutMetricAlarm - cloudwatch:DescribeAlarms - cloudwatch:DeleteAlarms - Resource: '*' + Resource: !Sub 'arn:${AWS::Partition}:cloudwatch:us-east-1:${AWS::AccountId}:alarm:${EnvLabel}*' East1AlarmFunction: Condition: EnableHealthCheck Type: AWS::Lambda::Function + # checkov:skip=CKV_AWS_116:Ensure that AWS Lambda function is configured for a Dead Letter Queue(DLQ) Properties: Handler: index.lambda_handler Layers: - !Ref 'CRHelperLambdaLayer' + ReservedConcurrentExecutions: 1 Description: Creates a Route53 Healthcheck Alarm in us-east-1 FunctionName: !Sub '${EnvLabel}-East1Alarm' Role: !GetAtt 'East1AlarmRole.Arn' @@ -2942,8 +2996,8 @@ Resources: - kms:Decrypt - ssm:GetParameter Resource: - - !Sub 'arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/*' - - !Sub 'arn:aws:kms:${AWS::Region}:${AWS::AccountId}:key/*' + - !Sub 'arn:${AWS::Partition}:ssm:${AWS::Region}:${AWS::AccountId}:parameter/*' + - !Sub 'arn:${AWS::Partition}:kms:${AWS::Region}:${AWS::AccountId}:key/*' - Effect: Allow Action: ssm:DescribeParameters Resource: '*' @@ -2952,6 +3006,8 @@ Resources: Resource: '*' ODSDerivativesLambdaFunction: Type: AWS::Lambda::Function + # checkov:skip=CKV_AWS_116:Ensure that AWS Lambda function is configured for a Dead Letter Queue(DLQ) + # checkov:skip=CKV_AWS_173:Check encryption settings for Lambda environment variable Properties: Handler: index.lambda_handler Layers: @@ -3198,6 +3254,8 @@ Resources: - !Ref RDSSecret ODSUserPermissionsFunction: Type: AWS::Lambda::Function + # checkov:skip=CKV_AWS_116:Ensure that AWS Lambda function is configured for a Dead Letter Queue(DLQ) + # checkov:skip=CKV_AWS_173:Check encryption settings for Lambda environment variable Properties: Runtime: python3.11 Role: !GetAtt 'ODSUserPermissionsRole.Arn' @@ -3225,6 +3283,7 @@ Resources: import os import boto3 from psycopg2.extensions import ISOLATION_LEVEL_AUTOCOMMIT + from psycopg2 import errors #The event_requirements object is used to return a useful error about the requirements of each action back to the user. This could also be expanded later for a 'Help' action or something like that. #If in the future an action is added/modified this object will need to be edited/updated. @@ -3317,6 +3376,7 @@ Resources: for db_name in db_list: # Execute schema permissions in the database conn_string = (f"host={host} dbname={db_name} port=5432 user={name} password={password}") + print("performing update for " + db_name) execute_query(conn_string, query) def validate_name(name): @@ -3386,6 +3446,8 @@ Resources: conn.set_isolation_level(ISOLATION_LEVEL_AUTOCOMMIT) with conn.cursor() as cursor: cursor.execute(query) + except errors.InvalidSchemaName as e: + print(f"Schema does not exist. Skipping schema permissions.", e) except psycopg2.Error as e: raise Exception("Error while attempting to change permissions.", e) finally: @@ -3438,6 +3500,8 @@ Resources: ChangeVersionFunctionLambdaFunction: Condition: UsePublisher Type: AWS::Lambda::Function + # checkov:skip=CKV_AWS_116:Ensure that AWS Lambda function is configured for a Dead Letter Queue(DLQ) + # checkov:skip=CKV_AWS_173:Check encryption settings for Lambda environment variable Properties: Handler: index.lambda_handler Layers: @@ -3553,6 +3617,137 @@ Resources: logGroupName: !Sub '/aws/lambda/${ChangeVersionFunctionLambdaFunction}' retentionInDays: 365 + QueryRunnerLambdaRole: + Type: AWS::IAM::Role + Properties: + AssumeRolePolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Principal: + Service: + - lambda.amazonaws.com + Action: + - sts:AssumeRole + ManagedPolicyArns: + - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole + - arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole + Path: / + Policies: + - PolicyName: !Sub '${EnvLabel}-lambda-query-runner-policy' + PolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Action: + - s3:GetBucketLocation + - s3:ListBucket + Resource: + - !Sub 'arn:${AWS::Partition}:s3:::${S3BucketSourceCode}' + - !Sub 'arn:${AWS::Partition}:s3:::${EnvLabel}-${Partner}-seed-data' + - Effect: Allow + Action: + - s3:GetObject + - s3:PutObject + Resource: + - !Sub 'arn:${AWS::Partition}:s3:::${S3BucketSourceCode}/*' + - !Sub 'arn:${AWS::Partition}:s3:::${EnvLabel}-${Partner}-seed-data/*' + - Effect: Allow + Action: secretsmanager:GetSecretValue + Resource: !Ref RDSSecret + QueryRunnerLambdaFunction: + # checkov:skip=CKV_AWS_116:Ensure that AWS Lambda function is configured for a Dead Letter Queue(DLQ) + # checkov:skip=CKV_AWS_173:Check encryption settings for Lambda environment variable + Type: AWS::Lambda::Function + Properties: + Handler: index.lambda_handler + Layers: + - !Ref 'PsycopgLambdaLayer' + Description: Runs a query in a database + FunctionName: !Sub '${EnvLabel}-QueryRunner' + Role: !GetAtt 'QueryRunnerLambdaRole.Arn' + Runtime: python3.11 + Timeout: 900 + ReservedConcurrentExecutions: 1 + VpcConfig: + SecurityGroupIds: + - !Ref 'LambdaRestoreSGID' + SubnetIds: + - !Ref 'PrivateSubnet1Id' + - !Ref 'PrivateSubnet2Id' + Environment: + Variables: + SECRET_ARN: !Ref RDSSecret + Code: + ZipFile: | + import json + import os + import boto3 + import psycopg2 + from psycopg2.extensions import ISOLATION_LEVEL_AUTOCOMMIT + + def lambda_handler(event, context): + # Get event input + query_s3_bucket = event.get('QueryS3Bucket') + query_s3_key = event.get('QueryS3Key') + database_name = event.get('DatabaseName') + print(database_name) + + # Get database connection details + secret_arn = os.environ['SECRET_ARN'] + secrets_manager = boto3.client('secretsmanager') + secret_response = secrets_manager.get_secret_value(SecretId=secret_arn) + secret_values = json.loads(secret_response['SecretString']) + host = secret_values['host'] + name = secret_values['username'] + password = secret_values['password'] + conn_string = (f"host={host} dbname={database_name} port=5432 user={name} password={password}") + + # Get query from S3 + s3 = boto3.client("s3") + s3_response = s3.get_object(Bucket=query_s3_bucket, Key=query_s3_key) + query = s3_response['Body'].read().decode('utf-8') + + # Execute query + try: + conn = psycopg2.connect(conn_string) + conn.set_isolation_level(ISOLATION_LEVEL_AUTOCOMMIT) + with conn.cursor() as cursor: + cursor.execute(query) + result = cursor.statusmessage + print(result) + conn.close() + status = "Success" + except Exception as e: + # Add error message to S3 output + status = "Error" + error_message = f"Error while attempting to execute the query on database {database_name}: {e}\n" + print(error_message) + query_filename = query_s3_key.split("/")[-1] + s3.put_object( + Bucket=query_s3_bucket, + Key=f"logs/{query_filename}-{database_name}.txt", + Body=error_message, + ContentType='text/plain') + finally: + if conn: + conn.close() + return { + "status": status, + "body": database_name + } + QueryRunnerLambdaVersion: + Type: AWS::Lambda::Version + Properties: + FunctionName: !Ref 'QueryRunnerLambdaFunction' + QueryRunnerLambdaRetention: + DependsOn: SetCloudWatchRetentionVersion + Type: Custom::CloudWatchRetention + Properties: + ServiceToken: !GetAtt 'SetCloudWatchRetentionLambdaFunction.Arn' + logGroupName: !Sub '/aws/lambda/${QueryRunnerLambdaFunction}' + retentionInDays: 365 + Outputs: DatabaseRestoreLambdaFunctionName: Description: Lambda Function Name to handle database restore operations. @@ -3567,6 +3762,9 @@ Outputs: CRHelperLambdaLayer: Value: !Ref 'CRHelperLambdaLayer' Description: Reference to the Custom Resource Helper Lambda Layer + PsycopgLambdaLayer: + Value: !Ref 'PsycopgLambdaLayer' + Description: Reference to the Psycopg Lambda Layer SetCloudWatchRetentionFunctionArn: Value: !GetAtt 'SetCloudWatchRetentionLambdaFunction.Arn' Description: Arn of the fuction to set CloudWatch log group retention @@ -3595,4 +3793,7 @@ Outputs: ChangeVersionLambdaArn: Condition: UsePublisher Description: Lambda ARN for ChangeVersion Function - Value: !GetAtt 'ChangeVersionFunctionLambdaFunction.Arn' \ No newline at end of file + Value: !GetAtt 'ChangeVersionFunctionLambdaFunction.Arn' + QueryRunnerLambdaArn: + Description: Lambda ARN for Query Runner Function + Value: !GetAtt 'QueryRunnerLambdaFunction.Arn' \ No newline at end of file diff --git a/templates/x-restore-databases.yml b/templates/x-restore-databases.yml index f9fbedf..96019de 100755 --- a/templates/x-restore-databases.yml +++ b/templates/x-restore-databases.yml @@ -86,7 +86,7 @@ Resources: DeletionPolicy: Retain Type: Custom::PostgresDBRestore Properties: - ServiceToken: !Sub 'arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:${LambdaFunctionName}' + ServiceToken: !Sub 'arn:${AWS::Partition}:lambda:${AWS::Region}:${AWS::AccountId}:function:${LambdaFunctionName}' Bucket: !Ref 'S3SourceBucket' BucketKey: !Sub '${S3SourceKeyName}/${AdminDbS3Name}.sql' Database: admin_default @@ -100,7 +100,7 @@ Resources: DependsOn: PostgresDBRestoreAdminDefault Type: Custom::PostgresDBRestore Properties: - ServiceToken: !Sub 'arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:${LambdaFunctionName}' + ServiceToken: !Sub 'arn:${AWS::Partition}:lambda:${AWS::Region}:${AWS::AccountId}:function:${LambdaFunctionName}' Bucket: !Ref 'S3SourceBucket' BucketKey: !Sub '${S3SourceKeyName}/${AdminApiDbS3Name}.sql' Database: admin_default @@ -112,7 +112,7 @@ Resources: DeletionPolicy: Retain Type: Custom::PostgresDBRestore Properties: - ServiceToken: !Sub 'arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:${LambdaFunctionName}' + ServiceToken: !Sub 'arn:${AWS::Partition}:lambda:${AWS::Region}:${AWS::AccountId}:function:${LambdaFunctionName}' Bucket: !Ref 'S3SourceBucket' BucketKey: !Sub '${S3SourceKeyName}/${AdminDbS3Name}.sql' Database: admin_template @@ -125,7 +125,7 @@ Resources: DependsOn: PostgresDBRestoreAdminTemplate Type: Custom::PostgresDBRestore Properties: - ServiceToken: !Sub 'arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:${LambdaFunctionName}' + ServiceToken: !Sub 'arn:${AWS::Partition}:lambda:${AWS::Region}:${AWS::AccountId}:function:${LambdaFunctionName}' Bucket: !Ref 'S3SourceBucket' BucketKey: !Sub '${S3SourceKeyName}/${AdminApiDbS3Name}.sql' Database: admin_template @@ -139,7 +139,7 @@ Resources: DeletionPolicy: Retain Type: Custom::PostgresDBRestore Properties: - ServiceToken: !Sub 'arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:${LambdaFunctionName}' + ServiceToken: !Sub 'arn:${AWS::Partition}:lambda:${AWS::Region}:${AWS::AccountId}:function:${LambdaFunctionName}' Bucket: !Ref 'S3SourceBucket' BucketKey: !Sub '${S3SourceKeyName}/${MinimalDbS3Name}.sql' Database: ods_default_default @@ -151,7 +151,7 @@ Resources: DeletionPolicy: Retain Type: Custom::PostgresDBRestore Properties: - ServiceToken: !Sub 'arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:${LambdaFunctionName}' + ServiceToken: !Sub 'arn:${AWS::Partition}:lambda:${AWS::Region}:${AWS::AccountId}:function:${LambdaFunctionName}' Bucket: !Ref 'S3SourceBucket' BucketKey: !Sub '${S3SourceKeyName}/${MinimalDbS3Name}.sql' Database: odst_default_minimal @@ -165,7 +165,7 @@ Resources: DeletionPolicy: Retain Type: Custom::PostgresDBRestore Properties: - ServiceToken: !Sub 'arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:${LambdaFunctionName}' + ServiceToken: !Sub 'arn:${AWS::Partition}:lambda:${AWS::Region}:${AWS::AccountId}:function:${LambdaFunctionName}' Bucket: !Ref 'S3SourceBucket' BucketKey: !Sub '${S3SourceKeyName}/${PopulatedDbS3Name}.sql' Database: ods_default_default @@ -177,7 +177,7 @@ Resources: DeletionPolicy: Retain Type: Custom::PostgresDBRestore Properties: - ServiceToken: !Sub 'arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:${LambdaFunctionName}' + ServiceToken: !Sub 'arn:${AWS::Partition}:lambda:${AWS::Region}:${AWS::AccountId}:function:${LambdaFunctionName}' Bucket: !Ref 'S3SourceBucket' BucketKey: !Sub '${S3SourceKeyName}/${PopulatedDbS3Name}.sql' Database: odst_default_populated @@ -191,7 +191,7 @@ Resources: DeletionPolicy: Retain Type: Custom::PostgresDBRestore Properties: - ServiceToken: !Sub 'arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:${LambdaFunctionName}' + ServiceToken: !Sub 'arn:${AWS::Partition}:lambda:${AWS::Region}:${AWS::AccountId}:function:${LambdaFunctionName}' Bucket: !Ref 'S3SourceBucket' BucketKey: !Sub '${S3SourceKeyName}/${SecurityDbS3Name}.sql' Database: security_default @@ -203,7 +203,7 @@ Resources: DeletionPolicy: Retain Type: Custom::PostgresDBRestore Properties: - ServiceToken: !Sub 'arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:${LambdaFunctionName}' + ServiceToken: !Sub 'arn:${AWS::Partition}:lambda:${AWS::Region}:${AWS::AccountId}:function:${LambdaFunctionName}' Bucket: !Ref 'S3SourceBucket' BucketKey: !Sub '${S3SourceKeyName}/${SecurityDbS3Name}.sql' Database: security_template diff --git a/templates/x-shared-resources.yml b/templates/x-shared-resources.yml index fc20737..f82c2db 100755 --- a/templates/x-shared-resources.yml +++ b/templates/x-shared-resources.yml @@ -11,7 +11,6 @@ Parameters: Type: String Description: Provide a label for your environment to identify resources easier. VpcId: - Default: '' Type: AWS::EC2::VPC::Id Description: VPC ID to create the security groups within the network SSHServerParentStack: @@ -45,21 +44,28 @@ Parameters: SSLPolicy: Description: Policy to determine which ssl/tls ciphers are accepted. Type: String - + S3AccessLogBucket: + Type: String + Description: S3 bucket to store access logs for ELB. Must be in the same region as the CF Stack. (optional) Conditions: AttachWAF: !Not [!Equals [!Ref WebACLArn, '']] UseAdminApi: !Equals [!Ref 'AdminInterface', "Ed-Fi Admin API"] + UseAdminApiIngress: !And [!Condition UseAdminApi, !Not [!Equals [!Join [',', !Ref AdminApiCIDRs], '']]] UseSSHParentStack: !Not - !Equals - !Ref SSHServerParentStack - '' + EnableAlbAccessLogs: + !Not [!Equals [!Ref S3AccessLogBucket, '']] Resources: KMSKey: DeletionPolicy: Retain UpdateReplacePolicy: Retain Type: AWS::KMS::Key + # checkov:skip=CKV_AWS_33: Ensure KMS key policy does not contain wildcard (*) principal + # Wildcard principal is scoped by Conditions Properties: Description: !Sub 'KMS key for the ${EnvLabel} Environment' EnableKeyRotation: true @@ -69,7 +75,7 @@ Resources: Statement: - Effect: Allow Principal: - AWS: !Sub 'arn:aws:iam::${AWS::AccountId}:root' + AWS: !Sub 'arn:${AWS::Partition}:iam::${AWS::AccountId}:root' Action: kms:* Resource: '*' - Effect: Allow @@ -135,6 +141,8 @@ Resources: VpcId: !Ref 'VpcId' RDSSecurityGroup: Type: AWS::EC2::SecurityGroup + # checkov:skip=CKV_AWS_23: Ensure every security groups rule has a description + # These security group rules do have descriptions, but are not being recognized by checkov Properties: GroupName: !Sub '${EnvLabel}-RDS-Servers' GroupDescription: !Sub 'Rules to allow access to RDS in the ${EnvLabel} environment' @@ -192,6 +200,14 @@ Resources: - kms:Decrypt - kms:GenerateDataKey* Resource: !GetAtt 'KMSKey.Arn' + - PolicyName: SNS + PolicyDocument: + Version: "2012-10-17" + Statement: + - Effect: Allow + Action: + - SNS:GetTopicAttributes + Resource: !Sub 'arn:${AWS::Partition}:sns:${AWS::Region}:${AWS::AccountId}:${EnvLabel}*' ElasticBeanstalkEC2Role: Type: AWS::IAM::Role Properties: @@ -221,8 +237,8 @@ Resources: - Effect: Allow Action: secretsmanager:GetSecretValue Resource: - - !Sub 'arn:aws:secretsmanager:${AWS::Region}:${AWS::AccountId}:secret:${EnvLabel}-AdminApiSecret-*' - - !Sub 'arn:aws:secretsmanager:${AWS::Region}:${AWS::AccountId}:secret:${EnvLabel}-WebApiSecret-*' + - !Sub 'arn:${AWS::Partition}:secretsmanager:${AWS::Region}:${AWS::AccountId}:secret:${EnvLabel}-AdminApiSecret-*' + - !Sub 'arn:${AWS::Partition}:secretsmanager:${AWS::Region}:${AWS::AccountId}:secret:${EnvLabel}-WebApiSecret-*' - Effect: Allow Action: - dynamodb:BatchGetItem @@ -230,17 +246,17 @@ Resources: - dynamodb:Query - dynamodb:Scan Resource: - - !Sub 'arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${EnvLabel}-tenants' + - !Sub 'arn:${AWS::Partition}:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${EnvLabel}-tenants' - PolicyName: 'AttachWebACL' PolicyDocument: Version: "2012-10-17" Statement: + - Effect: Allow + Action: ssm:GetParameter + Resource: !Sub 'arn:${AWS::Partition}:ssm:${AWS::Region}:${AWS::AccountId}:parameter/ed-fi/beanstalk/environments/${EnvLabel}-WebApi-Prod/parameters/*' - Effect: Allow Action: elasticloadbalancing:SetWebACL Resource: '*' - - Effect: Allow - Action: ssm:GetParameter - Resource: !Sub 'arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/ed-fi/beanstalk/environments/${EnvLabel}-WebApi-Prod/parameters/*' - PolicyName: 'CloudWatchAgent' PolicyDocument: Version: "2012-10-17" @@ -254,7 +270,15 @@ Resources: Statement: - Effect: Allow Action: autoscaling:SetInstanceHealth - Resource: '*' + Resource: !Sub 'arn:${AWS::Partition}:autoscaling:${AWS::Region}:${AWS::AccountId}:autoScalingGroup:*' + - PolicyName: KMSDecrypt + PolicyDocument: + Version: "2012-10-17" + Statement: + - Effect: Allow + Action: + - kms:Decrypt + Resource: !GetAtt 'KMSKey.Arn' ElasticBeanstalkEC2InstanceProfile: Type: 'AWS::IAM::InstanceProfile' Properties: @@ -277,6 +301,9 @@ Resources: # - !Ref ElasticBeanstalkEC2Role AuroraMasterSecret: Type: AWS::SecretsManager::Secret + DeletionPolicy: Delete + UpdateReplacePolicy: Delete + # checkov:skip=CKV_AWS_149: Ensure that Secrets Manager secret is encrypted using KMS CMK Properties: Name: !Sub '${EnvLabel}-AuroraMasterSecret' Description: !Sub 'Aurora PostgreSQL Master User Secret for EdFi Environment ${EnvLabel}' @@ -305,13 +332,21 @@ Resources: - Effect: "Deny" Principal: - AWS: !Sub "arn:aws:iam::${AWS::AccountId}:root" + AWS: !Sub "arn:${AWS::Partition}:iam::${AWS::AccountId}:root" Action: "secretsmanager:DeleteSecret" Resource: "*" TenantsDDBTable: Type: AWS::DynamoDB::Table + DeletionPolicy: Delete + UpdateReplacePolicy: Delete Properties: + PointInTimeRecoverySpecification: + PointInTimeRecoveryEnabled: true + SSESpecification: + SSEEnabled: true + KMSMasterKeyId: !Ref 'KMSKey' + SSEType: 'KMS' AttributeDefinitions: - AttributeName: 'Name' @@ -328,6 +363,7 @@ Resources: WildcardCertificate: Type: "AWS::CertificateManager::Certificate" + # checkov:skip=CKV2_AWS_71:Ensure AWS ACM Certificate domain name does not include wildcards Properties: DomainName: !Sub '*.${DomainName}' SubjectAlternativeNames: @@ -339,6 +375,8 @@ Resources: SharedAlbSecurityGroup: Type: AWS::EC2::SecurityGroup + # checkov:skip=CKV_AWS_260: Ensure no security groups allow ingress from 0.0.0.0:0 to port 80 + # ALB redirects traffic on port 80 to port 443 Properties: GroupName: !Sub '${EnvLabel}-shared-alb' GroupDescription: !Sub 'Shared application load balancer for the ${EnvLabel} environment' @@ -360,7 +398,7 @@ Resources: - !Ref AdminApiCIDRs - 'AdminApiIngress&{CidrIp}': Type: 'AWS::EC2::SecurityGroupIngress' - Condition: UseAdminApi + Condition: UseAdminApiIngress Properties: Description: Admin API GroupId: !Ref SharedAlbSecurityGroup @@ -371,11 +409,26 @@ Resources: SharedAlb: Type: AWS::ElasticLoadBalancingV2::LoadBalancer + # checkov:skip=CKV_AWS_91: Ensure the ELBv2 (Application/Network) has logging enabled + # Logging is optionally enabled for ease of OSS deployment Properties: IpAddressType: ipv4 LoadBalancerAttributes: - Key: waf.fail_open.enabled Value: true + - Key: routing.http.drop_invalid_header_fields.enabled + Value: true + - !If + - EnableAlbAccessLogs + - Key: access_logs.s3.enabled + Value: true + - Key: access_logs.s3.enabled + Value: false + - !If + - EnableAlbAccessLogs + - Key: access_logs.s3.bucket + Value: !Ref S3AccessLogBucket + - !Ref 'AWS::NoValue' Name: !Sub '${EnvLabel}-shared-alb' Scheme: internet-facing SecurityGroups: @@ -450,6 +503,7 @@ Resources: SharedAlbHTTPSListener: Type: AWS::ElasticLoadBalancingV2::Listener + # checkov:skip=CKV_AWS_103:Ensure that Load Balancer Listener is using at least TLS v1.2 Properties: LoadBalancerArn: !Ref SharedAlb Port: 443 @@ -466,6 +520,7 @@ Resources: SharedAlbAdminApiListener: Type: AWS::ElasticLoadBalancingV2::Listener + # checkov:skip=CKV_AWS_103:Ensure that Load Balancer Listener is using at least TLS v1.2 Condition: UseAdminApi Properties: LoadBalancerArn: !Ref SharedAlb @@ -484,6 +539,7 @@ Resources: # Blocked by security group, and fixed-response rule. Will house Beanstalk default rules. SharedAlbDummyListener: Type: AWS::ElasticLoadBalancingV2::Listener + # checkov:skip=CKV_AWS_103:Ensure that Load Balancer Listener is using at least TLS v1.2 Properties: LoadBalancerArn: !Ref SharedAlb Port: 14443 diff --git a/templates/x-state-machines.yml b/templates/x-state-machines.yml new file mode 100644 index 0000000..da31de6 --- /dev/null +++ b/templates/x-state-machines.yml @@ -0,0 +1,105 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at https://mozilla.org/MPL/2.0/. +AWSTemplateFormatVersion: '2010-09-09' +Description: >- + Deploys a State Machine to run queries in multiple databases. +Parameters: + EnvLabel: + Description: Provide a label for your environment to identify resources easier. + Type: String + QueryRunnerLambdaArn: + Type: String + Description: ARN of the Query Runner Lambda Function + +Resources: + QueryRunnerStateMachineRole: + Type: AWS::IAM::Role + Properties: + AssumeRolePolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Principal: + Service: + - states.amazonaws.com + Action: + - sts:AssumeRole + Path: / + Policies: + - PolicyName: AccessPolicy + PolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Action: + - lambda:InvokeFunction + Resource: !Ref 'QueryRunnerLambdaArn' + QueryRunnerStateMachine: + Type: AWS::StepFunctions::StateMachine + Properties: + DefinitionString: |- + { + "Comment": "Executes a query for a list of databases", + "StartAt": "Iterate through ODSs", + "States": { + "Iterate through ODSs": { + "Type": "Map", + "ItemProcessor": { + "ProcessorConfig": { + "Mode": "INLINE" + }, + "StartAt": "Execute query", + "States": { + "Execute query": { + "Type": "Task", + "Resource": "arn:aws:states:::lambda:invoke", + "OutputPath": "$.Payload", + "Parameters": { + "Payload.$": "$", + "FunctionName": "${LambdaArn}" + }, + "Retry": [ + { + "ErrorEquals": [ + "Lambda.ServiceException", + "Lambda.AWSLambdaException", + "Lambda.SdkClientException", + "Lambda.TooManyRequestsException" + ], + "IntervalSeconds": 1, + "MaxAttempts": 3, + "BackoffRate": 2 + } + ], + "End": true + } + } + }, + "ItemsPath": "$.DatabaseList", + "ItemSelector": { + "LambdaArn": "${LambdaArn}", + "QueryS3Bucket.$": "$.QueryS3Bucket", + "QueryS3Key.$": "$.QueryS3Key", + "DatabaseName.$": "$$.Map.Item.Value" + }, + "MaxConcurrency": 10, + "Next": "Filter results" + }, + "Filter results": { + "Type": "Pass", + "End": true, + "InputPath": "$[?(@.status != Success)]", + "Parameters": { + "failures.$": "States.Array($[*].body)" + } + } + } + } + DefinitionSubstitutions: + LambdaArn: !Ref 'QueryRunnerLambdaArn' + RoleArn: !GetAtt 'QueryRunnerStateMachineRole.Arn' + StateMachineName: !Sub 'QueryRunner-${EnvLabel}' + Tags: + - Key: Environment + Value: !Ref 'EnvLabel' \ No newline at end of file diff --git a/templates/x-swagger.yml b/templates/x-swagger.yml index 091a04e..1f4f258 100644 --- a/templates/x-swagger.yml +++ b/templates/x-swagger.yml @@ -28,9 +28,20 @@ Parameters: Type: String Description: Select partner for which Starting Blocks environment will be deployed. Default: 'ea' + LambdaDefaultSGID: + Type: AWS::EC2::SecurityGroup::Id + Description: Security Group ID to use with the Lambda Functions by default.' + PrivateSubnet1Id: + Type: AWS::EC2::Subnet::Id + Description: ID of the private subnet 1 in Availability Zone 1 (e.g., subnet-a0246dcd) + PrivateSubnet2Id: + Type: AWS::EC2::Subnet::Id + Description: ID of the private subnet 2 in Availability Zone 2 (e.g., subnet-a0246dcd) Resources: S3SwaggerFEBucket: Type: AWS::S3::Bucket + # checkov:skip=CKV_AWS_18: Ensure the S3 bucket has access logging enabled + # S3 bucket does not need access logging enabled as it is not publicly accessible and does not contain PII. UpdateReplacePolicy: Retain DeletionPolicy: Delete Properties: @@ -38,6 +49,13 @@ Resources: OwnershipControls: Rules: - ObjectOwnership: BucketOwnerEnforced + VersioningConfiguration: + Status: Enabled + PublicAccessBlockConfiguration: + BlockPublicAcls: true + IgnorePublicAcls: true + BlockPublicPolicy: true + RestrictPublicBuckets: true Tags: - Key: EnvLabel Value: !Ref EnvLabel @@ -50,9 +68,9 @@ Resources: - Action: - 's3:GetObject' Effect: Allow - Resource: !Sub 'arn:aws:s3:::${S3SwaggerFEBucket}/*' + Resource: !Sub 'arn:${AWS::Partition}:s3:::${S3SwaggerFEBucket}/*' Principal: - AWS: !Sub 'arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity ${CloudFrontOriginAccessIdentity}' + AWS: !Sub 'arn:${AWS::Partition}:iam::cloudfront:user/CloudFront Origin Access Identity ${CloudFrontOriginAccessIdentity}' CloudFrontOriginAccessIdentity: Type: AWS::CloudFront::CloudFrontOriginAccessIdentity Properties: @@ -73,6 +91,7 @@ Resources: Path: / ManagedPolicyArns: - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole + - arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole Policies: - PolicyName: 'AllowActions' PolicyDocument: @@ -80,15 +99,22 @@ Resources: Statement: - Effect: Allow Action: - - acm:ListCertificates - - acm:DescribeCertificate - acm:RequestCertificate - acm:DeleteCertificate - - route53:ListHostedZonesByName - route53:ChangeResourceRecordSets + - acm:DescribeCertificate + Resource: + - !Sub 'arn:${AWS::Partition}:route53:::hostedzone/${HostedZoneId}' + - !Sub 'arn:${AWS::Partition}:acm:${AWS::Region}:${AWS::AccountId}:*' + - !Sub 'arn:${AWS::Partition}:acm:us-east-1:${AWS::AccountId}:certificate/*' + - Effect: Allow + Action: + - acm:ListCertificates + - route53:ListHostedZonesByName Resource: '*' East1CertFunction: Type: AWS::Lambda::Function + # checkov:skip=CKV_AWS_116:Ensure that AWS Lambda function is configured for a Dead Letter Queue(DLQ) Properties: Handler: index.lambda_handler Layers: @@ -97,6 +123,13 @@ Resources: FunctionName: !Sub '${EnvLabel}-swagger-east-1-cert' Role: !GetAtt 'East1CertRole.Arn' Runtime: python3.11 + ReservedConcurrentExecutions: 1 + VpcConfig: + SecurityGroupIds: + - !Ref 'LambdaDefaultSGID' + SubnetIds: + - !Ref 'PrivateSubnet1Id' + - !Ref 'PrivateSubnet2Id' Timeout: 600 Code: ZipFile: | @@ -259,6 +292,9 @@ Resources: CloudFront: Type: AWS::CloudFront::Distribution + # checkov:skip=CKV_AWS_68: CloudFront Distribution should have WAF enabled + # CloudFront is in front of S3 in this case which hosts static files. We accept the risk for static content in forgoing WAF in this case. + # checkov:skip=CKV_AWS_86: Ensure CloudFront Distribution has Access Logging enabled Properties: DistributionConfig: DefaultRootObject: index.html @@ -332,7 +368,6 @@ Resources: Statement: - Effect: Allow Action: - - ec2:DescribeSubnets - ec2:DescribeAvailabilityZones Resource: - '*' @@ -340,7 +375,7 @@ Resources: Action: - cloudfront:CreateInvalidation Resource: - - !Sub 'arn:aws:cloudfront::${AWS::AccountId}:distribution/${CloudFront}' + - !Sub 'arn:${AWS::Partition}:cloudfront::${AWS::AccountId}:distribution/${CloudFront}' - Effect: Allow Action: - s3:DeleteObject @@ -351,8 +386,8 @@ Resources: - s3:ListBucketVersions - s3:PutObject Resource: - - !Sub 'arn:aws:s3:::${S3SwaggerFEBucket}' - - !Sub 'arn:aws:s3:::${S3SwaggerFEBucket}/*' + - !Sub 'arn:${AWS::Partition}:s3:::${S3SwaggerFEBucket}' + - !Sub 'arn:${AWS::Partition}:s3:::${S3SwaggerFEBucket}/*' Roles: - !Ref 'SwaggerLambdaRole' SwaggerLambdaAWSCLILayer: @@ -372,13 +407,17 @@ Resources: DeletionPolicy: Delete Properties: ServiceToken: !GetAtt SwaggerUpdateLambda.Arn + Bump: 1 # fake parameter to cause the function to run in existing deployments. SwaggerUpdateLambda: Type: 'AWS::Lambda::Function' + # checkov:skip=CKV_AWS_116:Ensure that AWS Lambda function is configured for a Dead Letter Queue(DLQ) + # checkov:skip=CKV_AWS_173:Check encryption settings for Lambda environment variable Properties: Layers: - !Ref SwaggerLambdaAWSCLILayer - !Ref 'CRHelperLambdaLayer' - Description: Creates lambda to update Swagger UI + ReservedConcurrentExecutions: 1 + Description: Updates and deploys Swagger UI in S3 Handler: index.lambda_handler Environment: Variables: @@ -388,6 +427,12 @@ Resources: Runtime: python3.11 MemorySize: 128 Role: !GetAtt 'SwaggerLambdaRole.Arn' + VpcConfig: + SecurityGroupIds: + - !Ref 'LambdaDefaultSGID' + SubnetIds: + - !Ref 'PrivateSubnet1Id' + - !Ref 'PrivateSubnet2Id' Timeout: 60 Code: ZipFile: | @@ -444,7 +489,7 @@ Resources: ) content = content.replace( 'deepLinking: true,', - 'deepLinking: true, showExtensions: true,' + 'deepLinking: true, showExtensions: true, docExpansion: "none",' ) print(content) @@ -500,4 +545,4 @@ Outputs: Value: !Ref CloudFront SwaggerURL: Description: The URL for swagger - Value: !Join ['', ['swagger-ui.', !Ref DomainName]] \ No newline at end of file + Value: !Sub 'swagger-ui.${DomainName}' \ No newline at end of file