mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-16 09:36:44 +00:00
feat: Add N8N_GIT_NODE_DISABLE_BARE_REPOS environment variable to allow users to disable bare repositories in Git Node (#19559)
This commit is contained in:
@@ -45,4 +45,10 @@ export class SecurityConfig {
|
|||||||
*/
|
*/
|
||||||
@Env('N8N_INSECURE_DISABLE_WEBHOOK_IFRAME_SANDBOX')
|
@Env('N8N_INSECURE_DISABLE_WEBHOOK_IFRAME_SANDBOX')
|
||||||
disableWebhookHtmlSandboxing: boolean = false;
|
disableWebhookHtmlSandboxing: boolean = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether to disable bare repositories support in the Git node.
|
||||||
|
*/
|
||||||
|
@Env('N8N_GIT_NODE_DISABLE_BARE_REPOS')
|
||||||
|
disableBareRepos: boolean = false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -307,6 +307,7 @@ describe('GlobalConfig', () => {
|
|||||||
contentSecurityPolicy: '{}',
|
contentSecurityPolicy: '{}',
|
||||||
contentSecurityPolicyReportOnly: false,
|
contentSecurityPolicyReportOnly: false,
|
||||||
disableWebhookHtmlSandboxing: false,
|
disableWebhookHtmlSandboxing: false,
|
||||||
|
disableBareRepos: false,
|
||||||
},
|
},
|
||||||
executions: {
|
executions: {
|
||||||
timeout: -1,
|
timeout: -1,
|
||||||
|
|||||||
@@ -2,6 +2,16 @@
|
|||||||
|
|
||||||
This list shows all the versions which include breaking changes and how to upgrade.
|
This list shows all the versions which include breaking changes and how to upgrade.
|
||||||
|
|
||||||
|
# 1.113.0
|
||||||
|
|
||||||
|
### What changed?
|
||||||
|
|
||||||
|
Support for bare repositories in Git Node was dropped in the cloud version of n8n due to security reasons. Also, an environment variable `N8N_GIT_NODE_DISABLE_BARE_REPOS` was added that allows self-hosted users to disable bare repositories as well.
|
||||||
|
|
||||||
|
### When is action necessary?
|
||||||
|
|
||||||
|
If you have workflows that use the Git Node and work with bare git repositories.
|
||||||
|
|
||||||
# 1.109.0
|
# 1.109.0
|
||||||
|
|
||||||
### What changed?
|
### What changed?
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ describe('DeprecationService', () => {
|
|||||||
// this test suite.
|
// this test suite.
|
||||||
process.env = {
|
process.env = {
|
||||||
N8N_BLOCK_ENV_ACCESS_IN_NODE: 'false',
|
N8N_BLOCK_ENV_ACCESS_IN_NODE: 'false',
|
||||||
|
N8N_GIT_NODE_DISABLE_BARE_REPOS: 'false',
|
||||||
};
|
};
|
||||||
|
|
||||||
jest.resetAllMocks();
|
jest.resetAllMocks();
|
||||||
@@ -140,6 +141,7 @@ describe('DeprecationService', () => {
|
|||||||
process.env = {
|
process.env = {
|
||||||
N8N_RUNNERS_ENABLED: 'true',
|
N8N_RUNNERS_ENABLED: 'true',
|
||||||
N8N_BLOCK_ENV_ACCESS_IN_NODE: 'false',
|
N8N_BLOCK_ENV_ACCESS_IN_NODE: 'false',
|
||||||
|
N8N_GIT_NODE_DISABLE_BARE_REPOS: 'false',
|
||||||
};
|
};
|
||||||
|
|
||||||
jest.spyOn(config, 'getEnv').mockImplementation((key) => {
|
jest.spyOn(config, 'getEnv').mockImplementation((key) => {
|
||||||
@@ -239,6 +241,7 @@ describe('DeprecationService', () => {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
process.env = {
|
process.env = {
|
||||||
N8N_RUNNERS_ENABLED: 'true',
|
N8N_RUNNERS_ENABLED: 'true',
|
||||||
|
N8N_GIT_NODE_DISABLE_BARE_REPOS: 'false',
|
||||||
};
|
};
|
||||||
|
|
||||||
jest.resetAllMocks();
|
jest.resetAllMocks();
|
||||||
@@ -259,4 +262,29 @@ describe('DeprecationService', () => {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('N8N_GIT_NODE_DISABLE_BARE_REPOS', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
process.env = {
|
||||||
|
N8N_RUNNERS_ENABLED: 'true',
|
||||||
|
N8N_BLOCK_ENV_ACCESS_IN_NODE: 'false',
|
||||||
|
};
|
||||||
|
jest.resetAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should warn when N8N_GIT_NODE_DISABLE_BARE_REPOS is not set', () => {
|
||||||
|
delete process.env.N8N_GIT_NODE_DISABLE_BARE_REPOS;
|
||||||
|
deprecationService.warn();
|
||||||
|
expect(logger.warn).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
test.each(['false', 'true'])(
|
||||||
|
'should not warn when N8N_GIT_NODE_DISABLE_BARE_REPOS is %s',
|
||||||
|
(value) => {
|
||||||
|
process.env.N8N_GIT_NODE_DISABLE_BARE_REPOS = value;
|
||||||
|
deprecationService.warn();
|
||||||
|
expect(logger.warn).not.toHaveBeenCalled();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -109,6 +109,12 @@ export class DeprecationService {
|
|||||||
'The default value of N8N_BLOCK_ENV_ACCESS_IN_NODE will be changed from false to true in a future version. If you need to access environment variables from the Code Node or from expressions, please set N8N_BLOCK_ENV_ACCESS_IN_NODE=false. Learn more: https://docs.n8n.io/hosting/configuration/environment-variables/security/',
|
'The default value of N8N_BLOCK_ENV_ACCESS_IN_NODE will be changed from false to true in a future version. If you need to access environment variables from the Code Node or from expressions, please set N8N_BLOCK_ENV_ACCESS_IN_NODE=false. Learn more: https://docs.n8n.io/hosting/configuration/environment-variables/security/',
|
||||||
checkValue: (value: string | undefined) => value === undefined || value === '',
|
checkValue: (value: string | undefined) => value === undefined || value === '',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
envVar: 'N8N_GIT_NODE_DISABLE_BARE_REPOS',
|
||||||
|
message:
|
||||||
|
'Support for bare repositories in the Git Node will be removed in a future version due to security concerns. If you are not using bare repositories in the Git Node, please set N8N_GIT_NODE_DISABLE_BARE_REPOS=true. Learn more: https://docs.n8n.io/hosting/configuration/environment-variables/security/',
|
||||||
|
checkValue: (value: string | undefined) => value === undefined || value === '',
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
/** Runtime state of deprecation-related env vars. */
|
/** Runtime state of deprecation-related env vars. */
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ import {
|
|||||||
switchBranchFields,
|
switchBranchFields,
|
||||||
tagFields,
|
tagFields,
|
||||||
} from './descriptions';
|
} from './descriptions';
|
||||||
|
import { Container } from '@n8n/di';
|
||||||
|
import { DeploymentConfig, SecurityConfig } from '@n8n/config';
|
||||||
|
|
||||||
export class Git implements INodeType {
|
export class Git implements INodeType {
|
||||||
description: INodeTypeDescription = {
|
description: INodeTypeDescription = {
|
||||||
@@ -291,8 +293,18 @@ export class Git implements INodeType {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const gitConfig: string[] = [];
|
||||||
|
const deploymentConfig = Container.get(DeploymentConfig);
|
||||||
|
const isCloud = deploymentConfig.type === 'cloud';
|
||||||
|
const securityConfig = Container.get(SecurityConfig);
|
||||||
|
const disableBareRepos = securityConfig.disableBareRepos;
|
||||||
|
if (isCloud || disableBareRepos) {
|
||||||
|
gitConfig.push('safe.bareRepository=explicit');
|
||||||
|
}
|
||||||
|
|
||||||
const gitOptions: Partial<SimpleGitOptions> = {
|
const gitOptions: Partial<SimpleGitOptions> = {
|
||||||
baseDir: repositoryPath,
|
baseDir: repositoryPath,
|
||||||
|
config: gitConfig,
|
||||||
};
|
};
|
||||||
|
|
||||||
const git: SimpleGit = simpleGit(gitOptions)
|
const git: SimpleGit = simpleGit(gitOptions)
|
||||||
|
|||||||
117
packages/nodes-base/nodes/Git/__test__/Git.node.test.ts
Normal file
117
packages/nodes-base/nodes/Git/__test__/Git.node.test.ts
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
import { DeploymentConfig, SecurityConfig } from '@n8n/config';
|
||||||
|
import { Container } from '@n8n/di';
|
||||||
|
import { mock } from 'jest-mock-extended';
|
||||||
|
import type { IExecuteFunctions } from 'n8n-workflow';
|
||||||
|
import type { SimpleGit } from 'simple-git';
|
||||||
|
import simpleGit from 'simple-git';
|
||||||
|
|
||||||
|
import { Git } from '../Git.node';
|
||||||
|
|
||||||
|
const mockGit = {
|
||||||
|
log: jest.fn(),
|
||||||
|
env: jest.fn().mockReturnThis(),
|
||||||
|
};
|
||||||
|
|
||||||
|
jest.mock('simple-git');
|
||||||
|
const mockSimpleGit = simpleGit as jest.MockedFunction<typeof simpleGit>;
|
||||||
|
mockSimpleGit.mockReturnValue(mockGit as unknown as SimpleGit);
|
||||||
|
|
||||||
|
describe('Git Node', () => {
|
||||||
|
let gitNode: Git;
|
||||||
|
let executeFunctions: jest.Mocked<IExecuteFunctions>;
|
||||||
|
let deploymentConfig: jest.Mocked<DeploymentConfig>;
|
||||||
|
let securityConfig: jest.Mocked<SecurityConfig>;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
|
||||||
|
deploymentConfig = mock<DeploymentConfig>({
|
||||||
|
type: 'default',
|
||||||
|
});
|
||||||
|
securityConfig = mock<SecurityConfig>({
|
||||||
|
disableBareRepos: false,
|
||||||
|
});
|
||||||
|
Container.set(DeploymentConfig, deploymentConfig);
|
||||||
|
Container.set(SecurityConfig, securityConfig);
|
||||||
|
|
||||||
|
executeFunctions = mock<IExecuteFunctions>({
|
||||||
|
getInputData: jest.fn().mockReturnValue([{ json: {} }]),
|
||||||
|
getNodeParameter: jest.fn(),
|
||||||
|
helpers: {
|
||||||
|
returnJsonArray: jest
|
||||||
|
.fn()
|
||||||
|
.mockImplementation((data: unknown[]) => data.map((item: unknown) => ({ json: item }))),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
executeFunctions.getNodeParameter.mockImplementation((name: string) => {
|
||||||
|
switch (name) {
|
||||||
|
case 'operation':
|
||||||
|
return 'log';
|
||||||
|
case 'repositoryPath':
|
||||||
|
return '/tmp/test-repo';
|
||||||
|
case 'options':
|
||||||
|
return {};
|
||||||
|
default:
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
mockGit.log.mockResolvedValue({ all: [] });
|
||||||
|
|
||||||
|
gitNode = new Git();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Bare Repository Configuration', () => {
|
||||||
|
it('should add safe.bareRepository=explicit when deployment type is cloud', async () => {
|
||||||
|
deploymentConfig.type = 'cloud';
|
||||||
|
securityConfig.disableBareRepos = false;
|
||||||
|
|
||||||
|
await gitNode.execute.call(executeFunctions);
|
||||||
|
|
||||||
|
expect(mockSimpleGit).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
config: ['safe.bareRepository=explicit'],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should add safe.bareRepository=explicit when disableBareRepos is true', async () => {
|
||||||
|
deploymentConfig.type = 'default';
|
||||||
|
securityConfig.disableBareRepos = true;
|
||||||
|
|
||||||
|
await gitNode.execute.call(executeFunctions);
|
||||||
|
|
||||||
|
expect(mockSimpleGit).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
config: ['safe.bareRepository=explicit'],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should add safe.bareRepository=explicit when both cloud and disableBareRepos are true', async () => {
|
||||||
|
deploymentConfig.type = 'cloud';
|
||||||
|
securityConfig.disableBareRepos = true;
|
||||||
|
|
||||||
|
await gitNode.execute.call(executeFunctions);
|
||||||
|
|
||||||
|
expect(mockSimpleGit).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
config: ['safe.bareRepository=explicit'],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not add safe.bareRepository=explicit when neither cloud nor disableBareRepos is true', async () => {
|
||||||
|
deploymentConfig.type = 'default';
|
||||||
|
securityConfig.disableBareRepos = false;
|
||||||
|
|
||||||
|
await gitNode.execute.call(executeFunctions);
|
||||||
|
|
||||||
|
expect(mockSimpleGit).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
config: [],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user