From af906f413bd1032f62d079541629460d51e3c8c3 Mon Sep 17 00:00:00 2001 From: Aleksandr Kharitonov Date: Thu, 5 Sep 2024 12:01:09 +0000 Subject: [PATCH] Update Docspace reverse proxy (#1) Co-authored-by: Yaroslav Pshenichnikov Co-authored-by: Alexey Bannov Reviewed-on: https://git.onlyoffice.com/ONLYOFFICE/docspace_reverse_proxy/pulls/1 --- .github/workflows/sam-pipeline.yml | 2 + develop-template.yaml | 32 ++++++++++-- docspace-reverse-proxy/index.mjs | 66 ++++++++++++++++------- main-template.yaml | 4 +- zoom-reverse-proxy/index.mjs | 84 ++++++++++++++++++++++++++++++ 5 files changed, 164 insertions(+), 24 deletions(-) create mode 100644 zoom-reverse-proxy/index.mjs diff --git a/.github/workflows/sam-pipeline.yml b/.github/workflows/sam-pipeline.yml index 201484f..7dbb3f6 100644 --- a/.github/workflows/sam-pipeline.yml +++ b/.github/workflows/sam-pipeline.yml @@ -31,6 +31,7 @@ jobs: export DYNAMODB_TABLE_NAME="${{ secrets.MAIN_DYNAMODB_TABLE_NAME }}" fi sed -i "s/dynamodb_table_name_placeholder/${DYNAMODB_TABLE_NAME}/g" ./docspace-reverse-proxy/index.mjs + sed -i "s/dynamodb_table_name_placeholder/${DYNAMODB_TABLE_NAME}/g" ./zoom-reverse-proxy/index.mjs - name: Set viewer request domain name variable run: | @@ -52,6 +53,7 @@ jobs: fi awk -v rmap="$( temp_index.mjs && mv temp_index.mjs ./docspace-reverse-proxy/index.mjs awk -v rmap="$( temp_index.mjs && mv temp_index.mjs ./docspace-reverse-proxy/index.mjs + awk -v rmap="$( temp_index.mjs && mv temp_index.mjs ./zoom-reverse-proxy/index.mjs # Build and deploy stack - run: sam build -u --template-file ${GITHUB_REF_NAME}-template.yaml diff --git a/develop-template.yaml b/develop-template.yaml index 41e4425..82872c1 100644 --- a/develop-template.yaml +++ b/develop-template.yaml @@ -13,10 +13,10 @@ Resources: Properties: CodeUri: docspace-reverse-proxy/ Description: "" - MemorySize: 256 + MemorySize: 512 Timeout: 3 Handler: index.handler - Runtime: nodejs18.x + Runtime: nodejs20.x Architectures: - x86_64 EventInvokeConfig: @@ -39,7 +39,30 @@ Resources: MemorySize: 128 Timeout: 3 Handler: index.handler - Runtime: nodejs18.x + Runtime: nodejs20.x + Architectures: + - x86_64 + EventInvokeConfig: + MaximumEventAgeInSeconds: 21600 + MaximumRetryAttempts: 2 + EphemeralStorage: + Size: 512 + RuntimeManagementConfig: + UpdateRuntimeOn: Auto + SnapStart: + ApplyOn: None + PackageType: Zip + Role: !GetAtt DocspaceReverseProxyRole.Arn + + zoomreverseproxy: + Type: AWS::Serverless::Function + Properties: + CodeUri: zoom-reverse-proxy/ + Description: "" + MemorySize: 128 + Timeout: 3 + Handler: index.handler + Runtime: nodejs20.x Architectures: - x86_64 EventInvokeConfig: @@ -130,12 +153,13 @@ Resources: - logs:CreateLogGroup - logs:CreateLogStream - logs:PutLogEvents + - cloudwatch:PutMetricData Resource: "*" DyanmoDBFullAccessPolicy: Type: AWS::IAM::ManagedPolicy Properties: - Description: Allows functions to write logs + Description: Set dynamodb permissions Roles: - !Ref DocspaceReverseProxyRole PolicyDocument: diff --git a/docspace-reverse-proxy/index.mjs b/docspace-reverse-proxy/index.mjs index dca8a45..d23f31a 100644 --- a/docspace-reverse-proxy/index.mjs +++ b/docspace-reverse-proxy/index.mjs @@ -1,47 +1,77 @@ 'use strict'; +import { DynamoDBClient, GetItemCommand } from "@aws-sdk/client-dynamodb"; + +const DEBUG_MODE_ON = true; + +if (!DEBUG_MODE_ON) { + console = console || {}; + console.log = function(){}; +} + const cachedItem = {}; + const regionsMap = { -regionsMap_placeholder + regionsMap_placeholder }; - + const ddbRegionsMap = { -ddbRegionsMap_placeholder + ddbRegionsMap_placeholder }; - + const dynamodbTableName = "dynamodb_table_name_placeholder"; -const execRegionCode = process.env.AWS_REGION; +const execAWSRegion = process.env.AWS_REGION; var ddbClientRegion = ddbRegionsMap["default"]; -if (ddbRegionsMap[execRegionCode]) { - ddbClientRegion = ddbRegionsMap[execRegionCode]; +if (ddbRegionsMap[execAWSRegion]) { + ddbClientRegion = ddbRegionsMap[execAWSRegion]; console.log("change ddbClient region from %s to %s", ddbRegionsMap["default"], ddbClientRegion); } -import { DynamoDBClient, GetItemCommand } from "@aws-sdk/client-dynamodb"; +var ddbClient = null; +var ddbClientDefault = null; -async function getTenantRegion(ddbRegion, tenantDomain) { +function getDynamoDBClient(ddbRegion) { + + if (ddbRegion == ddbRegionsMap["default"]) { + if (ddbClientDefault == null) { + ddbClientDefault = new DynamoDBClient({ region: ddbRegionsMap["default"] }); + } + + return ddbClientDefault; + } else { + if (ddbClient == null) { + ddbClient = new DynamoDBClient({ region: ddbRegion }); + } + + return ddbClient; + } +} - console.log("getTenantRegion params ddbRegion: %s, tenantDomain: %s", ddbRegion, tenantDomain); - const ddbClient = new DynamoDBClient({ region: ddbRegion }); +async function getTenantRegion(ddbRegion, tenantDomain) { + console.log("getTenantRegion params ddbRegion: %s, tenantDomain: %s", ddbRegion, tenantDomain); const getItemParams = { Key: { - 'tenant_domain': { S: tenantDomain } + "tenant_domain": { S: tenantDomain } }, ProjectionExpression: "tenant_region", TableName: dynamodbTableName }; - console.log("[DynamoDb] before send command get item %s with tenant domain %s", JSON.stringify(getItemParams), tenantDomain); + console.log(`[DynamoDb] before send GetItemCommand ${JSON.stringify(getItemParams)} with tenant domain ${tenantDomain}`); + + const start = Date.now(); + + const response = await getDynamoDBClient(ddbRegion).send(new GetItemCommand(getItemParams)); - const response = await ddbClient.send(new GetItemCommand(getItemParams)); + const end = Date.now(); - console.log("[DynamoDb] responce send command get item %s with tenant domain %s", JSON.stringify(response), tenantDomain); + console.log(`[DynamoDb] after send GetItemCommand ${JSON.stringify(response)} with tenant domain ${tenantDomain}. Execution time: ${end - start} `); if (response && response.Item) { @@ -74,17 +104,17 @@ export const handler = async (event, context, callback) => { if (request.uri.toLowerCase() == "/apisystem/portal/register" && request.method == "POST") { console.log("START: Register portal request"); - console.log("Register portal request body %s", request.body); let body = JSON.parse(Buffer.from(request.body.data, 'base64').toString('utf8')); let regionFromRequest = body["awsRegion"]; let portalName = body["portalName"]; - + if(regionFromRequest !== null && regionFromRequest !== '' && regionFromRequest!==undefined) { regionFromRequest = regionFromRequest.toLowerCase(); } + originDomain = regionsMap[regionFromRequest]; console.log("Register portal request: Origin Domain is %s, awsRegion is %s", originDomain, regionFromRequest); @@ -112,7 +142,7 @@ export const handler = async (event, context, callback) => { console.log("Register portal request: Change request origin to %s", originDomain); } - console.log("request after changed %s", JSON.stringify(request)); + console.log("request after changed %s", JSON.stringify(request)); console.log("END: Register portal request"); diff --git a/main-template.yaml b/main-template.yaml index 7079849..75a073a 100644 --- a/main-template.yaml +++ b/main-template.yaml @@ -13,10 +13,10 @@ Resources: Properties: CodeUri: docspace-reverse-proxy/ Description: "" - MemorySize: 256 + MemorySize: 512 Timeout: 3 Handler: index.handler - Runtime: nodejs18.x + Runtime: nodejs20.x Architectures: - x86_64 EventInvokeConfig: diff --git a/zoom-reverse-proxy/index.mjs b/zoom-reverse-proxy/index.mjs new file mode 100644 index 0000000..cc8e048 --- /dev/null +++ b/zoom-reverse-proxy/index.mjs @@ -0,0 +1,84 @@ +'use strict'; + +const cachedItem = {}; +const regionsMap = { + regionsMap_placeholder +}; + +const ddbRegionsMap = { + ddbRegionsMap_placeholder +}; + +const dynamodbTableName = "dynamodb_table_name_placeholder"; + +const execRegionCode = process.env.AWS_REGION; + +var ddbClientRegion = ddbRegionsMap["default"]; + +if (ddbRegionsMap[execRegionCode]) { + ddbClientRegion = ddbRegionsMap[execRegionCode]; + + console.log("change ddbClient region from %s to %s", ddbRegionsMap["default"], ddbClientRegion); +} + +console.log("DynamoDB client region set to: %s", ddbClientRegion); + +import { DynamoDBClient, GetItemCommand } from "@aws-sdk/client-dynamodb"; + +async function getTenantRegion(ddbRegion, tenantDomain) { + console.log("Fetching tenant region for domain: %s from DynamoDB region: %s", tenantDomain, ddbRegion); + const ddbClient = new DynamoDBClient({ region: ddbRegion }); + const getItemParams = { + Key: { 'tenant_domain': { S: tenantDomain } }, + ProjectionExpression: "tenant_region", + TableName: dynamodbTableName + }; + + try { + const response = await ddbClient.send(new GetItemCommand(getItemParams)); + if (response.Item) { + const tenantRegion = regionsMap[response.Item["tenant_region"]["S"]]; + console.log("Tenant region fetched: %s", tenantRegion); + return tenantRegion; + } else { + console.warn("No data found for domain: %s", tenantDomain); + return null; + } + } catch (err) { + console.error("Error fetching from DynamoDB: %s", err); + return null; + } +} + +export async function handler(event) { + const request = event.Records[0].cf.request; + const headers = request.headers; + const fullDomain = headers.host[0].value.toLowerCase(); + const domainParts = fullDomain.split('.'); + + if (domainParts.length === 4 && domainParts[1] === 'devops' && domainParts[2] === 'onlyoffice' && domainParts[3] === 'io') { + const tenantDomain = fullDomain; + + // Check the cache first + if (cachedItem[tenantDomain] && cachedItem[tenantDomain].expiresOn > Date.now()) { + request.origin.custom.domainName = cachedItem[tenantDomain].value; + console.log("Origin fetched from cache for domain: %s", tenantDomain); + } else { + // Fetch the region from DynamoDB and update the origin + const tenantRegion = await getTenantRegion(ddbClientRegion, tenantDomain); + if (tenantRegion) { + request.origin.custom.domainName = tenantRegion; + cachedItem[tenantDomain] = { + value: tenantRegion, + expiresOn: Date.now() + 15 * 60 * 1000 // Cache for 15 minutes + }; + console.log("Origin updated to %s for domain: %s", tenantRegion, tenantDomain); + } else { + request.origin.custom.domainName = regionsMap["default"]; + console.log("Default origin (%s) used for domain: %s", regionsMap["default"], tenantDomain); + } + } + } + + return request; +}