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

feat(core): Read ephemeral license from environment and clean up ee flags #5808

Merged
merged 9 commits into from
Mar 31, 2023
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