diff --git a/deployment/cdk/opensearch-service-migration/lib/common-utilities.ts b/deployment/cdk/opensearch-service-migration/lib/common-utilities.ts index 19dcb5e5f..a53084432 100644 --- a/deployment/cdk/opensearch-service-migration/lib/common-utilities.ts +++ b/deployment/cdk/opensearch-service-migration/lib/common-utilities.ts @@ -424,12 +424,40 @@ function parseAuth(json: any): ClusterAuth | null { return null; // Invalid auth type } +// Validate a proper url string is provided and return an url string which contains a protocol, host name, and port. +// If a port is not provided, the default protocol port (e.g. 443, 80) will be explicitly added +export function validateAndReturnFormattedHttpURL(urlString: string) { + // URL will throw error if the urlString is invalid + const url = new URL(urlString); + if (url.protocol !== "http:" && url.protocol !== "https:") { + throw new Error(`Invalid url protocol for endpoint: ${urlString} was expecting 'http' or 'https'`) + } + if (url.pathname !== "/") { + throw new Error(`Provided endpoint: ${urlString} must not contain a path: ${url.pathname}`) + } + // URLs that contain the default protocol port (e.g. 443, 80) will not show in the URL toString() + let formattedUrlString = url.toString() + if (formattedUrlString.endsWith("/")) { + formattedUrlString = formattedUrlString.slice(0, -1) + } + if (!url.port) { + if (url.protocol === "http:") { + formattedUrlString = formattedUrlString.concat(":80") + } + else { + formattedUrlString = formattedUrlString.concat(":443") + } + } + return formattedUrlString +} + // eslint-disable-next-line @typescript-eslint/no-explicit-any export function parseClusterDefinition(json: any): ClusterYaml { - const endpoint = json.endpoint + let endpoint = json.endpoint if (!endpoint) { throw new Error('Missing required field in cluster definition: endpoint') } + endpoint = validateAndReturnFormattedHttpURL(endpoint) const version = json.version; const auth = parseAuth(json.auth) if (!auth) { @@ -452,18 +480,18 @@ export function isRegionGovCloud(region: string): boolean { * * This allows us to create a private ECR repo for any image allowing us to have a consistent * experience across VPCs and regions (e.g. running within VPC in gov-cloud with no internet access) - * + * * This works by creating a temp Dockerfile with only the FROM with the param imageName and * using that Dockerfile with cdk.assets to create a local Docker image asset. * * @param {string} imageName - The name of the Docker image to save as a tarball and use in CDK. * @returns {ContainerImage} - A `ContainerImage` object representing the Docker image asset. - */ + */ export function makeLocalAssetContainerImage(scope: Construct, imageName: string): ContainerImage { const sanitizedImageName = imageName.replace(/[^a-zA-Z0-9-_]/g, '_'); const tempDir = mkdtempSync(join(tmpdir(), 'docker-build-' + sanitizedImageName)); const dockerfilePath = join(tempDir, 'Dockerfile'); - + let imageHash = null; try { // Update the image if it is not a local image @@ -481,7 +509,7 @@ export function makeLocalAssetContainerImage(scope: Construct, imageName: string CdkLogger.error('Error fetching the actual hash for the image: ' + imageName + ' Error: ' + error); throw new Error('Error fetching the image hash for the image: ' + imageName + ' Error: ' + error); } - + const dockerfileContent = ` FROM ${imageName} `; diff --git a/deployment/cdk/opensearch-service-migration/lib/network-stack.ts b/deployment/cdk/opensearch-service-migration/lib/network-stack.ts index 6621c04e5..7e391419b 100644 --- a/deployment/cdk/opensearch-service-migration/lib/network-stack.ts +++ b/deployment/cdk/opensearch-service-migration/lib/network-stack.ts @@ -42,33 +42,6 @@ export class NetworkStack extends Stack { public readonly albTargetProxyTG: IApplicationTargetGroup; public readonly albSourceClusterTG: IApplicationTargetGroup; - // Validate a proper url string is provided and return an url string which contains a protocol, host name, and port. - // If a port is not provided, the default protocol port (e.g. 443, 80) will be explicitly added - static validateAndReturnFormattedHttpURL(urlString: string) { - // URL will throw error if the urlString is invalid - const url = new URL(urlString); - if (url.protocol !== "http:" && url.protocol !== "https:") { - throw new Error(`Invalid url protocol for target endpoint: ${urlString} was expecting 'http' or 'https'`) - } - if (url.pathname !== "/") { - throw new Error(`Provided target endpoint: ${urlString} must not contain a path: ${url.pathname}`) - } - // URLs that contain the default protocol port (e.g. 443, 80) will not show in the URL toString() - let formattedUrlString = url.toString() - if (formattedUrlString.endsWith("/")) { - formattedUrlString = formattedUrlString.slice(0, -1) - } - if (!url.port) { - if (url.protocol === "http:") { - formattedUrlString = formattedUrlString.concat(":80") - } - else { - formattedUrlString = formattedUrlString.concat(":443") - } - } - return formattedUrlString - } - private validateVPC(vpc: IVpc) { let uniqueAzPrivateSubnets: string[] = [] if (vpc.privateSubnets.length > 0) { @@ -114,7 +87,7 @@ export class NetworkStack extends Stack { InterfaceVpcEndpointAwsService.XRAY, // X-Ray Traces isStackInGovCloud(this) ? InterfaceVpcEndpointAwsService.ELASTIC_FILESYSTEM_FIPS : // EFS Control Plane GovCloud - InterfaceVpcEndpointAwsService.ELASTIC_FILESYSTEM, // EFS Control Plane + InterfaceVpcEndpointAwsService.ELASTIC_FILESYSTEM, // EFS Control Plane ]; interfaceEndpoints.forEach(service => createInterfaceVpcEndpoint(service)); @@ -288,9 +261,8 @@ export class NetworkStack extends Stack { }); if (props.targetClusterEndpoint) { - const formattedClusterEndpoint = NetworkStack.validateAndReturnFormattedHttpURL(props.targetClusterEndpoint); const deployId = props.addOnMigrationDeployId ? props.addOnMigrationDeployId : props.defaultDeployId; - createMigrationStringParameter(this, formattedClusterEndpoint, { + createMigrationStringParameter(this, props.targetClusterEndpoint, { stage: props.stage, defaultDeployId: deployId, parameter: MigrationSSMParameter.OS_CLUSTER_ENDPOINT diff --git a/deployment/cdk/opensearch-service-migration/test/common-utilities.test.ts b/deployment/cdk/opensearch-service-migration/test/common-utilities.test.ts index 490fdfc31..ce07aac55 100644 --- a/deployment/cdk/opensearch-service-migration/test/common-utilities.test.ts +++ b/deployment/cdk/opensearch-service-migration/test/common-utilities.test.ts @@ -3,7 +3,8 @@ import { parseClusterDefinition, validateFargateCpuArch, parseArgsToDict, - appendArgIfNotInExtraArgs + appendArgIfNotInExtraArgs, + validateAndReturnFormattedHttpURL } from "../lib/common-utilities"; import {describe, test, expect} from '@jest/globals'; @@ -244,7 +245,7 @@ describe('validateFargateCpuArch', () => { test('parseClusterDefinition with basic auth parameters', () => { const clusterDefinition = { - endpoint: 'https://target-cluster', + endpoint: 'https://target-cluster:443', version: 'ES_7.10', auth: { type: 'basic', @@ -263,7 +264,7 @@ describe('validateFargateCpuArch', () => { test('parseClusterDefinition with no auth', () => { const clusterDefinition = { - endpoint: 'XXXXXXXXXXXXXXXXXXXXXX', + endpoint: 'https://vpc-domain-abcdef.us-east-1.es.amazonaws.com:443', auth: {"type": "none"} } const parsed = parseClusterDefinition(clusterDefinition); @@ -274,7 +275,7 @@ describe('validateFargateCpuArch', () => { test('parseClusterDefinition with sigv4 auth', () => { const clusterDefinition = { - endpoint: 'XXXXXXXXXXXXXXXXXXXXXX', + endpoint: 'https://vpc-domain-abcdef.us-east-1.es.amazonaws.com:443', auth: { type: 'sigv4', region: 'us-east-1', @@ -288,4 +289,57 @@ describe('validateFargateCpuArch', () => { expect(parsed.auth.sigv4?.region).toBe(clusterDefinition.auth.region); expect(parsed.auth.sigv4?.serviceSigningName).toBe(clusterDefinition.auth.serviceSigningName); }) + + test('Test valid https imported target cluster endpoint with port is formatted correctly', () => { + const inputTargetEndpoint = "https://vpc-domain-abcdef.us-east-1.es.amazonaws.com:443" + const expectedFormattedEndpoint = "https://vpc-domain-abcdef.us-east-1.es.amazonaws.com:443" + const actualFormatted = validateAndReturnFormattedHttpURL(inputTargetEndpoint) + expect(actualFormatted).toEqual(expectedFormattedEndpoint) + }); + + test('Test valid https imported target cluster endpoint with no port is formatted correctly', () => { + const inputTargetEndpoint = "https://vpc-domain-abcdef.us-east-1.es.amazonaws.com" + const expectedFormattedEndpoint = "https://vpc-domain-abcdef.us-east-1.es.amazonaws.com:443" + const actualFormatted = validateAndReturnFormattedHttpURL(inputTargetEndpoint) + expect(actualFormatted).toEqual(expectedFormattedEndpoint) + }); + + test('Test valid http imported target cluster endpoint with no port is formatted correctly', () => { + const inputTargetEndpoint = "http://vpc-domain-abcdef.us-east-1.es.amazonaws.com" + const expectedFormattedEndpoint = "http://vpc-domain-abcdef.us-east-1.es.amazonaws.com:80" + const actualFormatted = validateAndReturnFormattedHttpURL(inputTargetEndpoint) + expect(actualFormatted).toEqual(expectedFormattedEndpoint) + }); + + test('Test valid imported target cluster endpoint ending in slash is formatted correctly', () => { + const inputTargetEndpoint = "https://vpc-domain-abcdef.us-east-1.es.amazonaws.com/" + const expectedFormattedEndpoint = "https://vpc-domain-abcdef.us-east-1.es.amazonaws.com:443" + const actualFormatted = validateAndReturnFormattedHttpURL(inputTargetEndpoint) + expect(actualFormatted).toEqual(expectedFormattedEndpoint) + }); + + test('Test valid imported target cluster endpoint having port and ending in slash is formatted correctly', () => { + const inputTargetEndpoint = "https://vpc-domain-abcdef.us-east-1.es.amazonaws.com:443/" + const expectedFormattedEndpoint = "https://vpc-domain-abcdef.us-east-1.es.amazonaws.com:443" + const actualFormatted = validateAndReturnFormattedHttpURL(inputTargetEndpoint) + expect(actualFormatted).toEqual(expectedFormattedEndpoint) + }); + + test('Test target cluster endpoint with no protocol throws error', () => { + const inputTargetEndpoint = "vpc-domain-abcdef.us-east-1.es.amazonaws.com:443/" + const validateAndFormatURL = () => validateAndReturnFormattedHttpURL(inputTargetEndpoint) + expect(validateAndFormatURL).toThrow() + }); + + test('Test target cluster endpoint with path throws error', () => { + const inputTargetEndpoint = "https://vpc-domain-abcdef.us-east-1.es.amazonaws.com:443/indexes" + const validateAndFormatURL = () => validateAndReturnFormattedHttpURL(inputTargetEndpoint) + expect(validateAndFormatURL).toThrow() + }); + + test('Test invalid target cluster endpoint throws error', () => { + const inputTargetEndpoint = "vpc-domain-abcdef" + const validateAndFormatURL = () => validateAndReturnFormattedHttpURL(inputTargetEndpoint) + expect(validateAndFormatURL).toThrow() + }); }) diff --git a/deployment/cdk/opensearch-service-migration/test/network-stack.test.ts b/deployment/cdk/opensearch-service-migration/test/network-stack.test.ts index e7284036e..cd399de55 100644 --- a/deployment/cdk/opensearch-service-migration/test/network-stack.test.ts +++ b/deployment/cdk/opensearch-service-migration/test/network-stack.test.ts @@ -159,57 +159,4 @@ describe('NetworkStack Tests', () => { // Verify the total number of VPC Endpoints networkTemplate.resourceCountIs('AWS::EC2::VPCEndpoint', expectedEndpoints.length); }); - - test('Test valid https imported target cluster endpoint with port is formatted correctly', () => { - const inputTargetEndpoint = "https://vpc-domain-abcdef.us-east-1.es.amazonaws.com:443" - const expectedFormattedEndpoint = "https://vpc-domain-abcdef.us-east-1.es.amazonaws.com:443" - const actualFormatted = NetworkStack.validateAndReturnFormattedHttpURL(inputTargetEndpoint) - expect(actualFormatted).toEqual(expectedFormattedEndpoint) - }); - - test('Test valid https imported target cluster endpoint with no port is formatted correctly', () => { - const inputTargetEndpoint = "https://vpc-domain-abcdef.us-east-1.es.amazonaws.com" - const expectedFormattedEndpoint = "https://vpc-domain-abcdef.us-east-1.es.amazonaws.com:443" - const actualFormatted = NetworkStack.validateAndReturnFormattedHttpURL(inputTargetEndpoint) - expect(actualFormatted).toEqual(expectedFormattedEndpoint) - }); - - test('Test valid http imported target cluster endpoint with no port is formatted correctly', () => { - const inputTargetEndpoint = "http://vpc-domain-abcdef.us-east-1.es.amazonaws.com" - const expectedFormattedEndpoint = "http://vpc-domain-abcdef.us-east-1.es.amazonaws.com:80" - const actualFormatted = NetworkStack.validateAndReturnFormattedHttpURL(inputTargetEndpoint) - expect(actualFormatted).toEqual(expectedFormattedEndpoint) - }); - - test('Test valid imported target cluster endpoint ending in slash is formatted correctly', () => { - const inputTargetEndpoint = "https://vpc-domain-abcdef.us-east-1.es.amazonaws.com/" - const expectedFormattedEndpoint = "https://vpc-domain-abcdef.us-east-1.es.amazonaws.com:443" - const actualFormatted = NetworkStack.validateAndReturnFormattedHttpURL(inputTargetEndpoint) - expect(actualFormatted).toEqual(expectedFormattedEndpoint) - }); - - test('Test valid imported target cluster endpoint having port and ending in slash is formatted correctly', () => { - const inputTargetEndpoint = "https://vpc-domain-abcdef.us-east-1.es.amazonaws.com:443/" - const expectedFormattedEndpoint = "https://vpc-domain-abcdef.us-east-1.es.amazonaws.com:443" - const actualFormatted = NetworkStack.validateAndReturnFormattedHttpURL(inputTargetEndpoint) - expect(actualFormatted).toEqual(expectedFormattedEndpoint) - }); - - test('Test target cluster endpoint with no protocol throws error', () => { - const inputTargetEndpoint = "vpc-domain-abcdef.us-east-1.es.amazonaws.com:443/" - const validateAndFormatURL = () => NetworkStack.validateAndReturnFormattedHttpURL(inputTargetEndpoint) - expect(validateAndFormatURL).toThrow() - }); - - test('Test target cluster endpoint with path throws error', () => { - const inputTargetEndpoint = "https://vpc-domain-abcdef.us-east-1.es.amazonaws.com:443/indexes" - const validateAndFormatURL = () => NetworkStack.validateAndReturnFormattedHttpURL(inputTargetEndpoint) - expect(validateAndFormatURL).toThrow() - }); - - test('Test invalid target cluster endpoint throws error', () => { - const inputTargetEndpoint = "vpc-domain-abcdef" - const validateAndFormatURL = () => NetworkStack.validateAndReturnFormattedHttpURL(inputTargetEndpoint) - expect(validateAndFormatURL).toThrow() - }); });