Skip to content

Commit

Permalink
feat(core): Read ephemeral license from environment and clean up ee f…
Browse files Browse the repository at this point in the history
…lags (#5808)

Co-authored-by: Cornelius Suermann <[email protected]>
  • Loading branch information
flipswitchingmonkey and csuermann authored Mar 31, 2023
1 parent 3ae6933 commit 83aef17
Show file tree
Hide file tree
Showing 19 changed files with 75 additions and 92 deletions.
2 changes: 1 addition & 1 deletion packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@
"tsconfig-paths": "^4.1.2"
},
"dependencies": {
"@n8n_io/license-sdk": "~1.8.0",
"@n8n_io/license-sdk": "~2.0.0",
"@oclif/command": "^1.8.16",
"@oclif/core": "^1.16.4",
"@oclif/errors": "^1.3.6",
Expand Down
2 changes: 0 additions & 2 deletions packages/cli/src/Ldap/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ import type { LdapConfig } from './types';

export const LDAP_FEATURE_NAME = 'features.ldap';

export const LDAP_ENABLED = 'enterprise.features.ldap';

export const LDAP_LOGIN_LABEL = 'sso.ldap.loginLabel';

export const LDAP_LOGIN_ENABLED = 'sso.ldap.loginEnabled';
Expand Down
3 changes: 1 addition & 2 deletions packages/cli/src/Ldap/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import { LdapManager } from './LdapManager.ee';
import {
BINARY_AD_ATTRIBUTES,
LDAP_CONFIG_SCHEMA,
LDAP_ENABLED,
LDAP_FEATURE_NAME,
LDAP_LOGIN_ENABLED,
LDAP_LOGIN_LABEL,
Expand All @@ -37,7 +36,7 @@ import {
*/
export const isLdapEnabled = (): boolean => {
const license = Container.get(License);
return isUserManagementEnabled() && (config.getEnv(LDAP_ENABLED) || license.isLdapEnabled());
return isUserManagementEnabled() && license.isLdapEnabled();
};

/**
Expand Down
13 changes: 10 additions & 3 deletions packages/cli/src/License.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { TEntitlement, TLicenseContainerStr } from '@n8n_io/license-sdk';
import type { TEntitlement, TLicenseBlock } from '@n8n_io/license-sdk';
import { LicenseManager } from '@n8n_io/license-sdk';
import type { ILogger } from 'n8n-workflow';
import { getLogger } from './Logger';
Expand All @@ -7,7 +7,12 @@ import * as Db from '@/Db';
import { LICENSE_FEATURES, N8N_VERSION, SETTINGS_LICENSE_CERT_KEY } from './constants';
import { Service } from 'typedi';

async function loadCertStr(): Promise<TLicenseContainerStr> {
async function loadCertStr(): Promise<TLicenseBlock> {
// if we have an ephemeral license, we don't want to load it from the database
const ephemeralLicense = config.get('license.cert');
if (ephemeralLicense) {
return ephemeralLicense;
}
const databaseSettings = await Db.collections.Settings.findOne({
where: {
key: SETTINGS_LICENSE_CERT_KEY,
Expand All @@ -17,7 +22,9 @@ async function loadCertStr(): Promise<TLicenseContainerStr> {
return databaseSettings?.value ?? '';
}

async function saveCertStr(value: TLicenseContainerStr): Promise<void> {
async function saveCertStr(value: TLicenseBlock): Promise<void> {
// if we have an ephemeral license, we don't want to save it to the database
if (config.get('license.cert')) return;
await Db.collections.Settings.upsert(
{
key: SETTINGS_LICENSE_CERT_KEY,
Expand Down
4 changes: 2 additions & 2 deletions packages/cli/src/Server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -311,8 +311,8 @@ class Server extends AbstractServer {
sharing: false,
ldap: false,
saml: false,
logStreaming: config.getEnv('enterprise.features.logStreaming'),
advancedExecutionFilters: config.getEnv('enterprise.features.advancedExecutionFilters'),
logStreaming: false,
advancedExecutionFilters: false,
},
hideUsagePage: config.getEnv('hideUsagePage'),
license: {
Expand Down
5 changes: 1 addition & 4 deletions packages/cli/src/UserManagement/UserManagementHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,7 @@ export function isUserManagementEnabled(): boolean {

export function isSharingEnabled(): boolean {
const license = Container.get(License);
return (
isUserManagementEnabled() &&
(config.getEnv('enterprise.features.sharing') || license.isSharingEnabled())
);
return isUserManagementEnabled() && license.isSharingEnabled();
}

export async function getRoleId(scope: Role['scope'], name: Role['name']): Promise<Role['id']> {
Expand Down
22 changes: 19 additions & 3 deletions packages/cli/src/api/e2e.api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,34 @@
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/naming-convention */
import { Router } from 'express';
import type { Request } from 'express';
import bodyParser from 'body-parser';
import { v4 as uuid } from 'uuid';
import config from '@/config';
import * as Db from '@/Db';
import type { Role } from '@db/entities/Role';
import { hashPassword } from '@/UserManagement/UserManagementHelper';
import { eventBus } from '@/eventbus/MessageEventBus/MessageEventBus';
import Container from 'typedi';
import { License } from '../License';

if (process.env.E2E_TESTS !== 'true') {
console.error('E2E endpoints only allowed during E2E tests');
process.exit(1);
}

const enabledFeatures = {
sharing: true, //default to true here instead of setting it in config/index.ts for e2e
ldap: false,
saml: false,
logStreaming: false,
advancedExecutionFilters: false,
};

type Feature = keyof typeof enabledFeatures;

Container.get(License).isFeatureEnabled = (feature: Feature) => enabledFeatures[feature] ?? false;

const tablesToTruncate = [
'auth_identity',
'auth_provider_sync_history',
Expand Down Expand Up @@ -78,7 +93,7 @@ const setupUserManagement = async () => {
};

const resetLogStreaming = async () => {
config.set('enterprise.features.logStreaming', false);
enabledFeatures.logStreaming = false;
for (const id in eventBus.destinations) {
await eventBus.removeDestination(id);
}
Expand Down Expand Up @@ -127,7 +142,8 @@ e2eController.post('/db/setup-owner', bodyParser.json(), async (req, res) => {
res.writeHead(204).end();
});

e2eController.post('/enable-feature/:feature', async (req, res) => {
config.set(`enterprise.features.${req.params.feature}`, true);
e2eController.post('/enable-feature/:feature', async (req: Request<{ feature: Feature }>, res) => {
const { feature } = req.params;
enabledFeatures[feature] = true;
res.writeHead(204).end();
});
4 changes: 0 additions & 4 deletions packages/cli/src/config/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,6 @@ if (inE2ETests) {

const config = convict(schema, { args: [] });

if (inE2ETests) {
config.set('enterprise.features.sharing', true);
}

// eslint-disable-next-line @typescript-eslint/unbound-method
config.getEnv = config.get;

Expand Down
31 changes: 6 additions & 25 deletions packages/cli/src/config/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -990,31 +990,6 @@ export const schema = {
},
},

enterprise: {
features: {
sharing: {
format: Boolean,
default: false,
},
ldap: {
format: Boolean,
default: false,
},
saml: {
format: Boolean,
default: false,
},
logStreaming: {
format: Boolean,
default: false,
},
advancedExecutionFilters: {
format: Boolean,
default: false,
},
},
},

sso: {
justInTimeProvisioning: {
format: Boolean,
Expand Down Expand Up @@ -1166,6 +1141,12 @@ export const schema = {
env: 'N8N_LICENSE_TENANT_ID',
doc: 'Tenant id used by the license manager',
},
cert: {
format: String,
default: '',
env: 'N8N_LICENSE_CERT',
doc: 'Ephemeral license certificate',
},
},

hideUsagePage: {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import config from '@/config';
import { License } from '@/License';
import { Container } from 'typedi';

export function isLogStreamingEnabled(): boolean {
const license = Container.get(License);
return config.getEnv('enterprise.features.logStreaming') || license.isLogStreamingEnabled();
return license.isLogStreamingEnabled();
}
6 changes: 1 addition & 5 deletions packages/cli/src/executions/executionHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { Container } from 'typedi';
import type { IExecutionFlattedDb } from '@/Interfaces';
import type { ExecutionStatus } from 'n8n-workflow';
import { License } from '@/License';
import config from '@/config';

export function getStatusUsingPreviousExecutionStatusMethod(
execution: IExecutionFlattedDb,
Expand All @@ -22,8 +21,5 @@ export function getStatusUsingPreviousExecutionStatusMethod(

export function isAdvancedExecutionFiltersEnabled(): boolean {
const license = Container.get(License);
return (
config.getEnv('enterprise.features.advancedExecutionFilters') ||
license.isAdvancedExecutionFiltersEnabled()
);
return license.isAdvancedExecutionFiltersEnabled();
}
2 changes: 0 additions & 2 deletions packages/cli/src/sso/saml/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,6 @@ export class SamlUrls {

export const SAML_PREFERENCES_DB_KEY = 'features.saml';

export const SAML_ENTERPRISE_FEATURE_ENABLED = 'enterprise.features.saml';

export const SAML_LOGIN_LABEL = 'sso.saml.loginLabel';

export const SAML_LOGIN_ENABLED = 'sso.saml.loginEnabled';
7 changes: 2 additions & 5 deletions packages/cli/src/sso/saml/samlHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import type { SamlPreferences } from './types/samlPreferences';
import type { SamlUserAttributes } from './types/samlUserAttributes';
import type { FlowResult } from 'samlify/types/src/flow';
import type { SamlAttributeMapping } from './types/samlAttributeMapping';
import { SAML_ENTERPRISE_FEATURE_ENABLED, SAML_LOGIN_ENABLED, SAML_LOGIN_LABEL } from './constants';
import { SAML_LOGIN_ENABLED, SAML_LOGIN_LABEL } from './constants';
import {
isEmailCurrentAuthenticationMethod,
isSamlCurrentAuthenticationMethod,
Expand Down Expand Up @@ -52,10 +52,7 @@ export function setSamlLoginLabel(label: string): void {

export function isSamlLicensed(): boolean {
const license = Container.get(License);
return (
isUserManagementEnabled() &&
(license.isSamlEnabled() || config.getEnv(SAML_ENTERPRISE_FEATURE_ENABLED))
);
return isUserManagementEnabled() && license.isSamlEnabled();
}

export function isSamlLicensedAndEnabled(): boolean {
Expand Down
10 changes: 4 additions & 6 deletions packages/cli/test/integration/eventbus.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import config from '@/config';
import axios from 'axios';
import syslog from 'syslog-client';
import { v4 as uuid } from 'uuid';
import Container from 'typedi';
import type { SuperAgentTest } from 'supertest';
import * as utils from './shared/utils';
import * as testDb from './shared/testDb';
Expand All @@ -23,6 +24,7 @@ import { MessageEventBusDestinationWebhook } from '@/eventbus/MessageEventBusDes
import { MessageEventBusDestinationSentry } from '@/eventbus/MessageEventBusDestination/MessageEventBusDestinationSentry.ee';
import { EventMessageAudit } from '@/eventbus/EventMessageClasses/EventMessageAudit';
import { EventNamesTypes } from '@/eventbus/EventMessageClasses';
import { License } from '@/License';

jest.unmock('@/eventbus/MessageEventBus/MessageEventBus');
jest.mock('axios');
Expand Down Expand Up @@ -77,6 +79,7 @@ async function confirmIdSent(id: string) {
}

beforeAll(async () => {
Container.get(License).isLogStreamingEnabled = () => true;
app = await utils.initTestServer({ endpointGroups: ['eventBus'] });

globalOwnerRole = await testDb.getGlobalOwnerRole();
Expand All @@ -101,7 +104,6 @@ beforeAll(async () => {
utils.initConfigFile();
config.set('eventBus.logWriter.logBaseName', 'n8n-test-logwriter');
config.set('eventBus.logWriter.keepLogCount', 1);
config.set('enterprise.features.logStreaming', true);
config.set('userManagement.disabled', false);
config.set('userManagement.isInstanceOwnerSetUp', true);

Expand All @@ -110,6 +112,7 @@ beforeAll(async () => {

afterAll(async () => {
jest.mock('@/eventbus/MessageEventBus/MessageEventBus');
Container.reset();
await testDb.terminate();
await eventBus.close();
});
Expand Down Expand Up @@ -178,7 +181,6 @@ test.skip('should send message to syslog', async () => {
eventName: 'n8n.test.message' as EventNamesTypes,
id: uuid(),
});
config.set('enterprise.features.logStreaming', true);

const syslogDestination = eventBus.destinations[
testSyslogDestination.id!
Expand Down Expand Up @@ -219,7 +221,6 @@ test.skip('should confirm send message if there are no subscribers', async () =>
eventName: 'n8n.test.unsub' as EventNamesTypes,
id: uuid(),
});
config.set('enterprise.features.logStreaming', true);

const syslogDestination = eventBus.destinations[
testSyslogDestination.id!
Expand Down Expand Up @@ -255,7 +256,6 @@ test('should anonymize audit message to syslog ', async () => {
},
id: uuid(),
});
config.set('enterprise.features.logStreaming', true);

const syslogDestination = eventBus.destinations[
testSyslogDestination.id!
Expand Down Expand Up @@ -317,7 +317,6 @@ test('should send message to webhook ', async () => {
eventName: 'n8n.test.message' as EventNamesTypes,
id: uuid(),
});
config.set('enterprise.features.logStreaming', true);

const webhookDestination = eventBus.destinations[
testWebhookDestination.id!
Expand Down Expand Up @@ -352,7 +351,6 @@ test('should send message to sentry ', async () => {
eventName: 'n8n.test.message' as EventNamesTypes,
id: uuid(),
});
config.set('enterprise.features.logStreaming', true);

const sentryDestination = eventBus.destinations[
testSentryDestination.id!
Expand Down
7 changes: 5 additions & 2 deletions packages/cli/test/integration/ldap/ldap.api.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import express from 'express';
import type { Entry as LdapUser } from 'ldapts';
import { Not } from 'typeorm';
import Container from 'typedi';
import { jsonParse } from 'n8n-workflow';
import config from '@/config';
import * as Db from '@/Db';
Expand All @@ -12,11 +13,12 @@ import { LdapService } from '@/Ldap/LdapService.ee';
import { encryptPassword, saveLdapSynchronization } from '@/Ldap/helpers';
import type { LdapConfig } from '@/Ldap/types';
import { sanitizeUser } from '@/UserManagement/UserManagementHelper';
import { getCurrentAuthenticationMethod, setCurrentAuthenticationMethod } from '@/sso/ssoHelpers';
import { License } from '@/License';
import { randomEmail, randomName, uniqueId } from './../shared/random';
import * as testDb from './../shared/testDb';
import type { AuthAgent } from '../shared/types';
import * as utils from '../shared/utils';
import { getCurrentAuthenticationMethod, setCurrentAuthenticationMethod } from '@/sso/ssoHelpers';

jest.mock('@/telemetry');
jest.mock('@/UserManagement/email/NodeMailer');
Expand All @@ -41,6 +43,7 @@ const defaultLdapConfig = {
};

beforeAll(async () => {
Container.get(License).isLdapEnabled = () => true;
app = await utils.initTestServer({ endpointGroups: ['auth', 'ldap'] });

const [globalOwnerRole, fetchedGlobalMemberRole] = await testDb.getAllRoles();
Expand Down Expand Up @@ -77,10 +80,10 @@ beforeEach(async () => {
config.set('userManagement.disabled', false);
config.set('userManagement.isInstanceOwnerSetUp', true);
config.set('userManagement.emails.mode', '');
config.set('enterprise.features.ldap', true);
});

afterAll(async () => {
Container.reset();
await testDb.terminate();
});

Expand Down
Loading

0 comments on commit 83aef17

Please sign in to comment.