From d17e7493dd5f9dc6e34c141e78188d06b3705881 Mon Sep 17 00:00:00 2001 From: Jong Eun Lee Date: Tue, 10 Sep 2024 17:48:06 +0800 Subject: [PATCH] feat: empty sensitive envs values in Neo Session Launcher --- e2e/session-luancher.test.ts | 54 +++++++++++++++++ e2e/session.test.ts | 59 ------------------- react/src/components/EnvVarFormList.test.tsx | 35 +++++++++++ react/src/components/EnvVarFormList.tsx | 36 +++++++++++ react/src/components/VFolderTableFormItem.tsx | 1 + react/src/pages/SessionLauncherPage.tsx | 20 +++++-- 6 files changed, 140 insertions(+), 65 deletions(-) create mode 100644 e2e/session-luancher.test.ts delete mode 100644 e2e/session.test.ts create mode 100644 react/src/components/EnvVarFormList.test.tsx diff --git a/e2e/session-luancher.test.ts b/e2e/session-luancher.test.ts new file mode 100644 index 0000000000..d41ae993de --- /dev/null +++ b/e2e/session-luancher.test.ts @@ -0,0 +1,54 @@ +import { + createSession, + deleteSession, + loginAsUser, + navigateTo, +} from './test-util'; +import { test, expect } from '@playwright/test'; + +test.describe('NEO Sessions Launcher', () => { + test.beforeEach(async ({ page }) => { + await loginAsUser(page); + }); + + const sessionName = 'e2e-test-session'; + test('User can create session in NEO', async ({ page }) => { + await createSession(page, sessionName); + await deleteSession(page, sessionName); + }); + + test('Sensitive environment variables are cleared when the browser is reloaded.', async ({ + page, + }) => { + await navigateTo(page, 'session/start'); + await page + .getByRole('button', { name: '2 Environments & Resource' }) + .click(); + await page + .getByRole('button', { name: 'plus Add environment variables' }) + .click(); + await page.getByPlaceholder('Variable').fill('abc'); + await page.getByPlaceholder('Variable').press('Tab'); + await page.getByPlaceholder('Value').fill('123'); + await page + .getByRole('button', { name: 'plus Add environment variables' }) + .click(); + await page.locator('#envvars_1_variable').fill('password'); + await page.locator('#envvars_1_variable').press('Tab'); + await page.locator('#envvars_1_value').fill('hello'); + await page + .getByRole('button', { name: 'plus Add environment variables' }) + .click(); + await page.locator('#envvars_2_variable').fill('api_key'); + await page.locator('#envvars_2_variable').press('Tab'); + await page.locator('#envvars_2_value').fill('secret'); + await page.waitForTimeout(1000); // Wait for the form state to be saved as query param. + await page.reload(); + await expect( + page.locator('#envvars_1_value_help').getByText('Please enter a value.'), + ).toBeVisible(); + await expect( + page.locator('#envvars_2_value_help').getByText('Please enter a value.'), + ).toBeVisible(); + }); +}); diff --git a/e2e/session.test.ts b/e2e/session.test.ts deleted file mode 100644 index 66d54954c1..0000000000 --- a/e2e/session.test.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { - createSession, - deleteSession, - loginAsAdmin, - loginAsUser, - navigateTo, -} from './test-util'; -import { test, expect } from '@playwright/test'; - -test.describe('Sessions ', () => { - const sessionName = 'e2e-test-session'; - test('User can create session in NEO', async ({ page }) => { - await loginAsUser(page); - await createSession(page, sessionName); - await deleteSession(page, sessionName); - }); -}); - -test.describe('Restrict resource policy and see resource warning message', () => { - test('superadmin to modify keypair resource policy', async ({ page }) => { - await loginAsAdmin(page); - - // go to resource policy page - await navigateTo(page, 'resource-policy'); - - // modify resource limit (cpu, memory) to zero - await page - .getByRole('table') - .getByRole('button', { name: 'setting' }) - .click(); - await page.locator('.ant-checkbox-input').first().uncheck(); - await page.getByLabel('CPU(optional)').click(); - await page.getByLabel('CPU(optional)').fill('0'); - await page - .locator( - 'div:nth-child(2) > div > div > .ant-checkbox-wrapper > span:nth-child(2)', - ) - .first() - .uncheck(); - await page.getByLabel('Memory(optional)').click(); - await page.getByLabel('Memory(optional)').fill('0'); - await page.getByRole('button', { name: 'OK' }).click(); - - // go back to session page and see message in resource allocation section - await navigateTo(page, 'session/start'); - - await page.getByRole('button', { name: 'Next right' }).click(); - const notEnoughCPUResourceMsg = await page - .locator('#resource_cpu_help') - .getByText('Allocatable resources falls') - .textContent(); - const notEnoughRAMResourceMsg = await page - .getByText('Allocatable resources falls') - .nth(1) - .textContent(); - - expect(notEnoughCPUResourceMsg).toEqual(notEnoughRAMResourceMsg); - }); -}); diff --git a/react/src/components/EnvVarFormList.test.tsx b/react/src/components/EnvVarFormList.test.tsx new file mode 100644 index 0000000000..408dbe2d13 --- /dev/null +++ b/react/src/components/EnvVarFormList.test.tsx @@ -0,0 +1,35 @@ +// EnvVarFormList.test.tsx +import { sanitizeSensitiveEnv } from './EnvVarFormList'; + +describe('emptySensitiveEnv', () => { + it('should empty the value of sensitive environment variables', () => { + const envs = [ + { variable: 'SECRET_KEY', value: '12345' }, + { variable: 'API_KEY', value: 'abcdef' }, + { variable: 'NON_SENSITIVE', value: 'value' }, + ]; + + const result = sanitizeSensitiveEnv(envs); + + expect(result).toEqual([ + { variable: 'SECRET_KEY', value: '' }, + { variable: 'API_KEY', value: '' }, + { variable: 'NON_SENSITIVE', value: 'value' }, + ]); + }); + + it('should not change non-sensitive environment variables', () => { + const envs = [{ variable: 'NON_SENSITIVE', value: 'value' }]; + const result = sanitizeSensitiveEnv(envs); + + expect(result).toEqual([{ variable: 'NON_SENSITIVE', value: 'value' }]); + }); + + it('should handle an empty array', () => { + const envs: any[] = []; + + const result = sanitizeSensitiveEnv(envs); + + expect(result).toEqual([]); + }); +}); diff --git a/react/src/components/EnvVarFormList.tsx b/react/src/components/EnvVarFormList.tsx index e651a9a54f..0286a3f8ce 100644 --- a/react/src/components/EnvVarFormList.tsx +++ b/react/src/components/EnvVarFormList.tsx @@ -137,4 +137,40 @@ const EnvVarFormList: React.FC = ({ ); }; +const sensitivePatterns = [ + /AUTH/i, + /ACCESS/i, + /SECRET/i, + /_KEY/i, + /PASSWORD/i, + /PASSWD/i, + /PWD/i, + /TOKEN/i, + /PRIVATE/i, + /CREDENTIAL/i, + /JWT/i, + /KEYPAIR/i, + /CERTIFICATE/i, + /SSH/i, + /ENCRYPT/i, + /SIGNATURE/i, + /SALT/i, + /PIN/i, + /PASSPHRASE/i, + /OAUTH/i, +]; + +export function isSensitiveEnv(key: string) { + return sensitivePatterns.some((pattern) => pattern.test(key)); +} + +export function sanitizeSensitiveEnv(envs: EnvVarFormListValue[]) { + return envs.map((env) => { + if (env && isSensitiveEnv(env.variable)) { + return { ...env, value: '' }; + } + return env; + }); +} + export default EnvVarFormList; diff --git a/react/src/components/VFolderTableFormItem.tsx b/react/src/components/VFolderTableFormItem.tsx index 488bb169cf..56cb3060ff 100644 --- a/react/src/components/VFolderTableFormItem.tsx +++ b/react/src/components/VFolderTableFormItem.tsx @@ -19,6 +19,7 @@ interface VFolderTableFormItemProps extends Omit { export interface VFolderTableFormValues { mounts: string[]; vfoldersAliasMap: AliasMap; + autoMountedFolderNames?: string[]; } const VFolderTableFormItem: React.FC = ({ diff --git a/react/src/pages/SessionLauncherPage.tsx b/react/src/pages/SessionLauncherPage.tsx index 16fb23b1c2..f6e1385be1 100644 --- a/react/src/pages/SessionLauncherPage.tsx +++ b/react/src/pages/SessionLauncherPage.tsx @@ -3,6 +3,7 @@ import BAIIntervalText from '../components/BAIIntervalText'; import DatePickerISO from '../components/DatePickerISO'; import DoubleTag from '../components/DoubleTag'; import EnvVarFormList, { + sanitizeSensitiveEnv, EnvVarFormListValue, } from '../components/EnvVarFormList'; import Flex from '../components/Flex'; @@ -236,15 +237,22 @@ const SessionLauncherPage = () => { // console.log('syncFormToURLWithDebounce', form.getFieldsValue()); // To sync the latest form values to URL, // 'trailing' is set to true, and get the form values here." + const currentValue = form.getFieldsValue(); setQuery( { // formValues: form.getFieldsValue(), - formValues: _.omit( - form.getFieldsValue(), - ['environments.image'], - ['environments.customizedTag'], - ['autoMountedFolderNames'], - ['owner'], + formValues: _.extend( + _.omit( + form.getFieldsValue(), + ['environments.image'], + ['environments.customizedTag'], + ['autoMountedFolderNames'], + ['owner'], + ['envvars'], + ), + { + envvars: sanitizeSensitiveEnv(currentValue.envvars), + }, ), }, 'replaceIn',