Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow env access in Flows Run Script operation #16111

Merged
merged 12 commits into from
Jan 4, 2023
2 changes: 2 additions & 0 deletions api/src/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ const allowedEnvironmentVars = [
'EXPORT_BATCH_SIZE',
// flows
'FLOWS_EXEC_ALLOWED_MODULES',
'FLOWS_EXEC_ALLOWED_ENV',
licitdev marked this conversation as resolved.
Show resolved Hide resolved
].map((name) => new RegExp(`^${name}$`));

const acceptedEnvTypes = ['string', 'number', 'regex', 'array', 'json'];
Expand Down Expand Up @@ -264,6 +265,7 @@ const defaults: Record<string, any> = {
GRAPHQL_INTROSPECTION: true,

FLOWS_EXEC_ALLOWED_MODULES: false,
FLOWS_EXEC_ALLOWED_ENV: false,
};

// Allows us to force certain environment variable into a type, instead of relying
Expand Down
4 changes: 3 additions & 1 deletion api/src/flows.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
import { applyOptionsData, toArray } from '@directus/shared/utils';
import fastRedact from 'fast-redact';
import { Knex } from 'knex';
import { omit } from 'lodash';
import { omit, pick } from 'lodash';
import { get } from 'micromustache';
import { schedule, validate } from 'node-cron';
import getDatabase from './database';
Expand Down Expand Up @@ -56,6 +56,7 @@ type TriggerHandler = {
const TRIGGER_KEY = '$trigger';
const ACCOUNTABILITY_KEY = '$accountability';
const LAST_KEY = '$last';
const ENV_KEY = '$env';

class FlowManager {
private isLoaded = false;
Expand Down Expand Up @@ -296,6 +297,7 @@ class FlowManager {
[TRIGGER_KEY]: data,
[LAST_KEY]: data,
[ACCOUNTABILITY_KEY]: context?.accountability ?? null,
[ENV_KEY]: pick(env, env.FLOWS_EXEC_ALLOWED_ENV ? toArray(env.FLOWS_EXEC_ALLOWED_ENV) : []),
};

let nextOperation = flow.operation;
Expand Down
3 changes: 3 additions & 0 deletions api/src/operations/exec/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { defineOperationApi, toArray } from '@directus/shared/utils';
import { pick } from 'lodash';
import { NodeVM, NodeVMOptions, VMScript } from 'vm2';

type Options = {
Expand All @@ -9,10 +10,12 @@ export default defineOperationApi<Options>({
id: 'exec',
handler: async ({ code }, { data, env }) => {
const allowedModules = env.FLOWS_EXEC_ALLOWED_MODULES ? toArray(env.FLOWS_EXEC_ALLOWED_MODULES) : [];
const allowedEnv = pick(env, env.FLOWS_EXEC_ALLOWED_ENV ? toArray(env.FLOWS_EXEC_ALLOWED_ENV) : []);

const opts: NodeVMOptions = {
eval: false,
wasm: false,
env: allowedEnv,
};

if (allowedModules.length > 0) {
Expand Down
1 change: 1 addition & 0 deletions api/src/services/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ export class ServerService {

info.flows = {
execAllowedModules: env.FLOWS_EXEC_ALLOWED_MODULES ? toArray(env.FLOWS_EXEC_ALLOWED_MODULES) : [],
execAllowedEnv: env.FLOWS_EXEC_ALLOWED_ENV ? toArray(env.FLOWS_EXEC_ALLOWED_ENV) : [],
};
}

Expand Down
1 change: 1 addition & 0 deletions app/src/lang/translations/en-US.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2099,6 +2099,7 @@ operations:
name: Run Script
description: Execute arbitrary code to modify the payload
modules: 'The following **Node modules** can be used:'
env: 'The following **Environment Variables** can be used:'
item-create:
name: Create Data
description: Create items in the database
Expand Down
25 changes: 24 additions & 1 deletion app/src/operations/exec/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,34 @@ describe('Options', () => {
expect(config.options()).toHaveLength(1);
});

it('Shows notice when no modules are allowed', () => {
it('Shows notice when modules are allowed', () => {
const serverStore = useServerStore();

serverStore.info.flows = {
execAllowedModules: ['nanoid'],
execAllowedEnv: [],
};

expect(config.options()).toHaveLength(2);
});

it('Shows notice when env is allowed', () => {
const serverStore = useServerStore();

serverStore.info.flows = {
execAllowedModules: [],
execAllowedEnv: ['PUBLIC_URL'],
};

expect(config.options()).toHaveLength(2);
});

it('Shows notice when modules and env are allowed', () => {
const serverStore = useServerStore();

serverStore.info.flows = {
execAllowedModules: ['nanoid'],
execAllowedEnv: ['PUBLIC_URL'],
};

expect(config.options()).toHaveLength(2);
Expand Down
20 changes: 16 additions & 4 deletions app/src/operations/exec/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,20 +35,32 @@ export default defineOperationApp({
},
];

let notice = '';

if (serverStore.info?.flows?.execAllowedModules && serverStore.info.flows.execAllowedModules.length > 0) {
notice +=
t('operations.exec.modules') +
`<br>${serverStore.info.flows.execAllowedModules.map((mod) => `\`${mod}\``).join(', ')}`;
}

if (serverStore.info?.flows?.execAllowedEnv && serverStore.info.flows.execAllowedEnv.length > 0) {
if (notice) notice += '<br><br>';
notice +=
t('operations.exec.env') + `<br>${serverStore.info.flows.execAllowedEnv.map((env) => `\`${env}\``).join(', ')}`;
}

if (notice) {
return [
...standard,
{
field: 'notice',
name: '$t:modules',
name: '$t:interfaces.presentation-notice.notice',
type: 'alias',
meta: {
width: 'full',
interface: 'presentation-notice',
options: {
text:
t('operations.exec.modules') +
`<br>${serverStore.info.flows.execAllowedModules.map((mod) => `\`${mod}\``).join(', ')}`,
text: notice,
},
},
},
Expand Down
1 change: 1 addition & 0 deletions app/src/stores/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ type Info = {
};
flows?: {
execAllowedModules: string[];
execAllowedEnv: string[];
};
};

Expand Down