-
Notifications
You must be signed in to change notification settings - Fork 77
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix: disable searchable nodeToNode encryption unless it is already de…
…ployed to mitigate impact from enabling or disabling.
- Loading branch information
1 parent
3b729cb
commit 0283602
Showing
15 changed files
with
1,076 additions
and
10 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
181 changes: 181 additions & 0 deletions
181
...__tests__/graphql-v2/searchable-node-to-node-encryption/searchable-new-deployment.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
}; | ||
}); |
Oops, something went wrong.