[6.8] Backport/6.8/security basic (#35889)

* enable security plugin in basic

enable security on file dataviz and import (ML plugin)

update unit tests

add api test coverage for security in basic

move audit logging to standard+ license level

* removing test that wasn't added until 7.X (feature controls)
This commit is contained in:
Peter 2019-05-01 16:40:56 -07:00 committed by GitHub
parent 33b82252fa
commit 5b70dcabea
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
33 changed files with 399 additions and 283 deletions

View file

@ -26,11 +26,12 @@ import { setupUsers, DEFAULT_SUPERUSER_PASS } from './auth';
export async function runElasticsearch({ config, options }) {
const { log, esFrom } = options;
const license = config.get('esTestCluster.license');
const isTrialLicense = config.get('esTestCluster.license') === 'trial';
const esArgs = config.get('esTestCluster.serverArgs');
const isSecurityEnabled = esArgs.includes('xpack.security.enabled=true');
const cluster = createEsTestCluster({
port: config.get('servers.elasticsearch.port'),
password: isTrialLicense
password: isSecurityEnabled
? DEFAULT_SUPERUSER_PASS
: config.get('servers.elasticsearch.password'),
license,
@ -40,11 +41,9 @@ export async function runElasticsearch({ config, options }) {
dataArchive: config.get('esTestCluster.dataArchive'),
});
const esArgs = config.get('esTestCluster.serverArgs');
await cluster.start(esArgs);
if (isTrialLicense) {
if (isSecurityEnabled) {
await setupUsers(log, config.get('servers.elasticsearch.port'), [
config.get('servers.elasticsearch'),
config.get('servers.kibana'),

View file

@ -9,7 +9,7 @@
import expect from 'expect.js';
import sinon from 'sinon';
import { set } from 'lodash';
import { checkLicense, isBasicLicense } from '../check_license';
import { checkLicense } from '../check_license';
describe('check_license', () => {
@ -151,31 +151,3 @@ describe('check_license', () => {
});
});
});
function mockServerFactory(licenseType = 'basic') {
return {
plugins: {
xpack_main: {
info: {
license: {
getType: () => licenseType
}
}
}
}
};
}
describe('isBasicLicense', () => {
describe('license information is basic', () => {
it('should be true', () => {
expect(isBasicLicense(mockServerFactory())).to.be(true);
});
});
describe('license information is gold', () => {
it('should be true', () => {
expect(isBasicLicense(mockServerFactory('gold'))).to.be(false);
});
});
});

View file

@ -68,9 +68,3 @@ export function checkLicense(xpackLicenseInfo) {
hasExpired: false,
};
}
export function isBasicLicense(server) {
const xpackMainPlugin = server.plugins.xpack_main;
const xpackInfo = (xpackMainPlugin && xpackMainPlugin.info);
return (xpackInfo.license.getType() === 'basic');
}

View file

@ -6,4 +6,4 @@
export { checkLicense, isBasicLicense } from './check_license';
export { checkLicense } from './check_license';

View file

@ -14,7 +14,6 @@ import { polledDataCheckerFactory } from './polled_data_checker';
import { callWithInternalUserFactory } from '../../client/call_with_internal_user_factory';
import { isSecurityDisabled } from '../../lib/security_utils';
import { isBasicLicense } from '../../lib/check_license';
export function estimateBucketSpanFactory(callWithRequest, server) {
const callWithInternalUser = callWithInternalUserFactory(server);
@ -383,7 +382,7 @@ export function estimateBucketSpanFactory(callWithRequest, server) {
});
}
if (isBasicLicense(server) || isSecurityDisabled(server)) {
if (isSecurityDisabled(server)) {
getBucketSpanEstimation();
} else {
// if security is enabled, check that the user has permission to

View file

@ -14,7 +14,6 @@ import { wrapError } from '../client/errors';
import Boom from 'boom';
import { isSecurityDisabled } from '../lib/security_utils';
import { isBasicLicense } from '../lib/check_license';
export function systemRoutes(server, commonRouteConfig) {
const callWithInternalUser = callWithInternalUserFactory(server);
@ -61,11 +60,7 @@ export function systemRoutes(server, commonRouteConfig) {
}
}
// isSecurityDisabled will return true if it is a basic license
// this will cause the subsequent ml.privilegeCheck to fail.
// therefore, check for a basic license first and report that security
// is disabled because its not available on basic
if (isBasicLicense(server) || isSecurityDisabled(server)) {
if (isSecurityDisabled(server)) {
// if xpack.security.enabled has been explicitly set to false
// return that security is disabled and don't call the privilegeCheck endpoint
return {
@ -95,7 +90,7 @@ export function systemRoutes(server, commonRouteConfig) {
return new Promise((resolve, reject) => {
// check for basic license first for consistency with other
// security disabled checks
if (isBasicLicense(server) || isSecurityDisabled(server)) {
if (isSecurityDisabled(server)) {
getNodeCount()
.then(resolve)
.catch(reject);

View file

@ -124,7 +124,7 @@ export const security = (kibana) => new kibana.Plugin({
}
});
const auditLogger = new SecurityAuditLogger(server.config(), new AuditLogger(server, 'security'));
const auditLogger = new SecurityAuditLogger(new AuditLogger(server, 'security', server.config(), xpackInfo));
const { savedObjects } = server;
savedObjects.setScopedSavedObjectsClientFactory(({

View file

@ -34,8 +34,6 @@ describe('lib/auth_redirect', function () {
.isAvailable.returns(true);
server.plugins.xpack_main.info
.feature.returns({ isEnabled: sinon.stub().returns(true) });
server.plugins.xpack_main.info
.license.isOneOf.returns(false);
authenticate = authenticateFactory(server);
});
@ -128,12 +126,4 @@ describe('lib/auth_redirect', function () {
sinon.assert.notCalled(h.redirect);
});
it('replies with no credentials when license is basic', async () => {
server.plugins.xpack_main.info.license.isOneOf.returns(true);
await authenticate(request, h);
sinon.assert.calledWith(h.authenticated, { credentials: {} });
sinon.assert.notCalled(h.redirect);
});
});

View file

@ -58,21 +58,21 @@ describe('check_license', function () {
});
it('should not show login page or other security elements if license is basic.', () => {
it('should show login page and other security elements if license is basic and security is enabled.', () => {
mockXPackInfo.license.isOneOf.withArgs(['basic']).returns(true);
mockXPackInfo.license.isOneOf.withArgs(['platinum', 'trial']).returns(false);
mockXPackInfo.feature.withArgs('security').returns({
isEnabled: () => { return true; }
});
const licenseCheckResults = checkLicense(mockXPackInfo);
expect(licenseCheckResults).to.be.eql({
showLogin: false,
allowLogin: false,
showLinks: false,
showLogin: true,
allowLogin: true,
showLinks: true,
allowRoleDocumentLevelSecurity: false,
allowRoleFieldLevelSecurity: false,
allowRbac: false,
linksMessage: 'Your Basic license does not support Security. Please upgrade your license.'
allowRbac: true
});
});

View file

@ -5,16 +5,11 @@
*/
export class SecurityAuditLogger {
constructor(config, auditLogger) {
this._enabled = config.get('xpack.security.audit.enabled');
constructor(auditLogger) {
this._auditLogger = auditLogger;
}
savedObjectsAuthorizationFailure(username, action, types, missing, args) {
if (!this._enabled) {
return;
}
this._auditLogger.log(
'saved_objects_authorization_failure',
`${username} unauthorized to ${action} ${types.join(',')}, missing ${missing.join(',')}`,
@ -29,10 +24,6 @@ export class SecurityAuditLogger {
}
savedObjectsAuthorizationSuccess(username, action, types, args) {
if (!this._enabled) {
return;
}
this._auditLogger.log(
'saved_objects_authorization_success',
`${username} authorized to ${action} ${types.join(',')}`,

View file

@ -5,19 +5,6 @@
*/
import { SecurityAuditLogger } from './audit_logger';
const createMockConfig = (settings) => {
const mockConfig = {
get: jest.fn()
};
mockConfig.get.mockImplementation(key => {
return settings[key];
});
return mockConfig;
};
const createMockAuditLogger = () => {
return {
log: jest.fn()
@ -25,24 +12,10 @@ const createMockAuditLogger = () => {
};
describe(`#savedObjectsAuthorizationFailure`, () => {
test(`doesn't log anything when xpack.security.audit.enabled is false`, () => {
const config = createMockConfig({
'xpack.security.audit.enabled': false
});
test('logs via auditLogger', () => {
const auditLogger = createMockAuditLogger();
const securityAuditLogger = new SecurityAuditLogger(config, auditLogger);
securityAuditLogger.savedObjectsAuthorizationFailure();
expect(auditLogger.log).toHaveBeenCalledTimes(0);
});
test('logs via auditLogger when xpack.security.audit.enabled is true', () => {
const config = createMockConfig({
'xpack.security.audit.enabled': true
});
const auditLogger = createMockAuditLogger();
const securityAuditLogger = new SecurityAuditLogger(config, auditLogger);
const securityAuditLogger = new SecurityAuditLogger(auditLogger);
const username = 'foo-user';
const action = 'foo-action';
const types = [ 'foo-type-1', 'foo-type-2' ];
@ -69,24 +42,9 @@ describe(`#savedObjectsAuthorizationFailure`, () => {
});
describe(`#savedObjectsAuthorizationSuccess`, () => {
test(`doesn't log anything when xpack.security.audit.enabled is false`, () => {
const config = createMockConfig({
'xpack.security.audit.enabled': false
});
const auditLogger = createMockAuditLogger();
const securityAuditLogger = new SecurityAuditLogger(config, auditLogger);
securityAuditLogger.savedObjectsAuthorizationSuccess();
expect(auditLogger.log).toHaveBeenCalledTimes(0);
});
test('logs via auditLogger when xpack.security.audit.enabled is true', () => {
const config = createMockConfig({
'xpack.security.audit.enabled': true
});
const auditLogger = createMockAuditLogger();
const securityAuditLogger = new SecurityAuditLogger(config, auditLogger);
const securityAuditLogger = new SecurityAuditLogger(auditLogger);
const username = 'foo-user';
const action = 'foo-action';
const types = [ 'foo-type-1', 'foo-type-2' ];

View file

@ -16,11 +16,10 @@ import { wrapError } from './errors';
*/
export function authenticateFactory(server) {
return async function authenticate(request, h) {
// If security is disabled or license is basic, continue with no user credentials
// If security is disabled continue with no user credentials
// and delete the client cookie as well.
const xpackInfo = server.plugins.xpack_main.info;
if (xpackInfo.isAvailable()
&& (!xpackInfo.feature('security').isEnabled() || xpackInfo.license.isOneOf('basic'))) {
if (xpackInfo.isAvailable() && !xpackInfo.feature('security').isEnabled()) {
return h.authenticated({ credentials: {} });
}

View file

@ -37,9 +37,8 @@ export function checkLicense(xPackInfo) {
};
}
const isLicenseBasic = xPackInfo.license.isOneOf(['basic']);
const isEnabledInES = xPackInfo.feature('security').isEnabled();
if (!isEnabledInES || isLicenseBasic) {
if (!isEnabledInES) {
return {
showLogin: false,
allowLogin: false,
@ -47,11 +46,8 @@ export function checkLicense(xPackInfo) {
allowRoleDocumentLevelSecurity: false,
allowRoleFieldLevelSecurity: false,
allowRbac: false,
linksMessage: isLicenseBasic
? 'Your Basic license does not support Security. Please upgrade your license.'
: 'Access is denied because Security is disabled in Elasticsearch.'
linksMessage: 'Access is denied because Security is disabled in Elasticsearch.'
};
}
const isLicensePlatinumOrTrial = xPackInfo.license.isOneOf(['platinum', 'trial']);

View file

@ -110,7 +110,9 @@ export const spaces = (kibana: any) =>
const config = server.config();
const spacesAuditLogger = new SpacesAuditLogger(config, new AuditLogger(server, 'spaces'));
const spacesAuditLogger = new SpacesAuditLogger(
new AuditLogger(server, 'spaces', config, xpackMainPlugin.info)
);
server.expose('spacesClient', {
getScopedClient: (request: any) => {

View file

@ -5,22 +5,6 @@
*/
import { SpacesAuditLogger } from './audit_logger';
const createMockConfig = (settings: { [key: string]: any } = {}) => {
const mockConfig = {
get: jest.fn(),
};
mockConfig.get.mockImplementation(key => {
if (!settings.hasOwnProperty(key)) {
throw new Error('Undefined key, mock schema error');
}
return settings[key];
});
return mockConfig;
};
const createMockAuditLogger = () => {
return {
log: jest.fn(),
@ -28,38 +12,9 @@ const createMockAuditLogger = () => {
};
describe(`#savedObjectsAuthorizationFailure`, () => {
test(`doesn't log anything when xpack.security.enabled is false`, () => {
const config = createMockConfig({
'xpack.security.enabled': false,
});
test('logs auth failure with spaceIds via auditLogger', () => {
const auditLogger = createMockAuditLogger();
const securityAuditLogger = new SpacesAuditLogger(config, auditLogger);
securityAuditLogger.spacesAuthorizationFailure('foo-user', 'foo-action');
expect(auditLogger.log).toHaveBeenCalledTimes(0);
});
test(`doesn't log anything when xpack.security.audit.enabled is false`, () => {
const config = createMockConfig({
'xpack.security.enabled': true,
'xpack.security.audit.enabled': false,
});
const auditLogger = createMockAuditLogger();
const securityAuditLogger = new SpacesAuditLogger(config, auditLogger);
securityAuditLogger.spacesAuthorizationFailure('foo-user', 'foo-action');
expect(auditLogger.log).toHaveBeenCalledTimes(0);
});
test('logs with spaceIds via auditLogger when xpack.security.audit.enabled is true', () => {
const config = createMockConfig({
'xpack.security.enabled': true,
'xpack.security.audit.enabled': true,
});
const auditLogger = createMockAuditLogger();
const securityAuditLogger = new SpacesAuditLogger(config, auditLogger);
const securityAuditLogger = new SpacesAuditLogger(auditLogger);
const username = 'foo-user';
const action = 'foo-action';
const spaceIds = ['foo-space-1', 'foo-space-2'];
@ -77,13 +32,9 @@ describe(`#savedObjectsAuthorizationFailure`, () => {
);
});
test('logs without spaceIds via auditLogger when xpack.security.audit.enabled is true', () => {
const config = createMockConfig({
'xpack.security.enabled': true,
'xpack.security.audit.enabled': true,
});
test('logs auth failure without spaceIds via auditLogger', () => {
const auditLogger = createMockAuditLogger();
const securityAuditLogger = new SpacesAuditLogger(config, auditLogger);
const securityAuditLogger = new SpacesAuditLogger(auditLogger);
const username = 'foo-user';
const action = 'foo-action';
@ -101,38 +52,9 @@ describe(`#savedObjectsAuthorizationFailure`, () => {
});
describe(`#savedObjectsAuthorizationSuccess`, () => {
test(`doesn't log anything when xpack.security.enabled is false`, () => {
const config = createMockConfig({
'xpack.security.enabled': false,
});
test('logs auth success with spaceIds via auditLogger', () => {
const auditLogger = createMockAuditLogger();
const securityAuditLogger = new SpacesAuditLogger(config, auditLogger);
securityAuditLogger.spacesAuthorizationSuccess('foo-user', 'foo-action');
expect(auditLogger.log).toHaveBeenCalledTimes(0);
});
test(`doesn't log anything when xpack.security.audit.enabled is false`, () => {
const config = createMockConfig({
'xpack.security.enabled': true,
'xpack.security.audit.enabled': false,
});
const auditLogger = createMockAuditLogger();
const securityAuditLogger = new SpacesAuditLogger(config, auditLogger);
securityAuditLogger.spacesAuthorizationSuccess('foo-user', 'foo-action');
expect(auditLogger.log).toHaveBeenCalledTimes(0);
});
test('logs with spaceIds via auditLogger when xpack.security.audit.enabled is true', () => {
const config = createMockConfig({
'xpack.security.enabled': true,
'xpack.security.audit.enabled': true,
});
const auditLogger = createMockAuditLogger();
const securityAuditLogger = new SpacesAuditLogger(config, auditLogger);
const securityAuditLogger = new SpacesAuditLogger(auditLogger);
const username = 'foo-user';
const action = 'foo-action';
const spaceIds = ['foo-space-1', 'foo-space-2'];
@ -150,13 +72,9 @@ describe(`#savedObjectsAuthorizationSuccess`, () => {
);
});
test('logs without spaceIds via auditLogger when xpack.security.audit.enabled is true', () => {
const config = createMockConfig({
'xpack.security.enabled': true,
'xpack.security.audit.enabled': true,
});
test('logs auth success without spaceIds via auditLogger', () => {
const auditLogger = createMockAuditLogger();
const securityAuditLogger = new SpacesAuditLogger(config, auditLogger);
const securityAuditLogger = new SpacesAuditLogger(auditLogger);
const username = 'foo-user';
const action = 'foo-action';

View file

@ -5,19 +5,12 @@
*/
export class SpacesAuditLogger {
private readonly enabled: boolean;
private readonly auditLogger: any;
constructor(config: any, auditLogger: any) {
this.enabled =
config.get('xpack.security.enabled') && config.get('xpack.security.audit.enabled');
constructor(auditLogger: any) {
this.auditLogger = auditLogger;
}
public spacesAuthorizationFailure(username: string, action: string, spaceIds?: string[]) {
if (!this.enabled) {
return;
}
this.auditLogger.log(
'spaces_authorization_failure',
`${username} unauthorized to ${action}${spaceIds ? ' ' + spaceIds.join(',') : ''} spaces`,
@ -30,10 +23,6 @@ export class SpacesAuditLogger {
}
public spacesAuthorizationSuccess(username: string, action: string, spaceIds?: string[]) {
if (!this.enabled) {
return;
}
this.auditLogger.log(
'spaces_authorization_success',
`${username} authorized to ${action}${spaceIds ? ' ' + spaceIds.join(',') : ''} spaces`,

View file

@ -26,11 +26,6 @@ export async function replaceInjectedVars(originalInjectedVars, request, server)
return originalInjectedVars;
}
// authentication is not a thing you can do
if (xpackInfo.license.isOneOf('basic')) {
return await withXpackInfo();
}
// request is not authenticated
if (!await server.plugins.security.isAuthenticated(request)) {
return originalInjectedVars;

View file

@ -17,14 +17,18 @@ require('@kbn/test').runTestsCli([
//require.resolve('../test/reporting/configs/phantom_api.js'),
//require.resolve('../test/reporting/configs/phantom_functional.js'),
require.resolve('../test/functional/config.js'),
require.resolve('../test/api_integration/config_security_basic.js'),
require.resolve('../test/api_integration/config.js'),
require.resolve('../test/plugin_api_integration/config.js'),
require.resolve('../test/saml_api_integration/config.js'),
require.resolve('../test/token_api_integration/config.js'),
require.resolve('../test/spaces_api_integration/spaces_only/config'),
require.resolve('../test/spaces_api_integration/security_and_spaces/config'),
require.resolve('../test/saved_object_api_integration/security_and_spaces/config'),
require.resolve('../test/saved_object_api_integration/security_only/config'),
require.resolve('../test/spaces_api_integration/security_and_spaces/config_trial'),
require.resolve('../test/spaces_api_integration/security_and_spaces/config_basic'),
require.resolve('../test/saved_object_api_integration/security_and_spaces/config_trial'),
require.resolve('../test/saved_object_api_integration/security_and_spaces/config_basic'),
require.resolve('../test/saved_object_api_integration/security_only/config_trial'),
require.resolve('../test/saved_object_api_integration/security_only/config_basic'),
require.resolve('../test/saved_object_api_integration/spaces_only/config'),
...v6tests,
]);

View file

@ -3,17 +3,113 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { i18n } from '@kbn/i18n';
export const LICENSE_TYPE_BASIC = 'basic';
export const LICENSE_TYPE_STANDARD = 'standard';
export const LICENSE_TYPE_GOLD = 'gold';
export const LICENSE_TYPE_PLATINUM = 'platinum';
export const LICENSE_TYPE_TRIAL = 'trial';
export const LICENSE_STATUS_UNAVAILABLE = 'UNAVAILABLE';
export const LICENSE_STATUS_INVALID = 'INVALID';
export const LICENSE_STATUS_EXPIRED = 'EXPIRED';
export const LICENSE_STATUS_VALID = 'VALID';
// These are ordered from least featureful to most featureful, so we can assume that someone holding
// a license at a particular index cannot access any features unlocked by the licenses that follow it.
const RANKED_LICENSE_TYPES = [
LICENSE_TYPE_BASIC,
LICENSE_TYPE_STANDARD,
LICENSE_TYPE_GOLD,
LICENSE_TYPE_PLATINUM,
LICENSE_TYPE_TRIAL,
];
const FEATURE = {
ID: 'audit_logging'
};
export class AuditLogger {
constructor(server, pluginId) {
constructor(server, pluginId, config, xPackInfo) {
this._server = server;
this._pluginId = pluginId;
this._enabled = config.get('xpack.security.enabled') && config.get('xpack.security.audit.enabled');
this._licensed = false;
this._checkLicense = (xPackInfo) => {
this._licensed = checkLicense(FEATURE.ID, LICENSE_TYPE_STANDARD, xPackInfo).status === LICENSE_STATUS_VALID;
};
xPackInfo.feature(`${FEATURE.ID}-${pluginId}`).registerLicenseCheckResultsGenerator(this._checkLicense);
this._checkLicense(xPackInfo);
}
log(eventType, message, data = {}) {
if(!this._licensed || !this._enabled) {
return;
}
this._server.logWithMetadata(['info', 'audit', this._pluginId, eventType], message, {
...data,
eventType,
});
}
}
function checkLicense(pluginName, minimumLicenseRequired, xpackLicenseInfo) {
if(!RANKED_LICENSE_TYPES.includes(minimumLicenseRequired)) {
throw new Error(`Invalid license type supplied to checkLicense: ${minimumLicenseRequired}`);
}
// If, for some reason, we cannot get the license information
// from Elasticsearch, assume worst case and disable
if (!xpackLicenseInfo || !xpackLicenseInfo.isAvailable()) {
return {
status: LICENSE_STATUS_UNAVAILABLE,
message: i18n.translate(
'xpack.server.checkLicense.errorUnavailableMessage',
{
defaultMessage: 'You cannot use {pluginName} because license information is not available at this time.',
values: { pluginName },
},
),
};
}
const { license } = xpackLicenseInfo;
const isLicenseModeValid = license.isOneOf([...RANKED_LICENSE_TYPES].splice(RANKED_LICENSE_TYPES.indexOf(minimumLicenseRequired)));
const isLicenseActive = license.isActive();
const licenseType = license.getType();
// License is not valid
if (!isLicenseModeValid) {
return {
status: LICENSE_STATUS_INVALID,
message: i18n.translate(
'xpack.server.checkLicense.errorUnsupportedMessage',
{
defaultMessage: 'Your {licenseType} license does not support {pluginName}. Please upgrade your license.',
values: { licenseType, pluginName },
},
),
};
}
// License is valid but not active
if (!isLicenseActive) {
return {
status: LICENSE_STATUS_EXPIRED,
message: i18n.translate(
'xpack.server.checkLicense.errorExpiredMessage',
{
defaultMessage: 'You cannot use {pluginName} because your {licenseType} license has expired',
values: { licenseType, pluginName },
},
),
};
}
// License is valid and active
return {
status: LICENSE_STATUS_VALID,
};
}

View file

@ -3,14 +3,42 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { AuditLogger } from './audit_logger';
import { AuditLogger, LICENSE_TYPE_STANDARD, LICENSE_TYPE_BASIC, LICENSE_TYPE_GOLD } from './audit_logger';
const createMockConfig = (settings) => {
const mockConfig = {
get: jest.fn()
};
mockConfig.get.mockImplementation(key => {
return settings[key];
});
return mockConfig;
};
const mockLicenseInfo = {
isAvailable: () => true,
feature: () => { return { registerLicenseCheckResultsGenerator: () => { return; } };},
license: {
isActive: () => true,
isOneOf: () => true,
getType: () => LICENSE_TYPE_STANDARD
}
};
const mockConfig = createMockConfig({
'xpack.security.enabled': true,
'xpack.security.audit.enabled': true,
});
test(`calls server.log with 'info', audit', pluginId and eventType as tags`, () => {
const mockServer = {
logWithMetadata: jest.fn()
};
const pluginId = 'foo';
const auditLogger = new AuditLogger(mockServer, pluginId);
const auditLogger = new AuditLogger(mockServer, pluginId, mockConfig, mockLicenseInfo);
const eventType = 'bar';
auditLogger.log(eventType, '');
@ -18,13 +46,11 @@ test(`calls server.log with 'info', audit', pluginId and eventType as tags`, ()
expect(mockServer.logWithMetadata).toHaveBeenCalledWith(['info', 'audit', pluginId, eventType], expect.anything(), expect.anything());
});
test(`calls server.log with message`, () => {
const mockServer = {
logWithMetadata: jest.fn()
};
const auditLogger = new AuditLogger(mockServer, 'foo');
const auditLogger = new AuditLogger(mockServer, 'foo', mockConfig, mockLicenseInfo);
const message = 'summary of what happened';
auditLogger.log('bar', message);
@ -37,7 +63,7 @@ test(`calls server.log with metadata `, () => {
logWithMetadata: jest.fn()
};
const auditLogger = new AuditLogger(mockServer, 'foo');
const auditLogger = new AuditLogger(mockServer, 'foo', mockConfig, mockLicenseInfo);
const data = {
foo: 'yup',
@ -52,3 +78,88 @@ test(`calls server.log with metadata `, () => {
baz: data.baz,
});
});
test(`does not call server.log for license level < Standard`, () => {
const mockServer = {
logWithMetadata: jest.fn()
};
const mockLicenseInfo = {
isAvailable: () => true,
feature: () => { return { registerLicenseCheckResultsGenerator: () => { return; } };},
license: {
isActive: () => true,
isOneOf: () => false,
getType: () => LICENSE_TYPE_BASIC
}
};
const auditLogger = new AuditLogger(mockServer, 'foo', mockConfig, mockLicenseInfo);
auditLogger.log('bar', 'what happened');
expect(mockServer.logWithMetadata).toHaveBeenCalledTimes(0);
});
test(`does not call server.log if security is not enabled`, () => {
const mockServer = {
logWithMetadata: jest.fn()
};
const mockConfig = createMockConfig({
'xpack.security.enabled': false,
'xpack.security.audit.enabled': true,
});
const auditLogger = new AuditLogger(mockServer, 'foo', mockConfig, mockLicenseInfo);
auditLogger.log('bar', 'what happened');
expect(mockServer.logWithMetadata).toHaveBeenCalledTimes(0);
});
test(`does not call server.log if security audit logging is not enabled`, () => {
const mockServer = {
logWithMetadata: jest.fn()
};
const mockConfig = createMockConfig({
'xpack.security.enabled': true
});
const auditLogger = new AuditLogger(mockServer, 'foo', mockConfig, mockLicenseInfo);
auditLogger.log('bar', 'what happened');
expect(mockServer.logWithMetadata).toHaveBeenCalledTimes(0);
});
test(`calls server.log after basic -> gold upgrade`, () => {
const mockServer = {
logWithMetadata: jest.fn()
};
const endLicenseInfo = {
isAvailable: () => true,
license: {
isActive: () => true,
isOneOf: () => true,
getType: () => LICENSE_TYPE_GOLD
}
};
let licenseCheckResultsGenerator;
const startLicenseInfo = {
isAvailable: () => true,
feature: () => { return { registerLicenseCheckResultsGenerator: (fn) => { licenseCheckResultsGenerator = fn; } };},
license: {
isActive: () => true,
isOneOf: () => false,
getType: () => LICENSE_TYPE_BASIC
}
};
const auditLogger = new AuditLogger(mockServer, 'foo', mockConfig, startLicenseInfo);
auditLogger.log('bar', 'what happened');
expect(mockServer.logWithMetadata).toHaveBeenCalledTimes(0);
//change basic to gold
licenseCheckResultsGenerator(endLicenseInfo);
auditLogger.log('bar', 'what happened');
expect(mockServer.logWithMetadata).toHaveBeenCalledTimes(1);
});

View file

@ -5,7 +5,9 @@
*/
export default function ({ loadTestFile }) {
describe('security', () => {
describe('security', function () {
this.tags('ciGroup6');
loadTestFile(require.resolve('./basic_login'));
loadTestFile(require.resolve('./roles'));
});

View file

@ -9,6 +9,8 @@ import expect from 'expect.js';
export default function ({ getService }) {
const es = getService('es');
const supertest = getService('supertest');
const config = getService('config');
const basic = config.get('esTestCluster.license') === 'basic';
describe('Roles', () => {
describe('Create Role', () => {
@ -30,13 +32,8 @@ export default function ({ getService }) {
cluster: ['manage'],
indices: [
{
field_security: {
grant: ['*'],
except: ['geo.*']
},
names: ['logstash-*'],
privileges: ['read', 'view_index_metadata'],
query: `{ "match": { "geo.src": "CN" } }`,
},
],
run_as: ['watcher_user'],
@ -57,11 +54,6 @@ export default function ({ getService }) {
names: ['logstash-*'],
privileges: ['read', 'view_index_metadata'],
allow_restricted_indices: false,
field_security: {
grant: ['*'],
except: ['geo.*']
},
query: `{ "match": { "geo.src": "CN" } }`,
},
],
applications: [
@ -81,6 +73,33 @@ export default function ({ getService }) {
}
});
});
it(`should ${basic ? 'not' : ''} create a role with kibana and FLS/DLS elasticsearch
privileges on ${basic ? 'basic' : 'trial'} licenses`, async () => {
await supertest.put('/api/security/role/role_with_privileges_dls_fls')
.set('kbn-xsrf', 'xxx')
.send({
metadata: {
foo: 'test-metadata',
},
elasticsearch: {
cluster: ['manage'],
indices: [
{
field_security: {
grant: ['*'],
except: ['geo.*']
},
names: ['logstash-*'],
privileges: ['read', 'view_index_metadata'],
query: `{ "match": { "geo.src": "CN" } }`,
},
],
run_as: ['watcher_user'],
},
})
.expect(basic ? 403 : 204);
});
});
describe('Update Role', () => {
@ -93,11 +112,6 @@ export default function ({ getService }) {
{
names: ['beats-*'],
privileges: ['write'],
field_security: {
grant: ['request.*'],
except: ['response.*']
},
query: `{ "match": { "host.name": "localhost" } }`,
},
],
applications: [
@ -129,13 +143,8 @@ export default function ({ getService }) {
cluster: ['manage'],
indices: [
{
field_security: {
grant: ['*'],
except: ['geo.*']
},
names: ['logstash-*'],
privileges: ['read', 'view_index_metadata'],
query: `{ "match": { "geo.src": "CN" } }`,
allow_restricted_indices: true,
},
],
@ -157,11 +166,6 @@ export default function ({ getService }) {
names: ['logstash-*'],
privileges: ['read', 'view_index_metadata'],
allow_restricted_indices: true,
field_security: {
grant: ['*'],
except: ['geo.*']
},
query: `{ "match": { "geo.src": "CN" } }`,
},
],
applications: [
@ -186,20 +190,75 @@ export default function ({ getService }) {
}
});
});
it(`should ${basic ? 'not' : ''} update a role adding DLS and TLS priviledges
when using ${basic ? 'basic' : 'trial'} license`, async () => {
await es.shield.putRole({
name: 'role_to_update_with_dls_fls',
body: {
cluster: ['monitor'],
indices: [
{
names: ['beats-*'],
privileges: ['write'],
},
],
run_as: ['reporting_user'],
}
});
await supertest.put('/api/security/role/role_to_update_with_dls_fls')
.set('kbn-xsrf', 'xxx')
.send({
elasticsearch: {
cluster: ['manage'],
indices: [
{
field_security: {
grant: ['*'],
except: ['geo.*']
},
names: ['logstash-*'],
privileges: ['read'],
query: `{ "match": { "geo.src": "CN" } }`,
},
],
run_as: ['watcher_user'],
},
})
.expect(basic ? 403 : 204);
const role = await es.shield.getRole({ name: 'role_to_update_with_dls_fls' });
expect(role.role_to_update_with_dls_fls.cluster).to.eql(basic ? ['monitor'] : ['manage']);
expect(role.role_to_update_with_dls_fls.run_as).to.eql(basic ? ['reporting_user'] : ['watcher_user']);
expect(role.role_to_update_with_dls_fls.indices[0].names).to.eql(basic ? ['beats-*'] : ['logstash-*']);
expect(role.role_to_update_with_dls_fls.indices[0].query).to.eql(basic ? undefined : `{ "match": { "geo.src": "CN" } }`);
});
});
describe('Delete Role', () => {
it('should delete the three roles we created', async () => {
it('should delete the roles we created', async () => {
await supertest.delete('/api/security/role/empty_role').set('kbn-xsrf', 'xxx').expect(204);
await supertest.delete('/api/security/role/role_with_privileges').set('kbn-xsrf', 'xxx').expect(204);
await supertest.delete('/api/security/role/role_with_privileges_dls_fls').set('kbn-xsrf', 'xxx').expect(basic ? 404 : 204);
await supertest.delete('/api/security/role/role_to_update').set('kbn-xsrf', 'xxx').expect(204);
await supertest.delete('/api/security/role/role_to_update_with_dls_fls').set('kbn-xsrf', 'xxx').expect(204);
const emptyRole = await es.shield.getRole({ name: 'empty_role', ignore: [404] });
expect(emptyRole).to.eql({});
const roleWithPrivileges = await es.shield.getRole({ name: 'role_with_privileges', ignore: [404] });
expect(roleWithPrivileges).to.eql({});
const roleWithPriviledgesDlsFls = await es.shield.getRole({ name: 'role_with_privileges_dls_fls', ignore: [404] });
expect(roleWithPriviledgesDlsFls).to.eql({});
const roleToUpdate = await es.shield.getRole({ name: 'role_to_update', ignore: [404] });
expect(roleToUpdate).to.eql({});
const roleToUpdateWithDlsFls = await es.shield.getRole({ name: 'role_to_update_with_dls_fls', ignore: [404] });
expect(roleToUpdateWithDlsFls).to.eql({});
});
});
});

View file

@ -0,0 +1,17 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { default as createTestConfig } from './config';
export default async function ({ readConfigFile }) {
//security APIs should function the same under a basic or trial license
return createTestConfig({ readConfigFile }).then(config => {
config.esTestCluster.license = 'basic';
config.esTestCluster.serverArgs = ['xpack.license.self_generated.type=basic', 'xpack.security.enabled=true'];
config.testFiles = [require.resolve('./apis/security')];
return config;
});
}

View file

@ -54,7 +54,7 @@ export function createTestConfig(name: string, options: CreateTestConfigOptions)
license,
serverArgs: [
`xpack.license.self_generated.type=${license}`,
`xpack.security.enabled=${!disabledPlugins.includes('security') && license === 'trial'}`,
`xpack.security.enabled=${!disabledPlugins.includes('security')}`,
],
},

View file

@ -0,0 +1,10 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { createTestConfig } from '../common/config';
// tslint:disable-next-line: no-default-export
export default createTestConfig('security_and_spaces', { license: 'basic' });

View file

@ -0,0 +1,10 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { createTestConfig } from '../common/config';
// tslint:disable-next-line: no-default-export
export default createTestConfig('security_only', { disabledPlugins: ['spaces'], license: 'basic' });

View file

@ -6,5 +6,5 @@
import { createTestConfig } from '../common/config';
// tslint:disable:no-default-export
export default createTestConfig('spaces_only', { license: 'basic' });
// tslint:disable-next-line: no-default-export
export default createTestConfig('spaces_only', { disabledPlugins: ['security'], license: 'basic' });

View file

@ -54,7 +54,7 @@ export function createTestConfig(name: string, options: CreateTestConfigOptions)
license,
serverArgs: [
`xpack.license.self_generated.type=${license}`,
`xpack.security.enabled=${!disabledPlugins.includes('security') && license === 'trial'}`,
`xpack.security.enabled=${!disabledPlugins.includes('security')}`,
],
},

View file

@ -0,0 +1,10 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { createTestConfig } from '../common/config';
// tslint:disable-next-line: no-default-export
export default createTestConfig('security_and_spaces', { license: 'basic' });

View file

@ -5,5 +5,5 @@
*/
import { createTestConfig } from '../common/config';
// tslint:disable:no-default-export
export default createTestConfig('spaces_only', { license: 'basic' });
// tslint:disable-next-line: no-default-export
export default createTestConfig('spaces_only', { disabledPlugins: ['security'], license: 'basic' });