Skip to content

Commit

Permalink
fix: disable searchable nodeToNode encryption unless it is already de…
Browse files Browse the repository at this point in the history
…ployed to mitigate impact from enabling or disabling.
  • Loading branch information
alharris-at committed Jan 11, 2023
1 parent 3b729cb commit 0283602
Show file tree
Hide file tree
Showing 15 changed files with 1,076 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,9 @@ const getTransformerFactoryV2 = (
];

if (options?.addSearchableTransformer) {
transformerList.push(new SearchableModelTransformerV2());
const resourceDirParts = resourceDir.split(path.sep);
const apiName = resourceDirParts[resourceDirParts.length - 1];
transformerList.push(new SearchableModelTransformerV2(apiName));
}

const customTransformersConfig = await loadProject(resourceDir);
Expand Down
31 changes: 27 additions & 4 deletions packages/amplify-e2e-core/src/categories/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -982,8 +982,31 @@ export async function validateRestApiMeta(projRoot: string, meta?: any) {
expect(seenAtLeastOneFunc).toBe(true);
}

export function setStackMapping(projRoot: string, projectName: string, stackMapping: Record<string, string>) {
const transformConfig = getTransformConfig(projRoot, projectName);
transformConfig.StackMapping = stackMapping;
setTransformConfig(projRoot, projectName, transformConfig);
export function setStackMapping(projRoot: string, apiName: string, stackMapping: Record<string, string>) {
setTransformConfigValue(projRoot, apiName, 'StackMapping', stackMapping);
}

/**
* Set a specific key in the `transform.conf.json` file.
* @param projRoot root directory for the project
* @param apiName the name of the api to modify
* @param key the key in the transform.conf.json value
* @param value the value to set in the file
*/
export const setTransformConfigValue = (projRoot: string, apiName: string, key: string, value: any): void => {
const transformConfig = getTransformConfig(projRoot, apiName);
transformConfig[key] = value;
setTransformConfig(projRoot, apiName, transformConfig);
};

/**
* Remove a specified key from the `transform.conf.json` file.
* @param projRoot root directory for the project
* @param apiName the name of the api to modify
* @param key the key in the transform.conf.json value
*/
export const removeTransformConfigValue = (projRoot: string, apiName: string, key: string): void => {
const transformConfig = getTransformConfig(projRoot, apiName);
delete transformConfig[key];
setTransformConfig(projRoot, apiName, transformConfig);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
import {
initJSProjectWithProfile,
deleteProject,
amplifyPush,
createRandomName,
addAuthWithDefault,
setTransformConfigValue,
} from 'amplify-category-api-e2e-core';
import { addApiWithoutSchema, updateApiSchema, getProjectMeta } from 'amplify-category-api-e2e-core';
import { createNewProjectDir, deleteProjectDir } from 'amplify-category-api-e2e-core';
import gql from 'graphql-tag';
import AWSAppSyncClient, { AUTH_TYPE } from 'aws-appsync';
import * as path from 'path';
import * as fs from 'fs-extra';

(global as any).fetch = require('node-fetch');

describe('searchable deployments succeed with various NodeToNodeEncryption flag states', () => {
let projRoot: string;
let projectName: string;
let appSyncClient = undefined;

beforeEach(async () => {
projectName = createRandomName();
projRoot = await createNewProjectDir(createRandomName());
await initJSProjectWithProfile(projRoot, {
name: projectName,
});
await addAuthWithDefault(projRoot, {});
});

afterEach(async () => {
await deleteProject(projRoot);
deleteProjectDir(projRoot);
});

it('succeeds deployment with NodeToNodeEncryption set to true', async () => {
const v2Schema = 'transformer_migration/searchable-v2.graphql';

await addApiWithoutSchema(projRoot, { apiName: projectName });
updateApiSchema(projRoot, projectName, v2Schema);
setTransformConfigValue(projRoot, projectName, 'NodeToNodeEncryption', true);
await amplifyPush(projRoot);

appSyncClient = getAppSyncClientFromProj(projRoot);
await runAndValidateQuery('test1', 'test1', 10);

const searchableStackPath = path.join(projRoot, 'amplify', 'backend', 'api', projectName, 'build', 'stacks', 'SearchableStack.json');
const searchableStack = JSON.parse(fs.readFileSync(searchableStackPath).toString());
const searchDomainProps = searchableStack.Resources.OpenSearchDomain.Properties;

expect(searchDomainProps).toHaveProperty('NodeToNodeEncryptionOptions');
expect(searchDomainProps.NodeToNodeEncryptionOptions.Enabled).toEqual(true);
});

it('succeeds deployment with NodeToNodeEncryption set to false', async () => {
const v2Schema = 'transformer_migration/searchable-v2.graphql';

await addApiWithoutSchema(projRoot, { apiName: projectName });
updateApiSchema(projRoot, projectName, v2Schema);
setTransformConfigValue(projRoot, projectName, 'NodeToNodeEncryption', false);
await amplifyPush(projRoot);

appSyncClient = getAppSyncClientFromProj(projRoot);
await runAndValidateQuery('test1', 'test1', 10);

const searchableStackPath = path.join(projRoot, 'amplify', 'backend', 'api', projectName, 'build', 'stacks', 'SearchableStack.json');
const searchableStack = JSON.parse(fs.readFileSync(searchableStackPath).toString());
const searchDomainProps = searchableStack.Resources.OpenSearchDomain.Properties;

expect(searchDomainProps).not.toHaveProperty('NodeToNodeEncryptionOptions');
});

const getAppSyncClientFromProj = (projRoot: string) => {
const meta = getProjectMeta(projRoot);
const region = meta['providers']['awscloudformation']['Region'] as string;
const { output } = meta.api[projectName];
const url = output.GraphQLAPIEndpointOutput as string;
const apiKey = output.GraphQLAPIKeyOutput as string;

return new AWSAppSyncClient({
url,
region,
disableOffline: true,
auth: {
type: AUTH_TYPE.API_KEY,
apiKey,
},
});
};

const fragments = [`fragment FullTodo on Todo { id name description count }`];

const runMutation = async (query: string) => {
try {
const q = [query, ...fragments].join('\n');
const response = await appSyncClient.mutate({
mutation: gql(q),
fetchPolicy: 'no-cache',
});
return response;
} catch (e) {
console.error(e);
return null;
}
};

const runQuery = async (query: string) => {
try {
const q = [query, ...fragments].join('\n');
const response = await appSyncClient.query({
query: gql(q),
fetchPolicy: 'no-cache',
});
return response;
} catch (e) {
console.error(e);
return null;
}
};

const createEntry = async (name: string, description: string, count: number) => {
return await runMutation(getCreateTodosMutation(name, description, count));
};

const searchTodos = async () => {
return await runQuery(getTodos());
};

function getCreateTodosMutation(name: string, description: string, count: number): string {
return `mutation {
createTodo(input: {
name: "${name}"
description: "${description}"
count: ${count}
}) { ...FullTodo }
}`;
}

function getTodos() {
return `query {
searchTodos {
items {
...FullTodo
}
}
}`;
}

const runAndValidateQuery = async (name: string, description: string, count: number) => {
const response = await createEntry(name, description, count);
expect(response).toBeDefined();
expect(response.errors).toBeUndefined();
expect(response.data).toBeDefined();
expect(response.data.createTodo).toBeDefined();

await waitForOSPropagate();
const searchResponse = await searchTodos();

const expectedRows = 1;
expect(searchResponse).toBeDefined();
expect(searchResponse.errors).toBeUndefined();
expect(searchResponse.data).toBeDefined();
expect(searchResponse.data.searchTodos).toBeDefined();
expect(searchResponse.data.searchTodos.items).toHaveLength(expectedRows);
};

const waitForOSPropagate = async (initialWaitSeconds = 5, maxRetryCount = 5) => {
const expectedCount = 1;
let waitInMilliseconds = initialWaitSeconds * 1000;
let currentRetryCount = 0;
let searchResponse;

do {
await new Promise(r => setTimeout(r, waitInMilliseconds));
searchResponse = await searchTodos();
currentRetryCount += 1;
waitInMilliseconds = waitInMilliseconds * 2;
} while (searchResponse.data.searchTodos?.items?.length < expectedCount && currentRetryCount <= maxRetryCount);
};
});
Loading

0 comments on commit 0283602

Please sign in to comment.