From 3deed249448048508cbf793877a9f23399dfe3fa Mon Sep 17 00:00:00 2001 From: Ronaldo Macapobre Date: Wed, 13 Nov 2024 18:19:31 +0000 Subject: [PATCH] - Fixed failing build in Publish Lambda Functions GHA - Added Authorizer and Rotate Key Lambda Functions - Update Terraform code to support Authorizer - Added implementation to restart ECS service when key is rotated - Updates to TF to allow lambda to update secret and restart ECS service - Use x-origin-verify header and pass to http client - Update devcontainer to force .tf files to be formatted by TF extension - Added APIGW Settings for logging --- .devcontainer/devcontainer.json | 13 +- .../actions/deploy-lambda/action.yml | 4 +- .github/workflows/publish-lambdas.yml | 7 +- .../ServiceCollectionExtensions.cs | 10 +- aws/.prettierrc | 3 + aws/helpers/getSecret.ts | 24 - aws/lambdas/auth/authorizer/index.ts | 105 ++ aws/lambdas/auth/rotate-key/index.ts | 63 ++ aws/package-lock.json | 958 ++++++++++++++---- aws/package.json | 5 +- aws/services/ecsService.ts | 67 ++ aws/services/secretsManagerService.ts | 31 + .../cloud/environments/dev/webapp.tf | 67 +- .../cloud/modules/APIGateway/main.tf | 89 +- .../cloud/modules/APIGateway/variables.tf | 10 + .../modules/Cloudwatch/LogGroup/outputs.tf | 4 +- infrastructure/cloud/modules/ECR/outputs.tf | 3 + .../cloud/modules/ECS/Cluster/outputs.tf | 4 +- .../cloud/modules/ECS/Service/outputs.tf | 3 + .../cloud/modules/ECS/TaskDefinition/main.tf | 12 +- .../modules/ECS/TaskDefinition/variables.tf | 5 + infrastructure/cloud/modules/IAM/main.tf | 30 +- infrastructure/cloud/modules/KMS/main.tf | 17 +- infrastructure/cloud/modules/Lambda/main.tf | 43 +- .../cloud/modules/Lambda/outputs.tf | 5 +- .../cloud/modules/Lambda/variables.tf | 33 +- .../cloud/modules/SecretsManager/main.tf | 24 + .../cloud/modules/SecretsManager/output.tf | 8 +- .../cloud/modules/SecretsManager/variables.tf | 5 + 29 files changed, 1374 insertions(+), 278 deletions(-) create mode 100644 aws/.prettierrc delete mode 100644 aws/helpers/getSecret.ts create mode 100644 aws/lambdas/auth/authorizer/index.ts create mode 100644 aws/lambdas/auth/rotate-key/index.ts create mode 100644 aws/services/ecsService.ts create mode 100644 aws/services/secretsManagerService.ts diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 215c7876..32d67882 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -22,13 +22,22 @@ "ms-dotnettools.csdevkit", "ms-dotnettools.csharp", "ms-dotnettools.vscode-dotnet-runtime", - "amazonwebservices.aws-toolkit-vscode" + "amazonwebservices.aws-toolkit-vscode", + "hashicorp.terraform" ], "settings": { "editor.codeActionsOnSave": { "source.organizeImports": "explicit" }, - "editor.formatOnSave": true + "editor.formatOnSave": true, + "editor.defaultFormatter": "esbenp.prettier-vscode", + "[csharp]": { + "editor.defaultFormatter": "ms-dotnettools.csharp" + }, + "[terraform]": { + "editor.formatOnSave": true, + "editor.defaultFormatter": "hashicorp.terraform" + } } } }, diff --git a/.github/workflows/actions/deploy-lambda/action.yml b/.github/workflows/actions/deploy-lambda/action.yml index 529817a4..0a714c1a 100644 --- a/.github/workflows/actions/deploy-lambda/action.yml +++ b/.github/workflows/actions/deploy-lambda/action.yml @@ -68,7 +68,7 @@ runs: id: ecr-check shell: bash run: | - IMAGE_TAG=${{ inputs.lambda_name }}-${{ inputs.short_sha }} + IMAGE_TAG=${{ inputs.image_name }}-${{ inputs.short_sha }} REPOSITORY_NAME=${{ inputs.app_name }}-lambda-repo-${{ inputs.environment }} IMAGE_EXISTS=$(aws ecr describe-images --repository-name $REPOSITORY_NAME --query "imageDetails[?contains(imageTags, '$IMAGE_TAG')]" --output text) @@ -94,4 +94,4 @@ runs: run: | aws lambda update-function-code \ --function-name ${{ inputs.app_name }}-${{ inputs.lambda_name }}-lambda-${{ inputs.environment }} \ - --image-uri ${{ env.full_ecr_repo_url }}:${{ inputs.resource }}.${{ inputs.lambda_name }}-${{ inputs.short_sha }} + --image-uri ${{ steps.vars.outputs.full_ecr_repo_url }}:${{ inputs.image_name }}-${{ inputs.short_sha }} diff --git a/.github/workflows/publish-lambdas.yml b/.github/workflows/publish-lambdas.yml index 8b58ad2b..fe113941 100644 --- a/.github/workflows/publish-lambdas.yml +++ b/.github/workflows/publish-lambdas.yml @@ -28,6 +28,7 @@ jobs: runs-on: ubuntu-latest outputs: lambda_dir_list: ${{ steps.convert.outputs.LAMBDA_DIR_LIST }} + steps: - name: Checkout repository uses: actions/checkout@v4 @@ -51,6 +52,8 @@ jobs: deploy2gchr: needs: get-lambdas environment: ${{ inputs.environment }} + outputs: + short_sha: ${{ steps.short_sha.outputs.SHORT_SHA }} permissions: id-token: write packages: write @@ -82,7 +85,7 @@ jobs: - name: Get short SHA id: short_sha run: | - echo "SHORT_SHA=$(git rev-parse --short HEAD)" >> $GITHUB_ENV + echo "SHORT_SHA=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT - name: Parse Resource and Lambda Name id: parse @@ -100,7 +103,7 @@ jobs: images: | ${{ env.GITHUB_IMAGE_REPO }}/${{ env.RESOURCE }}.${{ env.LAMBDA }} tags: | - type=raw,value=${{ env.SHORT_SHA }} + type=raw,value=${{ steps.short_sha.outputs.SHORT_SHA }} - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 diff --git a/api/Infrastructure/ServiceCollectionExtensions.cs b/api/Infrastructure/ServiceCollectionExtensions.cs index 43dc10fa..dd432956 100644 --- a/api/Infrastructure/ServiceCollectionExtensions.cs +++ b/api/Infrastructure/ServiceCollectionExtensions.cs @@ -22,6 +22,9 @@ namespace Scv.Api.Infrastructure { public static class ServiceCollectionExtensions { + const string X_APIGW_KEY_HEADER = "x-api-key"; + const string X_ORIGIN_VERIFY_HEADER = "x-origin-verify"; + public static IServiceCollection AddMapster(this IServiceCollection services, Action options = null) { var config = TypeAdapterConfig.GlobalSettings; @@ -37,6 +40,9 @@ public static IServiceCollection AddMapster(this IServiceCollection services, Ac public static IServiceCollection AddHttpClientsAndScvServices(this IServiceCollection services, IConfiguration configuration) { + var apigwKey = configuration.GetNonEmptyValue("AWS_API_GATEWAY_API_KEY"); + var authorizerKey = configuration.GetNonEmptyValue("AuthorizerKey"); + services.AddTransient(); services.AddHttpClient(client => { @@ -61,8 +67,8 @@ public static IServiceCollection AddHttpClientsAndScvServices(this IServiceColle // configuration.GetNonEmptyValue("LocationServicesClient:Username"), // configuration.GetNonEmptyValue("LocationServicesClient:Password")); client.BaseAddress = new Uri(configuration.GetNonEmptyValue("LocationServicesClient:Url").EnsureEndingForwardSlash()); - var apiKey = configuration.GetNonEmptyValue("AWS_API_GATEWAY_API_KEY"); - client.DefaultRequestHeaders.Add("x-api-key", apiKey); + client.DefaultRequestHeaders.Add(X_APIGW_KEY_HEADER, apigwKey); + client.DefaultRequestHeaders.Add(X_ORIGIN_VERIFY_HEADER, authorizerKey); }).AddHttpMessageHandler(); services.AddHttpClient(client => diff --git a/aws/.prettierrc b/aws/.prettierrc new file mode 100644 index 00000000..732e2200 --- /dev/null +++ b/aws/.prettierrc @@ -0,0 +1,3 @@ +{ + "semi": true +} diff --git a/aws/helpers/getSecret.ts b/aws/helpers/getSecret.ts deleted file mode 100644 index 8ba2428b..00000000 --- a/aws/helpers/getSecret.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { - GetSecretValueCommand, - SecretsManagerClient -} from "@aws-sdk/client-secrets-manager" - -const secretsManagerClient = new SecretsManagerClient() - -export const getSecret = async (secretName: string): Promise => { - try { - const command = new GetSecretValueCommand({ SecretId: secretName }) - const data = await secretsManagerClient.send(command) - - if (data.SecretString) { - return data.SecretString - } else { - throw new Error( - `Secret with ID ${secretName} does not contain SecretString` - ) - } - } catch (error) { - console.error(`Error retrieving secret ${secretName}:`, error) - throw error - } -} diff --git a/aws/lambdas/auth/authorizer/index.ts b/aws/lambdas/auth/authorizer/index.ts new file mode 100644 index 00000000..f6834a79 --- /dev/null +++ b/aws/lambdas/auth/authorizer/index.ts @@ -0,0 +1,105 @@ +import { Logger } from "@aws-lambda-powertools/logger"; +import { + APIGatewayAuthorizerResult, + APIGatewayRequestAuthorizerEvent, + Context, + PolicyDocument, + StatementEffect, +} from "aws-lambda"; +import { v4 as uuidv4 } from "uuid"; +import SecretsManagerService from "../../../services/secretsManagerService"; + +const X_ORIGIN_VERIFY_HEADER = "x-origin-verify"; + +export const handler = async ( + event: APIGatewayRequestAuthorizerEvent, + context: Context +): Promise => { + console.log(`Event: ${JSON.stringify(event, null, 2)}`); + console.log(`Context: ${JSON.stringify(context, null, 2)}`); + + const correlationId: string = event.requestContext.requestId || uuidv4(); + const logger = new Logger({ + serviceName: "auth.authorizer", + }); + + try { + if (!event.headers) { + logger.error("headers is missing."); + throw new Error("Error: invalid token"); + } + + // x-verify-origin should be set by the caller + if (!(X_ORIGIN_VERIFY_HEADER in event.headers)) { + logger.error(`${X_ORIGIN_VERIFY_HEADER} not found in headers.`); + throw new Error("Error: invalid token"); + } + + // Extract the token from the request + const verifyToken = event.headers[X_ORIGIN_VERIFY_HEADER]; + const smService = new SecretsManagerService(); + const secretStringJson = await smService.getSecret( + process.env.VERIFY_SECRET_NAME! + ); + + let verifyTokenfromSecretManager = ""; + if (secretStringJson) { + verifyTokenfromSecretManager = JSON.parse(secretStringJson).verifyKey; + logger.info( + "Authorization token from secret manager", + verifyTokenfromSecretManager + ); + } else { + logger.error("Secret not found in secret manager"); + throw new Error("Error: invalid token"); + } + + if (verifyToken !== verifyTokenfromSecretManager) { + logger.error("Authorization token not valid"); + throw new Error("Error: invalid token"); + } + + const policy = generatePolicy( + correlationId, + "user", + "Allow", + event.methodArn + ); + + logger.info(JSON.stringify(policy)); + + return policy; + } catch (error) { + logger.error(error); + + throw new Error("Unauthorized"); + } +}; + +const generatePolicy = ( + correlationId: string, + principalId: string, + effect: StatementEffect, + resource: string +): APIGatewayAuthorizerResult => { + const policyDocument: PolicyDocument = { + Version: "2012-10-17", + Statement: [ + { + Action: "execute-api:Invoke", + Effect: effect, + Resource: resource, + }, + ], + }; + + const authResponse: APIGatewayAuthorizerResult = { + principalId, + context: { + correlation_id: correlationId, + }, + policyDocument, + }; + + return authResponse; +}; diff --git a/aws/lambdas/auth/rotate-key/index.ts b/aws/lambdas/auth/rotate-key/index.ts new file mode 100644 index 00000000..2a3af778 --- /dev/null +++ b/aws/lambdas/auth/rotate-key/index.ts @@ -0,0 +1,63 @@ +import { Logger } from "@aws-lambda-powertools/logger"; +import { APIGatewayEvent, APIGatewayProxyResult, Context } from "aws-lambda"; +import { v4 as uuidv4 } from "uuid"; +import ECSService from "../../../services/ecsService"; +import SecretsManagerService from "../../../services/secretsManagerService"; + +export const handler = async ( + event: APIGatewayEvent, + context: Context +): Promise => { + console.log(`Event: ${JSON.stringify(event, null, 2)}`); + console.log(`Context: ${JSON.stringify(context, null, 2)}`); + + const logger = new Logger({ + serviceName: "auth.rotate-key", + }); + + try { + logger.info("Rotating verifyKey."); + await updateSecret(); + logger.info("Successfully rotated verifyKey"); + + logger.info("Restarting ECS Services to pickup updated VerifyKey."); + await restartECSServices(); + logger.info("Restart completed."); + + return { + statusCode: 200, + body: JSON.stringify({ + message: + "Successfully rotated the key and restarted the ECS Service/TD", + }), + }; + } catch (error) { + logger.error(error); + + return { + statusCode: 500, + body: JSON.stringify({ + message: + "Something went wrong when updating the key and restarting the ECS Service/TD", + }), + }; + } +}; + +const updateSecret = async () => { + const smService = new SecretsManagerService(); + const newGuid = uuidv4(); + + await smService.updateSecret( + process.env.VERIFY_SECRET_NAME!, + JSON.stringify({ verifyKey: newGuid }) + ); +}; + +const restartECSServices = async () => { + const ecsService = new ECSService(process.env.CLUSTER_NAME!); + + const services = await ecsService.getECSServices(); + + await ecsService.restartServices(services); +}; diff --git a/aws/package-lock.json b/aws/package-lock.json index 61da6cd3..361ff098 100644 --- a/aws/package-lock.json +++ b/aws/package-lock.json @@ -9,9 +9,12 @@ "version": "1.0.0", "license": "ISC", "dependencies": { + "@aws-lambda-powertools/logger": "^2.10.0", + "@aws-sdk/client-ecs": "^3.692.0", "@aws-sdk/client-secrets-manager": "^3.686.0", "axios": "^1.7.7", - "glob": "^11.0.0" + "glob": "^11.0.0", + "uuid": "^11.0.3" }, "devDependencies": { "@types/aws-lambda": "^8.10.145", @@ -149,6 +152,576 @@ "node": ">=14.0.0" } }, + "node_modules/@aws-lambda-powertools/commons": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@aws-lambda-powertools/commons/-/commons-2.10.0.tgz", + "integrity": "sha512-XHC0FW/k33JWc2SZ9pbOJQPzNCMoRGnRuX/Cx1CYXRvRnaLYkPNRPrPYvGWy1OInAS3GSK6zxP1Asx0k4uaqSw==", + "license": "MIT-0" + }, + "node_modules/@aws-lambda-powertools/logger": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@aws-lambda-powertools/logger/-/logger-2.10.0.tgz", + "integrity": "sha512-QsVnoCBf6gKneGmSrIj1nxf34jWWOyDu4PO3I+VwkaH60lph2dIVcF1pP1zZagxUoYt9WWiddHcGQ7ORo0z5iw==", + "license": "MIT-0", + "dependencies": { + "@aws-lambda-powertools/commons": "^2.10.0", + "lodash.merge": "^4.6.2" + }, + "peerDependencies": { + "@middy/core": "4.x || 5.x" + }, + "peerDependenciesMeta": { + "@middy/core": { + "optional": true + } + } + }, + "node_modules/@aws-sdk/client-ecs": { + "version": "3.692.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-ecs/-/client-ecs-3.692.0.tgz", + "integrity": "sha512-hMMmRxx6ISP6v9/56aj7xp/A+SJSGjjKPZvK6/+zl88Z5nhDSe2rxQBoZhb+ZpQ0TAfjrL6Nz5H8AfXlvIb1gw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/client-sso-oidc": "3.692.0", + "@aws-sdk/client-sts": "3.692.0", + "@aws-sdk/core": "3.692.0", + "@aws-sdk/credential-provider-node": "3.692.0", + "@aws-sdk/middleware-host-header": "3.692.0", + "@aws-sdk/middleware-logger": "3.692.0", + "@aws-sdk/middleware-recursion-detection": "3.692.0", + "@aws-sdk/middleware-user-agent": "3.692.0", + "@aws-sdk/region-config-resolver": "3.692.0", + "@aws-sdk/types": "3.692.0", + "@aws-sdk/util-endpoints": "3.692.0", + "@aws-sdk/util-user-agent-browser": "3.692.0", + "@aws-sdk/util-user-agent-node": "3.692.0", + "@smithy/config-resolver": "^3.0.11", + "@smithy/core": "^2.5.2", + "@smithy/fetch-http-handler": "^4.1.0", + "@smithy/hash-node": "^3.0.9", + "@smithy/invalid-dependency": "^3.0.9", + "@smithy/middleware-content-length": "^3.0.11", + "@smithy/middleware-endpoint": "^3.2.2", + "@smithy/middleware-retry": "^3.0.26", + "@smithy/middleware-serde": "^3.0.9", + "@smithy/middleware-stack": "^3.0.9", + "@smithy/node-config-provider": "^3.1.10", + "@smithy/node-http-handler": "^3.3.0", + "@smithy/protocol-http": "^4.1.6", + "@smithy/smithy-client": "^3.4.3", + "@smithy/types": "^3.7.0", + "@smithy/url-parser": "^3.0.9", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.26", + "@smithy/util-defaults-mode-node": "^3.0.26", + "@smithy/util-endpoints": "^2.1.5", + "@smithy/util-middleware": "^3.0.9", + "@smithy/util-retry": "^3.0.9", + "@smithy/util-utf8": "^3.0.0", + "@smithy/util-waiter": "^3.1.8", + "@types/uuid": "^9.0.1", + "tslib": "^2.6.2", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-ecs/node_modules/@aws-sdk/client-sso": { + "version": "3.692.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.692.0.tgz", + "integrity": "sha512-YjielVjtz0VrCuE6j4Own0N+E4xSBK8AIocrL39s7eOntaRjxmdxtaPN+vAE3FenM7ltpLxpoFjQ262VLXid5Q==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.692.0", + "@aws-sdk/middleware-host-header": "3.692.0", + "@aws-sdk/middleware-logger": "3.692.0", + "@aws-sdk/middleware-recursion-detection": "3.692.0", + "@aws-sdk/middleware-user-agent": "3.692.0", + "@aws-sdk/region-config-resolver": "3.692.0", + "@aws-sdk/types": "3.692.0", + "@aws-sdk/util-endpoints": "3.692.0", + "@aws-sdk/util-user-agent-browser": "3.692.0", + "@aws-sdk/util-user-agent-node": "3.692.0", + "@smithy/config-resolver": "^3.0.11", + "@smithy/core": "^2.5.2", + "@smithy/fetch-http-handler": "^4.1.0", + "@smithy/hash-node": "^3.0.9", + "@smithy/invalid-dependency": "^3.0.9", + "@smithy/middleware-content-length": "^3.0.11", + "@smithy/middleware-endpoint": "^3.2.2", + "@smithy/middleware-retry": "^3.0.26", + "@smithy/middleware-serde": "^3.0.9", + "@smithy/middleware-stack": "^3.0.9", + "@smithy/node-config-provider": "^3.1.10", + "@smithy/node-http-handler": "^3.3.0", + "@smithy/protocol-http": "^4.1.6", + "@smithy/smithy-client": "^3.4.3", + "@smithy/types": "^3.7.0", + "@smithy/url-parser": "^3.0.9", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.26", + "@smithy/util-defaults-mode-node": "^3.0.26", + "@smithy/util-endpoints": "^2.1.5", + "@smithy/util-middleware": "^3.0.9", + "@smithy/util-retry": "^3.0.9", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-ecs/node_modules/@aws-sdk/client-sso-oidc": { + "version": "3.692.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.692.0.tgz", + "integrity": "sha512-2t2YDQej7mmh78l+0fM3pEsfQrmzVXU+G/TFYQGtkF0KpmReOphXL6K5I4OGHALvOZ2qmi/kGU9lYRfiTPmGig==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.692.0", + "@aws-sdk/credential-provider-node": "3.692.0", + "@aws-sdk/middleware-host-header": "3.692.0", + "@aws-sdk/middleware-logger": "3.692.0", + "@aws-sdk/middleware-recursion-detection": "3.692.0", + "@aws-sdk/middleware-user-agent": "3.692.0", + "@aws-sdk/region-config-resolver": "3.692.0", + "@aws-sdk/types": "3.692.0", + "@aws-sdk/util-endpoints": "3.692.0", + "@aws-sdk/util-user-agent-browser": "3.692.0", + "@aws-sdk/util-user-agent-node": "3.692.0", + "@smithy/config-resolver": "^3.0.11", + "@smithy/core": "^2.5.2", + "@smithy/fetch-http-handler": "^4.1.0", + "@smithy/hash-node": "^3.0.9", + "@smithy/invalid-dependency": "^3.0.9", + "@smithy/middleware-content-length": "^3.0.11", + "@smithy/middleware-endpoint": "^3.2.2", + "@smithy/middleware-retry": "^3.0.26", + "@smithy/middleware-serde": "^3.0.9", + "@smithy/middleware-stack": "^3.0.9", + "@smithy/node-config-provider": "^3.1.10", + "@smithy/node-http-handler": "^3.3.0", + "@smithy/protocol-http": "^4.1.6", + "@smithy/smithy-client": "^3.4.3", + "@smithy/types": "^3.7.0", + "@smithy/url-parser": "^3.0.9", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.26", + "@smithy/util-defaults-mode-node": "^3.0.26", + "@smithy/util-endpoints": "^2.1.5", + "@smithy/util-middleware": "^3.0.9", + "@smithy/util-retry": "^3.0.9", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-sts": "^3.692.0" + } + }, + "node_modules/@aws-sdk/client-ecs/node_modules/@aws-sdk/client-sts": { + "version": "3.692.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.692.0.tgz", + "integrity": "sha512-tUcwt6I9XmOlSNz5GHeYF8VuOcA6Cq58ZeV4MHdxkRVMpc20LhULaDIQvQAAwuokqiBuN/tm+Yyh34pe0wrVyQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/client-sso-oidc": "3.692.0", + "@aws-sdk/core": "3.692.0", + "@aws-sdk/credential-provider-node": "3.692.0", + "@aws-sdk/middleware-host-header": "3.692.0", + "@aws-sdk/middleware-logger": "3.692.0", + "@aws-sdk/middleware-recursion-detection": "3.692.0", + "@aws-sdk/middleware-user-agent": "3.692.0", + "@aws-sdk/region-config-resolver": "3.692.0", + "@aws-sdk/types": "3.692.0", + "@aws-sdk/util-endpoints": "3.692.0", + "@aws-sdk/util-user-agent-browser": "3.692.0", + "@aws-sdk/util-user-agent-node": "3.692.0", + "@smithy/config-resolver": "^3.0.11", + "@smithy/core": "^2.5.2", + "@smithy/fetch-http-handler": "^4.1.0", + "@smithy/hash-node": "^3.0.9", + "@smithy/invalid-dependency": "^3.0.9", + "@smithy/middleware-content-length": "^3.0.11", + "@smithy/middleware-endpoint": "^3.2.2", + "@smithy/middleware-retry": "^3.0.26", + "@smithy/middleware-serde": "^3.0.9", + "@smithy/middleware-stack": "^3.0.9", + "@smithy/node-config-provider": "^3.1.10", + "@smithy/node-http-handler": "^3.3.0", + "@smithy/protocol-http": "^4.1.6", + "@smithy/smithy-client": "^3.4.3", + "@smithy/types": "^3.7.0", + "@smithy/url-parser": "^3.0.9", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.26", + "@smithy/util-defaults-mode-node": "^3.0.26", + "@smithy/util-endpoints": "^2.1.5", + "@smithy/util-middleware": "^3.0.9", + "@smithy/util-retry": "^3.0.9", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-ecs/node_modules/@aws-sdk/core": { + "version": "3.692.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.692.0.tgz", + "integrity": "sha512-MsiPquDFPdVgx2RNV+p9VxFEIs5IEqluR9ibqekizbMz+1NTvua7b1WNBEyjAwl/VQRKN7fNKPZaVC+YTzZ36g==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.692.0", + "@smithy/core": "^2.5.2", + "@smithy/node-config-provider": "^3.1.10", + "@smithy/property-provider": "^3.1.9", + "@smithy/protocol-http": "^4.1.6", + "@smithy/signature-v4": "^4.2.2", + "@smithy/smithy-client": "^3.4.3", + "@smithy/types": "^3.7.0", + "@smithy/util-middleware": "^3.0.9", + "fast-xml-parser": "4.4.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-ecs/node_modules/@aws-sdk/credential-provider-env": { + "version": "3.692.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.692.0.tgz", + "integrity": "sha512-2o1qzSinyheeozQcG4l8QBRkrlYMbA2LKKumkCSVMRwF02u4SxZ/tbUjzr8wtUZO1bII9b7DUUOJWY8jDICDEw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.692.0", + "@aws-sdk/types": "3.692.0", + "@smithy/property-provider": "^3.1.9", + "@smithy/types": "^3.7.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-ecs/node_modules/@aws-sdk/credential-provider-http": { + "version": "3.692.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.692.0.tgz", + "integrity": "sha512-8erlMQOeXcBPy387YcrCKQn2METTiDSnPpHn5SoAWmL1DTsBe0gP7SQk3UDGTkaEV0r9EBxQHzY1plIC1rCq2w==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.692.0", + "@aws-sdk/types": "3.692.0", + "@smithy/fetch-http-handler": "^4.1.0", + "@smithy/node-http-handler": "^3.3.0", + "@smithy/property-provider": "^3.1.9", + "@smithy/protocol-http": "^4.1.6", + "@smithy/smithy-client": "^3.4.3", + "@smithy/types": "^3.7.0", + "@smithy/util-stream": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-ecs/node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.692.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.692.0.tgz", + "integrity": "sha512-HrrDiuxNSRoRcZstZ2hpF9/CSpi7csJz2dCwPKzb464uRJbmpj4WaSE9LImKRa8G8Xu7qguZfCeoRVC5gyNYGg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.692.0", + "@aws-sdk/credential-provider-env": "3.692.0", + "@aws-sdk/credential-provider-http": "3.692.0", + "@aws-sdk/credential-provider-process": "3.692.0", + "@aws-sdk/credential-provider-sso": "3.692.0", + "@aws-sdk/credential-provider-web-identity": "3.692.0", + "@aws-sdk/types": "3.692.0", + "@smithy/credential-provider-imds": "^3.2.6", + "@smithy/property-provider": "^3.1.9", + "@smithy/shared-ini-file-loader": "^3.1.10", + "@smithy/types": "^3.7.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-sts": "^3.692.0" + } + }, + "node_modules/@aws-sdk/client-ecs/node_modules/@aws-sdk/credential-provider-node": { + "version": "3.692.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.692.0.tgz", + "integrity": "sha512-2KG1Yv5jQIGYQmsn9gxNtGlgzAWnEc2Bx8nQZvyWaJF7EUmJPPPeaqBQrsYFbgW/FjVvi7ZjzMRWW/C50IKoLA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/credential-provider-env": "3.692.0", + "@aws-sdk/credential-provider-http": "3.692.0", + "@aws-sdk/credential-provider-ini": "3.692.0", + "@aws-sdk/credential-provider-process": "3.692.0", + "@aws-sdk/credential-provider-sso": "3.692.0", + "@aws-sdk/credential-provider-web-identity": "3.692.0", + "@aws-sdk/types": "3.692.0", + "@smithy/credential-provider-imds": "^3.2.6", + "@smithy/property-provider": "^3.1.9", + "@smithy/shared-ini-file-loader": "^3.1.10", + "@smithy/types": "^3.7.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-ecs/node_modules/@aws-sdk/credential-provider-process": { + "version": "3.692.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.692.0.tgz", + "integrity": "sha512-GeFm9SjJKbDRyk0uxZMsFRANmj8tTQErJsDJjjlT490O5dwV0DzxhR0KGSZJT1XYLfXGzixwh9cElaZ1X+0pTQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.692.0", + "@aws-sdk/types": "3.692.0", + "@smithy/property-provider": "^3.1.9", + "@smithy/shared-ini-file-loader": "^3.1.10", + "@smithy/types": "^3.7.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-ecs/node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.692.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.692.0.tgz", + "integrity": "sha512-IqXnkC30rknIP4Y5ZjvPp7sY0J/lLXA/ODvXuI7ZI5+asuWhUlrpF0ChJDjL8ld//HXJ4o3lm+40UGCiNbTZig==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/client-sso": "3.692.0", + "@aws-sdk/core": "3.692.0", + "@aws-sdk/token-providers": "3.692.0", + "@aws-sdk/types": "3.692.0", + "@smithy/property-provider": "^3.1.9", + "@smithy/shared-ini-file-loader": "^3.1.10", + "@smithy/types": "^3.7.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-ecs/node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.692.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.692.0.tgz", + "integrity": "sha512-K63X/MIYo64F1NegOb+fzm9XCCNpcx7vjbnTHBqEy7fLc2xvAF0+Hw8+ylvHv10RfsllxnyM7IGXwfOHkNsjVw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.692.0", + "@aws-sdk/types": "3.692.0", + "@smithy/property-provider": "^3.1.9", + "@smithy/types": "^3.7.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-sts": "^3.692.0" + } + }, + "node_modules/@aws-sdk/client-ecs/node_modules/@aws-sdk/middleware-host-header": { + "version": "3.692.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.692.0.tgz", + "integrity": "sha512-p9PBGyNeWr6wCi5HqomPw3JonuuzLSrI7dzRqoDWiNIa9/T6NXzQvxtgBNw+HpEUI0DlIJFP9IyUByuer/Ak1w==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.692.0", + "@smithy/protocol-http": "^4.1.6", + "@smithy/types": "^3.7.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-ecs/node_modules/@aws-sdk/middleware-logger": { + "version": "3.692.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.692.0.tgz", + "integrity": "sha512-vMI53GixIMhyiI1hDK8dJDOodB39PiSCRL0S+nkASLBjCJ5oteed4n/3u8aaQ+L0cqWNZTlEHFqJZwPl1WyG1A==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.692.0", + "@smithy/types": "^3.7.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-ecs/node_modules/@aws-sdk/middleware-recursion-detection": { + "version": "3.692.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.692.0.tgz", + "integrity": "sha512-QlE8P6f8lFjX39ZkBX2LbtW9eUuybvmcRAQY8gGWWv1WoaSsdTdHiaea/rPDpajCa5nYzv0S8/XaS1qzVoiu9Q==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.692.0", + "@smithy/protocol-http": "^4.1.6", + "@smithy/types": "^3.7.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-ecs/node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.692.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.692.0.tgz", + "integrity": "sha512-UbcSqzqy2EO5XGRvvuELPX/f4MjkTHvXefbQr9yIsQXYtHt5e+LVfKAJJNwjNBPGR+qzf63UArb+DsydD7bLFg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.692.0", + "@aws-sdk/types": "3.692.0", + "@aws-sdk/util-endpoints": "3.692.0", + "@smithy/core": "^2.5.2", + "@smithy/protocol-http": "^4.1.6", + "@smithy/types": "^3.7.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-ecs/node_modules/@aws-sdk/region-config-resolver": { + "version": "3.692.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.692.0.tgz", + "integrity": "sha512-RUX2FK7SZlvz66+rhca5Qlw5/6nD9Ju8xHtHasDdWA6ehZZKmyqcVwU0j8lwWr3J/VQa6abhyuY3ZMgK/+g2zg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.692.0", + "@smithy/node-config-provider": "^3.1.10", + "@smithy/types": "^3.7.0", + "@smithy/util-config-provider": "^3.0.0", + "@smithy/util-middleware": "^3.0.9", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-ecs/node_modules/@aws-sdk/token-providers": { + "version": "3.692.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.692.0.tgz", + "integrity": "sha512-/z7jDdj8ArSQb+ed5T87Fv677k0uX/fSFKpb8IZqDt+57lA2WdyEB28U8J2Nyj2Ykk1Hbk439B4qfytPagIEgA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.692.0", + "@smithy/property-provider": "^3.1.9", + "@smithy/shared-ini-file-loader": "^3.1.10", + "@smithy/types": "^3.7.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-sso-oidc": "^3.692.0" + } + }, + "node_modules/@aws-sdk/client-ecs/node_modules/@aws-sdk/types": { + "version": "3.692.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.692.0.tgz", + "integrity": "sha512-RpNvzD7zMEhiKgmlxGzyXaEcg2khvM7wd5sSHVapOcrde1awQSOMGI4zKBQ+wy5TnDfrm170ROz/ERLYtrjPZA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^3.7.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-ecs/node_modules/@aws-sdk/util-endpoints": { + "version": "3.692.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.692.0.tgz", + "integrity": "sha512-nKk7cKJjTREpuTVXPUzSJhIArDzwyCejTMjpdF58PKuGwAXeika6T2psTlN6egojcpRN0L1G22BHxOInZmjtew==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.692.0", + "@smithy/types": "^3.7.0", + "@smithy/util-endpoints": "^2.1.5", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-ecs/node_modules/@aws-sdk/util-user-agent-browser": { + "version": "3.692.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.692.0.tgz", + "integrity": "sha512-nawGsq4qk2IgmnVmyFgoCq3MXI2jgTak4iAbOFu5Fh+8RYAbNXd906okek5HbyVhiOUNKQmZB/pYUj/nUMJlsw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.692.0", + "@smithy/types": "^3.7.0", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-sdk/client-ecs/node_modules/@aws-sdk/util-user-agent-node": { + "version": "3.692.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.692.0.tgz", + "integrity": "sha512-rkFOabfZnTEYgqA03uSYwNFoVnWEljBWrbBKXrBFPhZYhHHsN10UubmMZ8LlKf2l8R1oNz7q4a3hRE5cNdFLHg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/middleware-user-agent": "3.692.0", + "@aws-sdk/types": "3.692.0", + "@smithy/node-config-provider": "^3.1.10", + "@smithy/types": "^3.7.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "aws-crt": ">=1.0.0" + }, + "peerDependenciesMeta": { + "aws-crt": { + "optional": true + } + } + }, + "node_modules/@aws-sdk/client-ecs/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/@aws-sdk/client-secrets-manager": { "version": "3.686.0", "resolved": "https://registry.npmjs.org/@aws-sdk/client-secrets-manager/-/client-secrets-manager-3.686.0.tgz", @@ -203,6 +776,19 @@ "node": ">=16.0.0" } }, + "node_modules/@aws-sdk/client-secrets-manager/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/@aws-sdk/client-sso": { "version": "3.686.0", "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.686.0.tgz", @@ -1382,12 +1968,12 @@ } }, "node_modules/@smithy/abort-controller": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-3.1.6.tgz", - "integrity": "sha512-0XuhuHQlEqbNQZp7QxxrFTdVWdwxch4vjxYgfInF91hZFkPxf9QDrdQka0KfxFMPqLNzSw0b95uGTrLliQUavQ==", + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-3.1.8.tgz", + "integrity": "sha512-+3DOBcUn5/rVjlxGvUPKc416SExarAQ+Qe0bqk30YSUjbepwpS7QN0cyKUSifvLJhdMZ0WPzPP5ymut0oonrpQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^3.6.0", + "@smithy/types": "^3.7.1", "tslib": "^2.6.2" }, "engines": { @@ -1395,15 +1981,15 @@ } }, "node_modules/@smithy/config-resolver": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-3.0.10.tgz", - "integrity": "sha512-Uh0Sz9gdUuz538nvkPiyv1DZRX9+D15EKDtnQP5rYVAzM/dnYk3P8cg73jcxyOitPgT3mE3OVj7ky7sibzHWkw==", + "version": "3.0.12", + "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-3.0.12.tgz", + "integrity": "sha512-YAJP9UJFZRZ8N+UruTeq78zkdjUHmzsY62J4qKWZ4SXB4QXJ/+680EfXXgkYA2xj77ooMqtUY9m406zGNqwivQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^3.1.9", - "@smithy/types": "^3.6.0", + "@smithy/node-config-provider": "^3.1.11", + "@smithy/types": "^3.7.1", "@smithy/util-config-provider": "^3.0.0", - "@smithy/util-middleware": "^3.0.8", + "@smithy/util-middleware": "^3.0.10", "tslib": "^2.6.2" }, "engines": { @@ -1411,17 +1997,17 @@ } }, "node_modules/@smithy/core": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@smithy/core/-/core-2.5.1.tgz", - "integrity": "sha512-DujtuDA7BGEKExJ05W5OdxCoyekcKT3Rhg1ZGeiUWaz2BJIWXjZmsG/DIP4W48GHno7AQwRsaCb8NcBgH3QZpg==", + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-2.5.3.tgz", + "integrity": "sha512-96uW8maifUSmehaeW7uydWn7wBc98NEeNI3zN8vqakGpyCQgzyJaA64Z4FCOUmAdCJkhppd/7SZ798Fo4Xx37g==", "license": "Apache-2.0", "dependencies": { - "@smithy/middleware-serde": "^3.0.8", - "@smithy/protocol-http": "^4.1.5", - "@smithy/types": "^3.6.0", + "@smithy/middleware-serde": "^3.0.10", + "@smithy/protocol-http": "^4.1.7", + "@smithy/types": "^3.7.1", "@smithy/util-body-length-browser": "^3.0.0", - "@smithy/util-middleware": "^3.0.8", - "@smithy/util-stream": "^3.2.1", + "@smithy/util-middleware": "^3.0.10", + "@smithy/util-stream": "^3.3.1", "@smithy/util-utf8": "^3.0.0", "tslib": "^2.6.2" }, @@ -1430,15 +2016,15 @@ } }, "node_modules/@smithy/credential-provider-imds": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-3.2.5.tgz", - "integrity": "sha512-4FTQGAsuwqTzVMmiRVTn0RR9GrbRfkP0wfu/tXWVHd2LgNpTY0uglQpIScXK4NaEyXbB3JmZt8gfVqO50lP8wg==", + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-3.2.7.tgz", + "integrity": "sha512-cEfbau+rrWF8ylkmmVAObOmjbTIzKyUC5TkBL58SbLywD0RCBC4JAUKbmtSm2w5KUJNRPGgpGFMvE2FKnuNlWQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^3.1.9", - "@smithy/property-provider": "^3.1.8", - "@smithy/types": "^3.6.0", - "@smithy/url-parser": "^3.0.8", + "@smithy/node-config-provider": "^3.1.11", + "@smithy/property-provider": "^3.1.10", + "@smithy/types": "^3.7.1", + "@smithy/url-parser": "^3.0.10", "tslib": "^2.6.2" }, "engines": { @@ -1446,25 +2032,25 @@ } }, "node_modules/@smithy/fetch-http-handler": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-4.0.0.tgz", - "integrity": "sha512-MLb1f5tbBO2X6K4lMEKJvxeLooyg7guq48C2zKr4qM7F2Gpkz4dc+hdSgu77pCJ76jVqFBjZczHYAs6dp15N+g==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-4.1.1.tgz", + "integrity": "sha512-bH7QW0+JdX0bPBadXt8GwMof/jz0H28I84hU1Uet9ISpzUqXqRQ3fEZJ+ANPOhzSEczYvANNl3uDQDYArSFDtA==", "license": "Apache-2.0", "dependencies": { - "@smithy/protocol-http": "^4.1.5", - "@smithy/querystring-builder": "^3.0.8", - "@smithy/types": "^3.6.0", + "@smithy/protocol-http": "^4.1.7", + "@smithy/querystring-builder": "^3.0.10", + "@smithy/types": "^3.7.1", "@smithy/util-base64": "^3.0.0", "tslib": "^2.6.2" } }, "node_modules/@smithy/hash-node": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-3.0.8.tgz", - "integrity": "sha512-tlNQYbfpWXHimHqrvgo14DrMAgUBua/cNoz9fMYcDmYej7MAmUcjav/QKQbFc3NrcPxeJ7QClER4tWZmfwoPng==", + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-3.0.10.tgz", + "integrity": "sha512-3zWGWCHI+FlJ5WJwx73Mw2llYR8aflVyZN5JhoqLxbdPZi6UyKSdCeXAWJw9ja22m6S6Tzz1KZ+kAaSwvydi0g==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^3.6.0", + "@smithy/types": "^3.7.1", "@smithy/util-buffer-from": "^3.0.0", "@smithy/util-utf8": "^3.0.0", "tslib": "^2.6.2" @@ -1474,12 +2060,12 @@ } }, "node_modules/@smithy/invalid-dependency": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-3.0.8.tgz", - "integrity": "sha512-7Qynk6NWtTQhnGTTZwks++nJhQ1O54Mzi7fz4PqZOiYXb4Z1Flpb2yRvdALoggTS8xjtohWUM+RygOtB30YL3Q==", + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-3.0.10.tgz", + "integrity": "sha512-Lp2L65vFi+cj0vFMu2obpPW69DU+6O5g3086lmI4XcnRCG8PxvpWC7XyaVwJCxsZFzueHjXnrOH/E0pl0zikfA==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^3.6.0", + "@smithy/types": "^3.7.1", "tslib": "^2.6.2" } }, @@ -1496,13 +2082,13 @@ } }, "node_modules/@smithy/middleware-content-length": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-3.0.10.tgz", - "integrity": "sha512-T4dIdCs1d/+/qMpwhJ1DzOhxCZjZHbHazEPJWdB4GDi2HjIZllVzeBEcdJUN0fomV8DURsgOyrbEUzg3vzTaOg==", + "version": "3.0.12", + "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-3.0.12.tgz", + "integrity": "sha512-1mDEXqzM20yywaMDuf5o9ue8OkJ373lSPbaSjyEvkWdqELhFMyNNgKGWL/rCSf4KME8B+HlHKuR8u9kRj8HzEQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/protocol-http": "^4.1.5", - "@smithy/types": "^3.6.0", + "@smithy/protocol-http": "^4.1.7", + "@smithy/types": "^3.7.1", "tslib": "^2.6.2" }, "engines": { @@ -1510,18 +2096,18 @@ } }, "node_modules/@smithy/middleware-endpoint": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-3.2.1.tgz", - "integrity": "sha512-wWO3xYmFm6WRW8VsEJ5oU6h7aosFXfszlz3Dj176pTij6o21oZnzkCLzShfmRaaCHDkBXWBdO0c4sQAvLFP6zA==", + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-3.2.3.tgz", + "integrity": "sha512-Hdl9296i/EMptaX7agrSzJZDiz5Y8XPUeBbctTmMtnCguGpqfU3jVsTUan0VLaOhsnquqWLL8Bl5HrlbVGT1og==", "license": "Apache-2.0", "dependencies": { - "@smithy/core": "^2.5.1", - "@smithy/middleware-serde": "^3.0.8", - "@smithy/node-config-provider": "^3.1.9", - "@smithy/shared-ini-file-loader": "^3.1.9", - "@smithy/types": "^3.6.0", - "@smithy/url-parser": "^3.0.8", - "@smithy/util-middleware": "^3.0.8", + "@smithy/core": "^2.5.3", + "@smithy/middleware-serde": "^3.0.10", + "@smithy/node-config-provider": "^3.1.11", + "@smithy/shared-ini-file-loader": "^3.1.11", + "@smithy/types": "^3.7.1", + "@smithy/url-parser": "^3.0.10", + "@smithy/util-middleware": "^3.0.10", "tslib": "^2.6.2" }, "engines": { @@ -1529,18 +2115,18 @@ } }, "node_modules/@smithy/middleware-retry": { - "version": "3.0.25", - "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-3.0.25.tgz", - "integrity": "sha512-m1F70cPaMBML4HiTgCw5I+jFNtjgz5z5UdGnUbG37vw6kh4UvizFYjqJGHvicfgKMkDL6mXwyPp5mhZg02g5sg==", + "version": "3.0.27", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-3.0.27.tgz", + "integrity": "sha512-H3J/PjJpLL7Tt+fxDKiOD25sMc94YetlQhCnYeNmina2LZscAdu0ZEZPas/kwePHABaEtqp7hqa5S4UJgMs1Tg==", "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^3.1.9", - "@smithy/protocol-http": "^4.1.5", - "@smithy/service-error-classification": "^3.0.8", - "@smithy/smithy-client": "^3.4.2", - "@smithy/types": "^3.6.0", - "@smithy/util-middleware": "^3.0.8", - "@smithy/util-retry": "^3.0.8", + "@smithy/node-config-provider": "^3.1.11", + "@smithy/protocol-http": "^4.1.7", + "@smithy/service-error-classification": "^3.0.10", + "@smithy/smithy-client": "^3.4.4", + "@smithy/types": "^3.7.1", + "@smithy/util-middleware": "^3.0.10", + "@smithy/util-retry": "^3.0.10", "tslib": "^2.6.2", "uuid": "^9.0.1" }, @@ -1548,13 +2134,26 @@ "node": ">=16.0.0" } }, + "node_modules/@smithy/middleware-retry/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/@smithy/middleware-serde": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-3.0.8.tgz", - "integrity": "sha512-Xg2jK9Wc/1g/MBMP/EUn2DLspN8LNt+GMe7cgF+Ty3vl+Zvu+VeZU5nmhveU+H8pxyTsjrAkci8NqY6OuvZnjA==", + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-3.0.10.tgz", + "integrity": "sha512-MnAuhh+dD14F428ubSJuRnmRsfOpxSzvRhaGVTvd/lrUDE3kxzCCmH8lnVTvoNQnV2BbJ4c15QwZ3UdQBtFNZA==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^3.6.0", + "@smithy/types": "^3.7.1", "tslib": "^2.6.2" }, "engines": { @@ -1562,12 +2161,12 @@ } }, "node_modules/@smithy/middleware-stack": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-3.0.8.tgz", - "integrity": "sha512-d7ZuwvYgp1+3682Nx0MD3D/HtkmZd49N3JUndYWQXfRZrYEnCWYc8BHcNmVsPAp9gKvlurdg/mubE6b/rPS9MA==", + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-3.0.10.tgz", + "integrity": "sha512-grCHyoiARDBBGPyw2BeicpjgpsDFWZZxptbVKb3CRd/ZA15F/T6rZjCCuBUjJwdck1nwUuIxYtsS4H9DDpbP5w==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^3.6.0", + "@smithy/types": "^3.7.1", "tslib": "^2.6.2" }, "engines": { @@ -1575,14 +2174,14 @@ } }, "node_modules/@smithy/node-config-provider": { - "version": "3.1.9", - "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-3.1.9.tgz", - "integrity": "sha512-qRHoah49QJ71eemjuS/WhUXB+mpNtwHRWQr77J/m40ewBVVwvo52kYAmb7iuaECgGTTcYxHS4Wmewfwy++ueew==", + "version": "3.1.11", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-3.1.11.tgz", + "integrity": "sha512-URq3gT3RpDikh/8MBJUB+QGZzfS7Bm6TQTqoh4CqE8NBuyPkWa5eUXj0XFcFfeZVgg3WMh1u19iaXn8FvvXxZw==", "license": "Apache-2.0", "dependencies": { - "@smithy/property-provider": "^3.1.8", - "@smithy/shared-ini-file-loader": "^3.1.9", - "@smithy/types": "^3.6.0", + "@smithy/property-provider": "^3.1.10", + "@smithy/shared-ini-file-loader": "^3.1.11", + "@smithy/types": "^3.7.1", "tslib": "^2.6.2" }, "engines": { @@ -1590,15 +2189,15 @@ } }, "node_modules/@smithy/node-http-handler": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-3.2.5.tgz", - "integrity": "sha512-PkOwPNeKdvX/jCpn0A8n9/TyoxjGZB8WVoJmm9YzsnAgggTj4CrjpRHlTQw7dlLZ320n1mY1y+nTRUDViKi/3w==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-3.3.1.tgz", + "integrity": "sha512-fr+UAOMGWh6bn4YSEezBCpJn9Ukp9oR4D32sCjCo7U81evE11YePOQ58ogzyfgmjIO79YeOdfXXqr0jyhPQeMg==", "license": "Apache-2.0", "dependencies": { - "@smithy/abort-controller": "^3.1.6", - "@smithy/protocol-http": "^4.1.5", - "@smithy/querystring-builder": "^3.0.8", - "@smithy/types": "^3.6.0", + "@smithy/abort-controller": "^3.1.8", + "@smithy/protocol-http": "^4.1.7", + "@smithy/querystring-builder": "^3.0.10", + "@smithy/types": "^3.7.1", "tslib": "^2.6.2" }, "engines": { @@ -1606,12 +2205,12 @@ } }, "node_modules/@smithy/property-provider": { - "version": "3.1.8", - "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-3.1.8.tgz", - "integrity": "sha512-ukNUyo6rHmusG64lmkjFeXemwYuKge1BJ8CtpVKmrxQxc6rhUX0vebcptFA9MmrGsnLhwnnqeH83VTU9hwOpjA==", + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-3.1.10.tgz", + "integrity": "sha512-n1MJZGTorTH2DvyTVj+3wXnd4CzjJxyXeOgnTlgNVFxaaMeT4OteEp4QrzF8p9ee2yg42nvyVK6R/awLCakjeQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^3.6.0", + "@smithy/types": "^3.7.1", "tslib": "^2.6.2" }, "engines": { @@ -1619,12 +2218,12 @@ } }, "node_modules/@smithy/protocol-http": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.5.tgz", - "integrity": "sha512-hsjtwpIemmCkm3ZV5fd/T0bPIugW1gJXwZ/hpuVubt2hEUApIoUTrf6qIdh9MAWlw0vjMrA1ztJLAwtNaZogvg==", + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.7.tgz", + "integrity": "sha512-FP2LepWD0eJeOTm0SjssPcgqAlDFzOmRXqXmGhfIM52G7Lrox/pcpQf6RP4F21k0+O12zaqQt5fCDOeBtqY6Cg==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^3.6.0", + "@smithy/types": "^3.7.1", "tslib": "^2.6.2" }, "engines": { @@ -1632,12 +2231,12 @@ } }, "node_modules/@smithy/querystring-builder": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-3.0.8.tgz", - "integrity": "sha512-btYxGVqFUARbUrN6VhL9c3dnSviIwBYD9Rz1jHuN1hgh28Fpv2xjU1HeCeDJX68xctz7r4l1PBnFhGg1WBBPuA==", + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-3.0.10.tgz", + "integrity": "sha512-nT9CQF3EIJtIUepXQuBFb8dxJi3WVZS3XfuDksxSCSn+/CzZowRLdhDn+2acbBv8R6eaJqPupoI/aRFIImNVPQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^3.6.0", + "@smithy/types": "^3.7.1", "@smithy/util-uri-escape": "^3.0.0", "tslib": "^2.6.2" }, @@ -1646,12 +2245,12 @@ } }, "node_modules/@smithy/querystring-parser": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-3.0.8.tgz", - "integrity": "sha512-BtEk3FG7Ks64GAbt+JnKqwuobJNX8VmFLBsKIwWr1D60T426fGrV2L3YS5siOcUhhp6/Y6yhBw1PSPxA5p7qGg==", + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-3.0.10.tgz", + "integrity": "sha512-Oa0XDcpo9SmjhiDD9ua2UyM3uU01ZTuIrNdZvzwUTykW1PM8o2yJvMh1Do1rY5sUQg4NDV70dMi0JhDx4GyxuQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^3.6.0", + "@smithy/types": "^3.7.1", "tslib": "^2.6.2" }, "engines": { @@ -1659,24 +2258,24 @@ } }, "node_modules/@smithy/service-error-classification": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-3.0.8.tgz", - "integrity": "sha512-uEC/kCCFto83bz5ZzapcrgGqHOh/0r69sZ2ZuHlgoD5kYgXJEThCoTuw/y1Ub3cE7aaKdznb+jD9xRPIfIwD7g==", + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-3.0.10.tgz", + "integrity": "sha512-zHe642KCqDxXLuhs6xmHVgRwy078RfqxP2wRDpIyiF8EmsWXptMwnMwbVa50lw+WOGNrYm9zbaEg0oDe3PTtvQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^3.6.0" + "@smithy/types": "^3.7.1" }, "engines": { "node": ">=16.0.0" } }, "node_modules/@smithy/shared-ini-file-loader": { - "version": "3.1.9", - "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-3.1.9.tgz", - "integrity": "sha512-/+OsJRNtoRbtsX0UpSgWVxFZLsJHo/4sTr+kBg/J78sr7iC+tHeOvOJrS5hCpVQ6sWBbhWLp1UNiuMyZhE6pmA==", + "version": "3.1.11", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-3.1.11.tgz", + "integrity": "sha512-AUdrIZHFtUgmfSN4Gq9nHu3IkHMa1YDcN+s061Nfm+6pQ0mJy85YQDB0tZBCmls0Vuj22pLwDPmL92+Hvfwwlg==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^3.6.0", + "@smithy/types": "^3.7.1", "tslib": "^2.6.2" }, "engines": { @@ -1684,16 +2283,16 @@ } }, "node_modules/@smithy/signature-v4": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-4.2.1.tgz", - "integrity": "sha512-NsV1jF4EvmO5wqmaSzlnTVetemBS3FZHdyc5CExbDljcyJCEEkJr8ANu2JvtNbVg/9MvKAWV44kTrGS+Pi4INg==", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-4.2.3.tgz", + "integrity": "sha512-pPSQQ2v2vu9vc8iew7sszLd0O09I5TRc5zhY71KA+Ao0xYazIG+uLeHbTJfIWGO3BGVLiXjUr3EEeCcEQLjpWQ==", "license": "Apache-2.0", "dependencies": { "@smithy/is-array-buffer": "^3.0.0", - "@smithy/protocol-http": "^4.1.5", - "@smithy/types": "^3.6.0", + "@smithy/protocol-http": "^4.1.7", + "@smithy/types": "^3.7.1", "@smithy/util-hex-encoding": "^3.0.0", - "@smithy/util-middleware": "^3.0.8", + "@smithy/util-middleware": "^3.0.10", "@smithy/util-uri-escape": "^3.0.0", "@smithy/util-utf8": "^3.0.0", "tslib": "^2.6.2" @@ -1703,17 +2302,17 @@ } }, "node_modules/@smithy/smithy-client": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-3.4.2.tgz", - "integrity": "sha512-dxw1BDxJiY9/zI3cBqfVrInij6ShjpV4fmGHesGZZUiP9OSE/EVfdwdRz0PgvkEvrZHpsj2htRaHJfftE8giBA==", + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-3.4.4.tgz", + "integrity": "sha512-dPGoJuSZqvirBq+yROapBcHHvFjChoAQT8YPWJ820aPHHiowBlB3RL1Q4kPT1hx0qKgJuf+HhyzKi5Gbof4fNA==", "license": "Apache-2.0", "dependencies": { - "@smithy/core": "^2.5.1", - "@smithy/middleware-endpoint": "^3.2.1", - "@smithy/middleware-stack": "^3.0.8", - "@smithy/protocol-http": "^4.1.5", - "@smithy/types": "^3.6.0", - "@smithy/util-stream": "^3.2.1", + "@smithy/core": "^2.5.3", + "@smithy/middleware-endpoint": "^3.2.3", + "@smithy/middleware-stack": "^3.0.10", + "@smithy/protocol-http": "^4.1.7", + "@smithy/types": "^3.7.1", + "@smithy/util-stream": "^3.3.1", "tslib": "^2.6.2" }, "engines": { @@ -1721,9 +2320,9 @@ } }, "node_modules/@smithy/types": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.6.0.tgz", - "integrity": "sha512-8VXK/KzOHefoC65yRgCn5vG1cysPJjHnOVt9d0ybFQSmJgQj152vMn4EkYhGuaOmnnZvCPav/KnYyE6/KsNZ2w==", + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.1.tgz", + "integrity": "sha512-XKLcLXZY7sUQgvvWyeaL/qwNPp6V3dWcUjqrQKjSb+tzYiCy340R/c64LV5j+Tnb2GhmunEX0eou+L+m2hJNYA==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -1733,13 +2332,13 @@ } }, "node_modules/@smithy/url-parser": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-3.0.8.tgz", - "integrity": "sha512-4FdOhwpTW7jtSFWm7SpfLGKIBC9ZaTKG5nBF0wK24aoQKQyDIKUw3+KFWCQ9maMzrgTJIuOvOnsV2lLGW5XjTg==", + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-3.0.10.tgz", + "integrity": "sha512-j90NUalTSBR2NaZTuruEgavSdh8MLirf58LoGSk4AtQfyIymogIhgnGUU2Mga2bkMkpSoC9gxb74xBXL5afKAQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/querystring-parser": "^3.0.8", - "@smithy/types": "^3.6.0", + "@smithy/querystring-parser": "^3.0.10", + "@smithy/types": "^3.7.1", "tslib": "^2.6.2" } }, @@ -1804,14 +2403,14 @@ } }, "node_modules/@smithy/util-defaults-mode-browser": { - "version": "3.0.25", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-3.0.25.tgz", - "integrity": "sha512-fRw7zymjIDt6XxIsLwfJfYUfbGoO9CmCJk6rjJ/X5cd20+d2Is7xjU5Kt/AiDt6hX8DAf5dztmfP5O82gR9emA==", + "version": "3.0.27", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-3.0.27.tgz", + "integrity": "sha512-GV8NvPy1vAGp7u5iD/xNKUxCorE4nQzlyl057qRac+KwpH5zq8wVq6rE3lPPeuFLyQXofPN6JwxL1N9ojGapiQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/property-provider": "^3.1.8", - "@smithy/smithy-client": "^3.4.2", - "@smithy/types": "^3.6.0", + "@smithy/property-provider": "^3.1.10", + "@smithy/smithy-client": "^3.4.4", + "@smithy/types": "^3.7.1", "bowser": "^2.11.0", "tslib": "^2.6.2" }, @@ -1820,17 +2419,17 @@ } }, "node_modules/@smithy/util-defaults-mode-node": { - "version": "3.0.25", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-3.0.25.tgz", - "integrity": "sha512-H3BSZdBDiVZGzt8TG51Pd2FvFO0PAx/A0mJ0EH8a13KJ6iUCdYnw/Dk/MdC1kTd0eUuUGisDFaxXVXo4HHFL1g==", + "version": "3.0.27", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-3.0.27.tgz", + "integrity": "sha512-7+4wjWfZqZxZVJvDutO+i1GvL6bgOajEkop4FuR6wudFlqBiqwxw3HoH6M9NgeCd37km8ga8NPp2JacQEtAMPg==", "license": "Apache-2.0", "dependencies": { - "@smithy/config-resolver": "^3.0.10", - "@smithy/credential-provider-imds": "^3.2.5", - "@smithy/node-config-provider": "^3.1.9", - "@smithy/property-provider": "^3.1.8", - "@smithy/smithy-client": "^3.4.2", - "@smithy/types": "^3.6.0", + "@smithy/config-resolver": "^3.0.12", + "@smithy/credential-provider-imds": "^3.2.7", + "@smithy/node-config-provider": "^3.1.11", + "@smithy/property-provider": "^3.1.10", + "@smithy/smithy-client": "^3.4.4", + "@smithy/types": "^3.7.1", "tslib": "^2.6.2" }, "engines": { @@ -1838,13 +2437,13 @@ } }, "node_modules/@smithy/util-endpoints": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-2.1.4.tgz", - "integrity": "sha512-kPt8j4emm7rdMWQyL0F89o92q10gvCUa6sBkBtDJ7nV2+P7wpXczzOfoDJ49CKXe5CCqb8dc1W+ZdLlrKzSAnQ==", + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-2.1.6.tgz", + "integrity": "sha512-mFV1t3ndBh0yZOJgWxO9J/4cHZVn5UG1D8DeCc6/echfNkeEJWu9LD7mgGH5fHrEdR7LDoWw7PQO6QiGpHXhgA==", "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^3.1.9", - "@smithy/types": "^3.6.0", + "@smithy/node-config-provider": "^3.1.11", + "@smithy/types": "^3.7.1", "tslib": "^2.6.2" }, "engines": { @@ -1864,12 +2463,12 @@ } }, "node_modules/@smithy/util-middleware": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-3.0.8.tgz", - "integrity": "sha512-p7iYAPaQjoeM+AKABpYWeDdtwQNxasr4aXQEA/OmbOaug9V0odRVDy3Wx4ci8soljE/JXQo+abV0qZpW8NX0yA==", + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-3.0.10.tgz", + "integrity": "sha512-eJO+/+RsrG2RpmY68jZdwQtnfsxjmPxzMlQpnHKjFPwrYqvlcT+fHdT+ZVwcjlWSrByOhGr9Ff2GG17efc192A==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^3.6.0", + "@smithy/types": "^3.7.1", "tslib": "^2.6.2" }, "engines": { @@ -1877,13 +2476,13 @@ } }, "node_modules/@smithy/util-retry": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-3.0.8.tgz", - "integrity": "sha512-TCEhLnY581YJ+g1x0hapPz13JFqzmh/pMWL2KEFASC51qCfw3+Y47MrTmea4bUE5vsdxQ4F6/KFbUeSz22Q1ow==", + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-3.0.10.tgz", + "integrity": "sha512-1l4qatFp4PiU6j7UsbasUHL2VU023NRB/gfaa1M0rDqVrRN4g3mCArLRyH3OuktApA4ye+yjWQHjdziunw2eWA==", "license": "Apache-2.0", "dependencies": { - "@smithy/service-error-classification": "^3.0.8", - "@smithy/types": "^3.6.0", + "@smithy/service-error-classification": "^3.0.10", + "@smithy/types": "^3.7.1", "tslib": "^2.6.2" }, "engines": { @@ -1891,14 +2490,14 @@ } }, "node_modules/@smithy/util-stream": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-3.2.1.tgz", - "integrity": "sha512-R3ufuzJRxSJbE58K9AEnL/uSZyVdHzud9wLS8tIbXclxKzoe09CRohj2xV8wpx5tj7ZbiJaKYcutMm1eYgz/0A==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-3.3.1.tgz", + "integrity": "sha512-Ff68R5lJh2zj+AUTvbAU/4yx+6QPRzg7+pI7M1FbtQHcRIp7xvguxVsQBKyB3fwiOwhAKu0lnNyYBaQfSW6TNw==", "license": "Apache-2.0", "dependencies": { - "@smithy/fetch-http-handler": "^4.0.0", - "@smithy/node-http-handler": "^3.2.5", - "@smithy/types": "^3.6.0", + "@smithy/fetch-http-handler": "^4.1.1", + "@smithy/node-http-handler": "^3.3.1", + "@smithy/types": "^3.7.1", "@smithy/util-base64": "^3.0.0", "@smithy/util-buffer-from": "^3.0.0", "@smithy/util-hex-encoding": "^3.0.0", @@ -1934,6 +2533,20 @@ "node": ">=16.0.0" } }, + "node_modules/@smithy/util-waiter": { + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/@smithy/util-waiter/-/util-waiter-3.1.9.tgz", + "integrity": "sha512-/aMXPANhMOlMPjfPtSrDfPeVP8l56SJlz93xeiLmhLe5xvlXA5T3abZ2ilEsDEPeY9T/wnN/vNGn9wa1SbufWA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/abort-controller": "^3.1.8", + "@smithy/types": "^3.7.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/@types/aws-lambda": { "version": "8.10.145", "resolved": "https://registry.npmjs.org/@types/aws-lambda/-/aws-lambda-8.10.145.tgz", @@ -3167,7 +3780,6 @@ "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true, "license": "MIT" }, "node_modules/lru-cache": { @@ -3730,16 +4342,16 @@ } }, "node_modules/uuid": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", - "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.0.3.tgz", + "integrity": "sha512-d0z310fCWv5dJwnX1Y/MncBAqGMKEzlBb1AOf7z9K8ALnd0utBX/msg/fA0+sbyN1ihbMsLhrBlnl1ak7Wa0rg==", "funding": [ "https://github.com/sponsors/broofa", "https://github.com/sponsors/ctavan" ], "license": "MIT", "bin": { - "uuid": "dist/bin/uuid" + "uuid": "dist/esm/bin/uuid" } }, "node_modules/which": { diff --git a/aws/package.json b/aws/package.json index 945c8bfc..3e870335 100644 --- a/aws/package.json +++ b/aws/package.json @@ -20,8 +20,11 @@ "eslint-define-config": "^2.1.0" }, "dependencies": { + "@aws-lambda-powertools/logger": "^2.10.0", + "@aws-sdk/client-ecs": "^3.692.0", "@aws-sdk/client-secrets-manager": "^3.686.0", "axios": "^1.7.7", - "glob": "^11.0.0" + "glob": "^11.0.0", + "uuid": "^11.0.3" } } diff --git a/aws/services/ecsService.ts b/aws/services/ecsService.ts new file mode 100644 index 00000000..ee9397e5 --- /dev/null +++ b/aws/services/ecsService.ts @@ -0,0 +1,67 @@ +import { + DescribeServicesCommand, + ECSClient, + ListServicesCommand, + Service, + UpdateServiceCommand, +} from "@aws-sdk/client-ecs"; + +export default class ECSService { + private client = new ECSClient(); + private readonly clusterName: string; + + constructor(clusterName: string) { + this.clusterName = clusterName; + } + + async getECSServices(): Promise { + const servicesResponse = await this.client.send( + new ListServicesCommand({ + cluster: this.clusterName, + }) + ); + + if ( + !servicesResponse || + !servicesResponse.serviceArns || + servicesResponse.serviceArns.length === 0 + ) { + throw new Error( + `Error occured when listing the services from ${this.clusterName} Cluster` + ); + } + + const describeServicesResponse = await this.client.send( + new DescribeServicesCommand({ + cluster: this.clusterName, + services: servicesResponse.serviceArns, + }) + ); + + const services = describeServicesResponse.services; + if (!services || services.length === 0) { + throw new Error( + `Error occured when describing services from ${this.clusterName} Cluster` + ); + } + + return services; + } + + async restartServices(services: Service[]): Promise { + for (const { status, serviceName } of services) { + if (status === "ACTIVE") { + const updateServiceCommand = new UpdateServiceCommand({ + cluster: this.clusterName, + service: serviceName, + forceNewDeployment: true, + }); + + const response = await this.client.send(updateServiceCommand); + console.log(`Service ${serviceName} is restarting tasks:`, response); + } else { + console.log(`Service ${serviceName} is not active, skipping restart.`); + } + } + } +} diff --git a/aws/services/secretsManagerService.ts b/aws/services/secretsManagerService.ts new file mode 100644 index 00000000..7642f788 --- /dev/null +++ b/aws/services/secretsManagerService.ts @@ -0,0 +1,31 @@ +import { + GetSecretValueCommand, + SecretsManagerClient, + UpdateSecretCommand, +} from "@aws-sdk/client-secrets-manager"; + +export default class SecretsManagerService { + private client = new SecretsManagerClient(); + + async getSecret(secretName: string): Promise { + const command = new GetSecretValueCommand({ SecretId: secretName }); + const data = await this.client.send(command); + + if (data.SecretString) { + return data.SecretString; + } else { + throw new Error( + `Secret with ID ${secretName} does not contain SecretString` + ); + } + } + + async updateSecret(secretId: string, secretString: string): Promise { + const command = new UpdateSecretCommand({ + SecretId: secretId, + SecretString: secretString, + }); + + await this.client.send(command); + } +} diff --git a/infrastructure/cloud/environments/dev/webapp.tf b/infrastructure/cloud/environments/dev/webapp.tf index 07f50c2a..bd3dab14 100644 --- a/infrastructure/cloud/environments/dev/webapp.tf +++ b/infrastructure/cloud/environments/dev/webapp.tf @@ -45,11 +45,12 @@ data "aws_ecr_repository" "lambda_ecr_repo" { # Create Secrets placeholder for Secrets Manager module "secrets_manager" { - source = "../../modules/SecretsManager" - environment = var.environment - app_name = var.app_name - region = var.region - kms_key_arn = data.aws_kms_key.kms_key.arn + source = "../../modules/SecretsManager" + environment = var.environment + app_name = var.app_name + region = var.region + kms_key_arn = data.aws_kms_key.kms_key.arn + rotate_key_lambda_arn = module.lambda.lambda_functions["rotate-key"].arn } # Create RDS Database @@ -129,6 +130,54 @@ module "lambda" { apigw_execution_arn = module.apigw.apigw_execution_arn lambda_ecr_repo_url = data.aws_ecr_repository.lambda_ecr_repo.repository_url mtls_secret_name = module.secrets_manager.mtls_secret_name + functions = { + "authorizer" = { + http_method = "*" + resource_path = "/*" + env_variables = { + VERIFY_SECRET_NAME = module.secrets_manager.api_authorizer_secret.name + } + }, + "rotate-key" = { + http_method = "POST" + resource_path = "/*" + statement_id_prefix = "AllowSecretsManagerInvoke" + source_arn = module.secrets_manager.api_authorizer_secret.arn + principal = "secretsmanager.amazonaws.com" + env_variables = { + VERIFY_SECRET_NAME = module.secrets_manager.api_authorizer_secret.name + CLUSTER_NAME = module.ecs_cluster.ecs_cluster.name + } + } + } +} + +# Create Cloudwatch LogGroups +module "ecs_api_td_log_group" { + source = "../../modules/Cloudwatch/LogGroup" + environment = var.environment + app_name = var.app_name + kms_key_arn = data.aws_kms_key.kms_key.arn + resource_name = "ecs" + name = "api-td" +} + +module "ecs_web_td_log_group" { + source = "../../modules/Cloudwatch/LogGroup" + environment = var.environment + app_name = var.app_name + kms_key_arn = data.aws_kms_key.kms_key.arn + resource_name = "ecs" + name = "web-td" +} + +module "apigw_api_log_group" { + source = "../../modules/Cloudwatch/LogGroup" + environment = var.environment + app_name = var.app_name + kms_key_arn = data.aws_kms_key.kms_key.arn + resource_name = "apigateway" + name = "api" } # Create API Gateway @@ -140,6 +189,8 @@ module "apigw" { account_id = data.aws_caller_identity.current.account_id lambda_functions = module.lambda.lambda_functions ecs_execution_role_arn = module.iam.ecs_execution_role_arn + log_group_arn = module.apigw_api_log_group.log_group.arn + apigw_logging_role_arn = module.iam.apigw_logging_role_arn } # Create ECS Cluster @@ -162,6 +213,7 @@ module "ecs_web_td" { port = 8080 secret_env_variables = module.secrets_manager.web_secrets kms_key_arn = data.aws_kms_key.kms_key.arn + log_group_name = module.ecs_web_td_log_group.log_group.name } # Create API ECS Task Definition @@ -186,6 +238,7 @@ module "ecs_api_td" { ] secret_env_variables = module.secrets_manager.api_secrets kms_key_arn = data.aws_kms_key.kms_key.arn + log_group_name = module.ecs_api_td_log_group.log_group.name } # Create Web ECS Service @@ -194,7 +247,7 @@ module "ecs_web_service" { environment = var.environment app_name = var.app_name name = "web" - ecs_cluster_id = module.ecs_cluster.ecs_cluster_id + ecs_cluster_id = module.ecs_cluster.ecs_cluster.id ecs_td_arn = module.ecs_web_td.ecs_td_arn tg_arn = module.tg_web.tg_arn sg_id = data.aws_security_group.app_sg.id @@ -208,7 +261,7 @@ module "ecs_api_service" { environment = var.environment app_name = var.app_name name = "api" - ecs_cluster_id = module.ecs_cluster.ecs_cluster_id + ecs_cluster_id = module.ecs_cluster.ecs_cluster.id ecs_td_arn = module.ecs_api_td.ecs_td_arn tg_arn = module.tg_api.tg_arn sg_id = data.aws_security_group.app_sg.id diff --git a/infrastructure/cloud/modules/APIGateway/main.tf b/infrastructure/cloud/modules/APIGateway/main.tf index e2cb0262..1346925f 100644 --- a/infrastructure/cloud/modules/APIGateway/main.tf +++ b/infrastructure/cloud/modules/APIGateway/main.tf @@ -5,13 +5,56 @@ resource "aws_api_gateway_rest_api" "apigw" { resource "aws_api_gateway_deployment" "apigw_deployment" { depends_on = [ + # Add new integration here so that it registers in API Gateway aws_api_gateway_integration.get_locations_integration, aws_api_gateway_integration.get_locations_rooms_integration, aws_api_gateway_integration.get_files_civil_integration, aws_api_gateway_integration.get_files_criminal_integration, ] rest_api_id = aws_api_gateway_rest_api.apigw.id - stage_name = var.environment + + triggers = { + redeployment = sha1(jsonencode(aws_api_gateway_rest_api.apigw.body)) + } + + lifecycle { + create_before_destroy = true + } +} + +resource "aws_api_gateway_stage" "apigw_stage" { + stage_name = var.environment + deployment_id = aws_api_gateway_deployment.apigw_deployment.id + rest_api_id = aws_api_gateway_rest_api.apigw.id + xray_tracing_enabled = true + + access_log_settings { + destination_arn = var.log_group_arn + format = jsonencode({ + requestId = "$context.requestId", + ip = "$context.identity.sourceIp", + caller = "$context.identity.caller", + user = "$context.identity.user", + requestTime = "$context.requestTime", + httpMethod = "$context.httpMethod", + resourcePath = "$context.resourcePath", + status = "$context.status", + protocol = "$context.protocol", + responseLength = "$context.responseLength" + }) + } +} + +resource "aws_api_gateway_method_settings" "apgw_method_settings" { + rest_api_id = aws_api_gateway_rest_api.apigw.id + stage_name = aws_api_gateway_stage.apigw_stage.stage_name + method_path = "*/*" + + settings { + data_trace_enabled = true + metrics_enabled = true + logging_level = "INFO" + } } resource "aws_api_gateway_rest_api_policy" "apigw_rest_api_policy" { @@ -21,8 +64,9 @@ resource "aws_api_gateway_rest_api_policy" "apigw_rest_api_policy" { Version = "2012-10-17" Statement = [ { - Effect = "Allow" - Principal = var.ecs_execution_role_arn + Effect = "Allow" + #Principal = var.ecs_execution_role_arn + Principal = "*" Action = "execute-api:Invoke" Resource = "arn:aws:execute-api:${var.region}:${var.account_id}:${aws_api_gateway_rest_api.apigw.id}/*" } @@ -30,12 +74,18 @@ resource "aws_api_gateway_rest_api_policy" "apigw_rest_api_policy" { }) } +resource "aws_api_gateway_account" "apigateway_account" { + cloudwatch_role_arn = var.apigw_logging_role_arn + + depends_on = [aws_api_gateway_stage.apigw_stage] +} + resource "aws_api_gateway_usage_plan" "apigw_usage_plan" { name = "${var.app_name}-apigw-usage-plan-${var.environment}" api_stages { api_id = aws_api_gateway_rest_api.apigw.id - stage = aws_api_gateway_deployment.apigw_deployment.stage_name + stage = aws_api_gateway_stage.apigw_stage.stage_name } } @@ -49,6 +99,17 @@ resource "aws_api_gateway_usage_plan_key" "apigw_usage_plan_key" { usage_plan_id = aws_api_gateway_usage_plan.apigw_usage_plan.id } +# +# Authorizer +# +resource "aws_api_gateway_authorizer" "authorizer" { + name = "${var.app_name}-authorizer-${var.environment}" + rest_api_id = aws_api_gateway_rest_api.apigw.id + authorizer_uri = var.lambda_functions["authorizer"].invoke_arn + type = "REQUEST" + identity_source = "method.request.header.x-origin-verify" +} + # # /locations Resource # @@ -63,7 +124,8 @@ resource "aws_api_gateway_method" "get_locations_method" { rest_api_id = aws_api_gateway_rest_api.apigw.id resource_id = aws_api_gateway_resource.locations_resource.id http_method = var.lambda_functions["get-locations"].http_method - authorization = "AWS_IAM" + authorization = "CUSTOM" + authorizer_id = aws_api_gateway_authorizer.authorizer.id api_key_required = true request_parameters = { @@ -92,7 +154,8 @@ resource "aws_api_gateway_method" "get_locations_rooms_method" { rest_api_id = aws_api_gateway_rest_api.apigw.id resource_id = aws_api_gateway_resource.rooms_resource.id http_method = var.lambda_functions["get-rooms"].http_method - authorization = "AWS_IAM" + authorization = "CUSTOM" + authorizer_id = aws_api_gateway_authorizer.authorizer.id api_key_required = true request_parameters = { @@ -130,8 +193,13 @@ resource "aws_api_gateway_method" "get_files_civil_method" { rest_api_id = aws_api_gateway_rest_api.apigw.id resource_id = aws_api_gateway_resource.civil_resource.id http_method = var.lambda_functions["search-civil-files"].http_method - authorization = "AWS_IAM" + authorization = "CUSTOM" + authorizer_id = aws_api_gateway_authorizer.authorizer.id api_key_required = true + + request_parameters = { + "method.request.header.x-origin-verify" = true + } } resource "aws_api_gateway_integration" "get_files_civil_integration" { @@ -155,8 +223,13 @@ resource "aws_api_gateway_method" "get_files_criminal_method" { rest_api_id = aws_api_gateway_rest_api.apigw.id resource_id = aws_api_gateway_resource.criminal_resource.id http_method = var.lambda_functions["search-criminal-files"].http_method - authorization = "AWS_IAM" + authorization = "CUSTOM" + authorizer_id = aws_api_gateway_authorizer.authorizer.id api_key_required = true + + request_parameters = { + "method.request.header.x-origin-verify" = true + } } resource "aws_api_gateway_integration" "get_files_criminal_integration" { diff --git a/infrastructure/cloud/modules/APIGateway/variables.tf b/infrastructure/cloud/modules/APIGateway/variables.tf index fb7e023b..c702f15b 100644 --- a/infrastructure/cloud/modules/APIGateway/variables.tf +++ b/infrastructure/cloud/modules/APIGateway/variables.tf @@ -31,3 +31,13 @@ variable "ecs_execution_role_arn" { description = "The ECS Task Definition Execution role ARN" type = string } + +variable "log_group_arn" { + description = "The API Gateway Cloudwatch Log Group ARN" + type = string +} + +variable "apigw_logging_role_arn" { + description = "The API Gateway Logging IAM Role ARN" + type = string +} diff --git a/infrastructure/cloud/modules/Cloudwatch/LogGroup/outputs.tf b/infrastructure/cloud/modules/Cloudwatch/LogGroup/outputs.tf index fbee1a01..d740cac8 100644 --- a/infrastructure/cloud/modules/Cloudwatch/LogGroup/outputs.tf +++ b/infrastructure/cloud/modules/Cloudwatch/LogGroup/outputs.tf @@ -1,3 +1,3 @@ -output "log_group_name" { - value = aws_cloudwatch_log_group.log_group.name +output "log_group" { + value = aws_cloudwatch_log_group.log_group } diff --git a/infrastructure/cloud/modules/ECR/outputs.tf b/infrastructure/cloud/modules/ECR/outputs.tf index e69de29b..e376a904 100644 --- a/infrastructure/cloud/modules/ECR/outputs.tf +++ b/infrastructure/cloud/modules/ECR/outputs.tf @@ -0,0 +1,3 @@ +output "ecs_cluster_arn" { + value = aws_ecr_repository.ecr_repository.arn +} diff --git a/infrastructure/cloud/modules/ECS/Cluster/outputs.tf b/infrastructure/cloud/modules/ECS/Cluster/outputs.tf index d9a71f0b..fac6a3e9 100644 --- a/infrastructure/cloud/modules/ECS/Cluster/outputs.tf +++ b/infrastructure/cloud/modules/ECS/Cluster/outputs.tf @@ -1,3 +1,3 @@ -output "ecs_cluster_id" { - value = aws_ecs_cluster.ecs_cluster.id +output "ecs_cluster" { + value = aws_ecs_cluster.ecs_cluster } diff --git a/infrastructure/cloud/modules/ECS/Service/outputs.tf b/infrastructure/cloud/modules/ECS/Service/outputs.tf index e69de29b..07d13677 100644 --- a/infrastructure/cloud/modules/ECS/Service/outputs.tf +++ b/infrastructure/cloud/modules/ECS/Service/outputs.tf @@ -0,0 +1,3 @@ +output "ecs_service_arn" { + value = aws_ecs_service.ecs_service.id +} diff --git a/infrastructure/cloud/modules/ECS/TaskDefinition/main.tf b/infrastructure/cloud/modules/ECS/TaskDefinition/main.tf index 4e2da487..6bfc2c2f 100644 --- a/infrastructure/cloud/modules/ECS/TaskDefinition/main.tf +++ b/infrastructure/cloud/modules/ECS/TaskDefinition/main.tf @@ -7,11 +7,11 @@ resource "aws_ecs_task_definition" "ecs_td" { execution_role_arn = var.ecs_execution_role_arn task_role_arn = var.ecs_execution_role_arn - # lifecycle { - # # Since the dummy-image will be replaced when the GHA pipeline runs, - # # the whole container_definition edits has been ignored. - # ignore_changes = [container_definitions] - # } + lifecycle { + # Since the dummy-image will be replaced when the GHA pipeline runs, + # the whole container_definition edits has been ignored. + ignore_changes = [container_definitions] + } container_definitions = jsonencode([ { @@ -28,7 +28,7 @@ resource "aws_ecs_task_definition" "ecs_td" { logConfiguration = { logDriver = "awslogs" options = { - "awslogs-group" = "/ecs/${var.name}-td" + "awslogs-group" = var.log_group_name "awslogs-region" = var.region "awslogs-stream-prefix" = "ecs" } diff --git a/infrastructure/cloud/modules/ECS/TaskDefinition/variables.tf b/infrastructure/cloud/modules/ECS/TaskDefinition/variables.tf index 0645b12d..9c8ec172 100644 --- a/infrastructure/cloud/modules/ECS/TaskDefinition/variables.tf +++ b/infrastructure/cloud/modules/ECS/TaskDefinition/variables.tf @@ -68,3 +68,8 @@ variable "image_name" { type = string default = "dummy-image" } + +variable "log_group_name" { + description = "The Cloudwatch Log Group Name" + type = string +} diff --git a/infrastructure/cloud/modules/IAM/main.tf b/infrastructure/cloud/modules/IAM/main.tf index 19e2fc6a..32cb8a0d 100644 --- a/infrastructure/cloud/modules/IAM/main.tf +++ b/infrastructure/cloud/modules/IAM/main.tf @@ -66,10 +66,8 @@ resource "aws_iam_role_policy" "ecs_execution_policy" { Action = [ "secretsmanager:GetSecretValue" ], - Effect = "Allow", - Resource = [ - "arn:aws:secretsmanager:*:*:secret:external/*" - ] + Effect = "Allow", + Resource = var.secrets_arn_list }, { Action = [ @@ -256,7 +254,9 @@ resource "aws_iam_policy" "lambda_role_policy" { { "Action" : [ "kms:Encrypt", - "kms:Decrypt" + "kms:Decrypt", + "kms:GenerateDataKey", + "kms:GenerateDataKeyWithoutPlaintext" ], "Effect" : "Allow", "Resource" : [ @@ -268,6 +268,8 @@ resource "aws_iam_policy" "lambda_role_policy" { "secretsmanager:GetSecretValue", "secretsmanager:DescribeSecret", "secretsmanager:PutSecretValue", + "secretsmanager:UpdateSecret", + "secretsmanager:UpdateSecretVersionStage" ] Effect = "Allow" Resource = var.secrets_arn_list @@ -280,6 +282,24 @@ resource "aws_iam_policy" "lambda_role_policy" { "logs:PutLogEvents" ] Resource = "arn:aws:logs:*:*:log-group:/aws/lambda/${var.app_name}-*-lambda-${var.environment}:*" + }, + { + "Effect" : "Allow", + "Action" : [ + "ecs:UpdateService", + "ecs:DescribeServices", + "ecs:ListServices" + ], + "Resource" : "*" + }, + { + "Effect" : "Allow", + "Action" : [ + "ecr:GetAuthorizationToken", + "ecr:BatchGetImage", + "ecr:BatchCheckLayerAvailability" + ], + "Resource" : "*" } ] }) diff --git a/infrastructure/cloud/modules/KMS/main.tf b/infrastructure/cloud/modules/KMS/main.tf index e6ed8100..0fbfb3b1 100644 --- a/infrastructure/cloud/modules/KMS/main.tf +++ b/infrastructure/cloud/modules/KMS/main.tf @@ -45,11 +45,18 @@ resource "aws_kms_key_policy" "kms_key_policy" { Principal = { Service = "logs.amazonaws.com" } - Action = [ - "kms:Decrypt", - "kms:Encrypt", - "kms:GenerateDataKey" - ] + Action = "kms:*" + Resource = "*" + }, + + # Allow Lambda Functions to use the key + { + Sid = "AllowLambdasUsage" + Effect = "Allow" + Principal = { + Service = "lambda.amazonaws.com" + } + Action = "kms:*" Resource = "*" } ] diff --git a/infrastructure/cloud/modules/Lambda/main.tf b/infrastructure/cloud/modules/Lambda/main.tf index e7dde05e..0b15326c 100644 --- a/infrastructure/cloud/modules/Lambda/main.tf +++ b/infrastructure/cloud/modules/Lambda/main.tf @@ -1,12 +1,35 @@ locals { + # Keys should match the folder name in lambda code + default_functions = { + "get-locations" = { + http_method = "GET" + resource_path = "/locations" + }, + "get-rooms" = { + http_method = "GET" + resource_path = "/locations/rooms" + }, + "search-criminal-files" = { + http_method = "GET" + resource_path = "/files/criminal" + }, + "search-civil-files" = { + http_method = "GET" + resource_path = "/files/civil" + } + } + lambda_functions = { - for k, v in var.functions : k => { - name = k - memory_size = lookup(v, "memory_size", 2048) - timeout = lookup(v, "timeout", 30) - http_method = v.http_method - resource_path = v.resource_path - env_variables = v.env_variables + for k, v in merge(local.default_functions, var.functions) : k => { + name = k + memory_size = lookup(v, "memory_size", 2048) + timeout = lookup(v, "timeout", 30) + http_method = v.http_method + resource_path = v.resource_path + statement_id_prefix = lookup(v, "statement_id_prefix", "AllowAPIGatewayInvoke") + principal = lookup(v, "principal", "apigateway.amazonaws.com") + env_variables = lookup(v, "env_variables", {}) + source_arn = lookup(v, "source_arn", "${var.apigw_execution_arn}/*/${v.http_method}${v.resource_path}") } } @@ -42,9 +65,9 @@ resource "aws_lambda_function" "lambda" { resource "aws_lambda_permission" "lambda_permissions" { for_each = local.lambda_functions - statement_id = "AllowAPIGatewayInvoke-${each.key}" + statement_id = "${each.value.statement_id_prefix}-${each.key}" action = "lambda:InvokeFunction" function_name = aws_lambda_function.lambda[each.key].arn - principal = "apigateway.amazonaws.com" - source_arn = "${var.apigw_execution_arn}/*/${each.value.http_method}${each.value.resource_path}" + principal = each.value.principal + source_arn = each.value.source_arn } diff --git a/infrastructure/cloud/modules/Lambda/outputs.tf b/infrastructure/cloud/modules/Lambda/outputs.tf index acf0503c..7c77e02f 100644 --- a/infrastructure/cloud/modules/Lambda/outputs.tf +++ b/infrastructure/cloud/modules/Lambda/outputs.tf @@ -2,9 +2,10 @@ output "lambda_functions" { value = { for name, lambda in aws_lambda_function.lambda : name => { name = lambda.function_name - http_method = var.functions[name].http_method - resource_path = var.functions[name].resource_path + http_method = local.lambda_functions[name].http_method + resource_path = local.lambda_functions[name].resource_path invoke_arn = lambda.invoke_arn + arn = lambda.arn } } } diff --git a/infrastructure/cloud/modules/Lambda/variables.tf b/infrastructure/cloud/modules/Lambda/variables.tf index 6d9b443f..006dd6a6 100644 --- a/infrastructure/cloud/modules/Lambda/variables.tf +++ b/infrastructure/cloud/modules/Lambda/variables.tf @@ -16,31 +16,16 @@ variable "lambda_role_arn" { variable "functions" { description = "Lambda functions config" type = map(object({ - http_method = string - resource_path = string - env_variables = optional(map(string), {}) - timeout = optional(number, 300) - memory_size = optional(number, 2048) + http_method = string + resource_path = string + env_variables = optional(map(string), {}) + timeout = optional(number, 300) + memory_size = optional(number, 2048) + statement_id_prefix = optional(string, "AllowAPIGatewayInvoke") + principal = optional(string, "apigateway.amazonaws.com") + source_arn = optional(string, null) })) - default = { - # Keys should match the folder name in lambda code - "get-locations" = { - http_method = "GET" - resource_path = "/locations" - }, - "get-rooms" = { - http_method = "GET" - resource_path = "/locations/rooms" - }, - "search-criminal-files" = { - http_method = "GET" - resource_path = "/files/criminal" - }, - "search-civil-files" = { - http_method = "GET" - resource_path = "/files/civil" - } - } + default = {} } variable "apigw_execution_arn" { diff --git a/infrastructure/cloud/modules/SecretsManager/main.tf b/infrastructure/cloud/modules/SecretsManager/main.tf index bacd8a49..8f474e91 100644 --- a/infrastructure/cloud/modules/SecretsManager/main.tf +++ b/infrastructure/cloud/modules/SecretsManager/main.tf @@ -181,3 +181,27 @@ resource "aws_secretsmanager_secret_version" "mtls_cert_secret_value" { ca = "" }) } + +resource "aws_secretsmanager_secret" "api_authorizer_secret" { + name = "${var.app_name}-api-authorizer-secret-${var.environment}" + kms_key_id = var.kms_key_arn + +} + +resource "random_uuid" "initial_api_auth_value" {} + +resource "aws_secretsmanager_secret_rotation" "api_authorizer_secret_rotation" { + secret_id = aws_secretsmanager_secret.api_authorizer_secret.id + rotation_lambda_arn = var.rotate_key_lambda_arn + + rotation_rules { + schedule_expression = "cron(0 8 * * ? *)" # Daily @ 8AM UTC (12AM PST) + } +} + +resource "aws_secretsmanager_secret_version" "api_authorizer_secret_value" { + secret_id = aws_secretsmanager_secret.api_authorizer_secret.id + secret_string = jsonencode({ + "verifyKey" = random_uuid.initial_api_auth_value.result + }) +} diff --git a/infrastructure/cloud/modules/SecretsManager/output.tf b/infrastructure/cloud/modules/SecretsManager/output.tf index 7c3b8803..654e7464 100644 --- a/infrastructure/cloud/modules/SecretsManager/output.tf +++ b/infrastructure/cloud/modules/SecretsManager/output.tf @@ -11,7 +11,8 @@ output "secrets_arn_list" { aws_secretsmanager_secret.mtls_cert_secret.arn, aws_secretsmanager_secret.request_secret.arn, aws_secretsmanager_secret.splunk_secret.arn, - aws_secretsmanager_secret.user_services_client_secret.arn + aws_secretsmanager_secret.user_services_client_secret.arn, + aws_secretsmanager_secret.api_authorizer_secret.arn ] } @@ -23,6 +24,7 @@ output "api_secrets" { ["Auth__UserId", "${aws_secretsmanager_secret.auth_secret.arn}:userId::"], ["Auth__UserPassword", "${aws_secretsmanager_secret.auth_secret.arn}:userPassword::"], ["Auth__AllowSiteMinderUserType", "${aws_secretsmanager_secret.auth_secret.arn}:allowSiteMinderUserType::"], + ["AuthorizerKey", "${aws_secretsmanager_secret.api_authorizer_secret.arn}:verifyKey::"], ["DatabaseConnectionString", "${aws_secretsmanager_secret.database_secret.arn}:dbConnectionString::"], ["DataProtectionKeyEncryptionKey", "${aws_secretsmanager_secret.misc_secret.arn}:dataProtectionKeyEncryptionKey::"], ["FileServicesClient__Username", "${aws_secretsmanager_secret.file_services_client_secret.arn}:username::"], @@ -78,3 +80,7 @@ output "db_password" { output "mtls_secret_name" { value = aws_secretsmanager_secret.mtls_cert_secret.name } + +output "api_authorizer_secret" { + value = aws_secretsmanager_secret.api_authorizer_secret +} diff --git a/infrastructure/cloud/modules/SecretsManager/variables.tf b/infrastructure/cloud/modules/SecretsManager/variables.tf index b234206c..f45dc2a8 100644 --- a/infrastructure/cloud/modules/SecretsManager/variables.tf +++ b/infrastructure/cloud/modules/SecretsManager/variables.tf @@ -17,3 +17,8 @@ variable "kms_key_arn" { description = "The KMS Key ARN" type = string } + +variable "rotate_key_lambda_arn" { + description = "The Rotate Key Lambda ARN" + type = string +}