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

[WIP] Securing Spaces #21995

Merged
merged 88 commits into from
Sep 12, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
88 commits
Select commit Hold shift + click to select a range
a25ed8e
Splitting to a client and a wrapper, integation tests are passing
kobelb Aug 2, 2018
e0a83cd
Adding the spaces wrapper back in the mix using a priority collection
kobelb Aug 3, 2018
f937154
Restructuring the secure wrapper as we don't need to switch between
kobelb Aug 3, 2018
591a31b
Checking authorization at the current space
kobelb Aug 3, 2018
dbfe32e
Beginning to make the rbac api integration tests run against spaces
kobelb Aug 6, 2018
d25514a
Adding identical data to the rbac esArchives for two more spaces
kobelb Aug 6, 2018
7ed4332
Adding some space tests for find
kobelb Aug 6, 2018
43322c2
Beginning to work on the spaces client
kobelb Aug 8, 2018
daad804
Fixing find and filtering out unrequested privileges from response
kobelb Aug 8, 2018
d5b9b53
Adding get code and test
kobelb Aug 8, 2018
74335fa
Introducing an RBAC Auth Scope
kobelb Aug 10, 2018
706c505
Exposing the spacesClient a bit better
kobelb Aug 10, 2018
e0a6c6f
Moving the server.expose to the security plugin init
kobelb Aug 13, 2018
5487f0f
Moving checkPrivilegesAtAllResources to it's own thing
kobelb Aug 13, 2018
69b1eb6
No longer using the auth scope for RBAC, dashboard mode didn't work with
kobelb Aug 13, 2018
685b061
Securing the create space method
kobelb Aug 14, 2018
c7e390b
Adding secure update method
kobelb Aug 14, 2018
6d5a15a
Adding secured delete endpoints
kobelb Aug 14, 2018
e355ff0
Restructuring some code in the spaces client
kobelb Aug 14, 2018
67b64e9
Adding tests for the select endpoint
kobelb Aug 14, 2018
52633f0
Spaces can't be managed via the SavedObjectsClient now
kobelb Aug 15, 2018
d2ab5cc
Creating separate space_all and space_read privileges
kobelb Aug 15, 2018
cff7c61
Merge branch 'spaces-phase-1' into spaces/securing
kobelb Aug 17, 2018
b827684
Splitting out the spaces and global privileges
kobelb Aug 20, 2018
ed54020
Merge branch 'spaces-phase-1' into spaces/securing
kobelb Aug 20, 2018
3d75fb3
Fixing edit role screen after API changes
kobelb Aug 20, 2018
151d4ee
Revising comment, there is a Set in JavaScript now, but lodash can't
kobelb Aug 20, 2018
cec82a9
Using authorization mode to log deprecation warning on login
kobelb Aug 21, 2018
5649e10
Changing the signature of checkPrivileges
kobelb Aug 22, 2018
5995cae
Refactoring the way we specify resources when checking privileges
kobelb Aug 22, 2018
b87b506
Exposing the space service more intuitively
kobelb Aug 22, 2018
13fdf1f
Fixing comments
kobelb Aug 22, 2018
588e2e8
Security defines all actions
kobelb Aug 22, 2018
87a8332
Renaming `response` returned by the checkPrivileges function
kobelb Aug 23, 2018
58f037c
Merge remote-tracking branch 'upstream/spaces-phase-1' into spaces/se…
kobelb Aug 28, 2018
e64ec72
Hard-coding the kibana app privileges teporarily
kobelb Aug 28, 2018
daca66b
Adding Authenticator authorization mode tests
kobelb Aug 28, 2018
8ee733f
Adding actions.manageSpaces tests
kobelb Aug 28, 2018
58aa6f1
Adding check privileges tests
kobelb Aug 28, 2018
18e058f
Fixing checkPrivileges test snapshots
kobelb Aug 28, 2018
3813a12
Making sure tests fail until I correct this deficiency
kobelb Aug 28, 2018
a50d51a
Adding stubbed out authorization mode tests
kobelb Aug 28, 2018
4dfb720
Fixing tests for RegisterPrivilegesWithCluster
kobelb Aug 28, 2018
2a632f0
Fixing service AuthorizationService tests
kobelb Aug 28, 2018
e3b23e6
Addinng serializer tests
kobelb Aug 29, 2018
1f5c127
Adding validateEsResponse tests
kobelb Aug 29, 2018
31cc9ff
We don't need the SecureSavedObjectsClient anymore!
kobelb Aug 29, 2018
ac3d143
Adding SecureSavedObjectsClientWrapper tests...
kobelb Aug 29, 2018
aefe2c5
Merge remote-tracking branch 'upstream/spaces-phase-1' into spaces/se…
kobelb Sep 4, 2018
ce171ca
Fixing a few stray tests
kobelb Sep 4, 2018
cef2e78
Fixing issue when user isn't authenticated and check useRbacForRequest
kobelb Sep 4, 2018
7b4575b
Merge remote-tracking branch 'upstream/spaces-phase-1' into spaces/se…
kobelb Sep 5, 2018
5413b47
Validating spaces we're adding to roles
kobelb Sep 7, 2018
c02d0fb
Reusing hasAnyPrivileges from hasAnyResourcePrivileges
kobelb Sep 7, 2018
1436495
Better variable name
kobelb Sep 7, 2018
79884be
toArray -> toPrioritizedArray
kobelb Sep 7, 2018
b7d2aa4
Using Space throughout the SpacesClient
kobelb Sep 7, 2018
14a1d96
GetActiveSpace uses the SpacesClient now
kobelb Sep 10, 2018
d9699f9
Squashed commit of the following:
kobelb Sep 10, 2018
6da2f05
Merge branch 'spaces-phase-1' into spaces/securing
legrego Sep 10, 2018
8e52fc0
update public api to use SpacesClient
legrego Sep 7, 2018
81b785b
fix
legrego Sep 7, 2018
b3b04f3
test and api fixes
legrego Sep 7, 2018
fb40ad9
fix tests
legrego Sep 10, 2018
eb3fde7
Merge branch 'spaces-phase-1' into spaces/securing
legrego Sep 10, 2018
f52e688
Merge pull request #5 from legrego/spaces/securing-update
kobelb Sep 10, 2018
88b03e7
Disallowing use of Spaces with the SpacesSavedObjectsClientWrapper
kobelb Sep 11, 2018
b2e4321
Adding spaces audit logging
kobelb Sep 11, 2018
454dcb1
Updating snapshots
kobelb Sep 11, 2018
d01f9b6
Adding get and getAll tests for the spaces client
kobelb Sep 11, 2018
e3569c8
Adding update tests
kobelb Sep 12, 2018
90b9811
Adding create tests
kobelb Sep 12, 2018
3d0a8f4
Adding SpacesClient delete tests
kobelb Sep 12, 2018
a061555
Fixing authenticate tests
kobelb Sep 12, 2018
b8d9738
Making tests more consistent
kobelb Sep 12, 2018
5e79942
Merge remote-tracking branch 'upstream/spaces-phase-1' into spaces/se…
kobelb Sep 12, 2018
54360a2
Fixing kibana privileges tests
kobelb Sep 12, 2018
ec1eeb7
Fixing a few type issues
kobelb Sep 12, 2018
6685a52
Making typescript ignore the .only test suites
kobelb Sep 12, 2018
dea9859
Switching to beforeEach and afterEach and removing the mocha types
kobelb Sep 12, 2018
d9dc42b
Switching to our own expect.js definitions
kobelb Sep 12, 2018
77538e8
Fixing more linting rules
kobelb Sep 12, 2018
b7fc11e
Removing test stubs and replacing with TODOs. Updating snapshots
kobelb Sep 12, 2018
f59a2ee
No longer shadowing application for the put role API
kobelb Sep 12, 2018
bbc54cb
Relying on the errors thrown by the SpacedClient when determining active
kobelb Sep 12, 2018
aba3f39
Back to after/before... mocha doesn't have beforeAll/afterAll
kobelb Sep 12, 2018
0a531b5
We got them types, thanks Spencer!!!
kobelb Sep 12, 2018
a68f029
Ignoring space type from the secure saved objects client find with no
kobelb Sep 12, 2018
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 7 additions & 4 deletions packages/kbn-test/src/functional_tests/lib/run_elasticsearch.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,15 @@ import { setupUsers, DEFAULT_SUPERUSER_PASS } from './auth';

export async function runElasticsearch({ config, options }) {
const { log, esFrom } = options;
const isOss = config.get('esTestCluster.license') === 'oss';
const license = config.get('esTestCluster.license');
const isTrialLicense = config.get('esTestCluster.license') === 'trial';

const cluster = createEsTestCluster({
port: config.get('servers.elasticsearch.port'),
password: !isOss ? DEFAULT_SUPERUSER_PASS : config.get('servers.elasticsearch.password'),
license: config.get('esTestCluster.license'),
password: isTrialLicense
? DEFAULT_SUPERUSER_PASS
: config.get('servers.elasticsearch.password'),
license,
log,
basePath: resolve(KIBANA_ROOT, '.es'),
esFrom: esFrom || config.get('esTestCluster.from'),
Expand All @@ -40,7 +43,7 @@ export async function runElasticsearch({ config, options }) {

await cluster.start(esArgs);

if (!isOss) {
if (isTrialLicense) {
await setupUsers(log, config);
}

Expand Down
1 change: 1 addition & 0 deletions src/dev/typescript/projects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { Project } from './project';
export const PROJECTS = [
new Project(resolve(REPO_ROOT, 'tsconfig.json')),
new Project(resolve(REPO_ROOT, 'x-pack/tsconfig.json')),
new Project(resolve(REPO_ROOT, 'x-pack/test/tsconfig.json'), 'x-pack/test'),

// NOTE: using glob.sync rather than glob-all or globby
// because it takes less than 10 ms, while the other modules
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`1, 1 throws Error 1`] = `"Already have entry with this priority"`;
57 changes: 57 additions & 0 deletions src/server/saved_objects/service/lib/priority_collection.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { PriorityCollection } from './priority_collection';

test(`1, 2, 3`, () => {
const priorityCollection = new PriorityCollection();
priorityCollection.add(1, 1);
priorityCollection.add(2, 2);
priorityCollection.add(3, 3);
expect(priorityCollection.toPrioritizedArray()).toEqual([1, 2, 3]);
});

test(`3, 2, 1`, () => {
const priorityCollection = new PriorityCollection();
priorityCollection.add(3, 3);
priorityCollection.add(2, 2);
priorityCollection.add(1, 1);
expect(priorityCollection.toPrioritizedArray()).toEqual([1, 2, 3]);
});

test(`2, 3, 1`, () => {
const priorityCollection = new PriorityCollection();
priorityCollection.add(2, 2);
priorityCollection.add(3, 3);
priorityCollection.add(1, 1);
expect(priorityCollection.toPrioritizedArray()).toEqual([1, 2, 3]);
});

test(`Number.MAX_VALUE, NUMBER.MIN_VALUE, 1`, () => {
const priorityCollection = new PriorityCollection();
priorityCollection.add(Number.MAX_VALUE, 3);
priorityCollection.add(Number.MIN_VALUE, 1);
priorityCollection.add(1, 2);
expect(priorityCollection.toPrioritizedArray()).toEqual([1, 2, 3]);
});

test(`1, 1 throws Error`, () => {
const priorityCollection = new PriorityCollection();
priorityCollection.add(1, 1);
expect(() => priorityCollection.add(1, 1)).toThrowErrorMatchingSnapshot();
});
47 changes: 47 additions & 0 deletions src/server/saved_objects/service/lib/priority_collection.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

interface PriorityCollectionEntry<T> {
priority: number;
value: T;
}

export class PriorityCollection<T> {
private readonly array: Array<PriorityCollectionEntry<T>> = [];

public add(priority: number, value: T) {
let i = 0;
while (i < this.array.length) {
const current = this.array[i];
if (priority === current.priority) {
throw new Error('Already have entry with this priority');
}

if (priority < current.priority) {
break;
}
++i;
}
this.array.splice(i, 0, { priority, value });
}

public toPrioritizedArray(): T[] {
return this.array.map(entry => entry.value);
}
}
29 changes: 12 additions & 17 deletions src/server/saved_objects/service/lib/scoped_client_provider.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,30 +16,23 @@
* specific language governing permissions and limitations
* under the License.
*/
import { PriorityCollection } from './priority_collection';

/**
* Provider for the Scoped Saved Object Client.
*/
export class ScopedSavedObjectsClientProvider {

_wrapperFactories = [];
_wrapperFactories = new PriorityCollection();

constructor({
defaultClientFactory
}) {
this._originalClientFactory = this._clientFactory = defaultClientFactory;
}

// the client wrapper factories are put at the front of the array, so that
// when we use `reduce` below they're invoked in LIFO order. This is so that
// if multiple plugins register their client wrapper factories, then we can use
// the plugin dependencies/optionalDependencies to implicitly control the order
// in which these are used. For example, if we have a plugin a that declares a
// dependency on plugin b, that means that plugin b's client wrapper would want
// to be able to run first when the SavedObjectClient methods are invoked to
// provide additional context to plugin a's client wrapper.
addClientWrapperFactory(wrapperFactory) {
this._wrapperFactories.unshift(wrapperFactory);
addClientWrapperFactory(priority, wrapperFactory) {
this._wrapperFactories.add(priority, wrapperFactory);
}

setClientFactory(customClientFactory) {
Expand All @@ -55,11 +48,13 @@ export class ScopedSavedObjectsClientProvider {
request,
});

return this._wrapperFactories.reduce((clientToWrap, wrapperFactory) => {
return wrapperFactory({
request,
client: clientToWrap,
});
}, client);
return this._wrapperFactories
.toPrioritizedArray()
.reduceRight((clientToWrap, wrapperFactory) => {
return wrapperFactory({
request,
client: clientToWrap,
});
}, client);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -64,40 +64,20 @@ test(`throws error when more than one scoped saved objects client factory is set
}).toThrowErrorMatchingSnapshot();
});

test(`invokes and uses instance from single added wrapper factory`, () => {
test(`invokes and uses wrappers in specified order`, () => {
const defaultClient = Symbol();
const defaultClientFactoryMock = jest.fn().mockReturnValue(defaultClient);
const clientProvider = new ScopedSavedObjectsClientProvider({
defaultClientFactory: defaultClientFactoryMock
});
const wrappedClient = Symbol();
const clientWrapperFactoryMock = jest.fn().mockReturnValue(wrappedClient);
const request = Symbol();

clientProvider.addClientWrapperFactory(clientWrapperFactoryMock);
const actualClient = clientProvider.getClient(request);

expect(actualClient).toBe(wrappedClient);
expect(clientWrapperFactoryMock).toHaveBeenCalledWith({
request,
client: defaultClient
});
});

test(`invokes and uses wrappers in LIFO order`, () => {
const defaultClient = Symbol();
const defaultClientFactoryMock = jest.fn().mockReturnValue(defaultClient);
const clientProvider = new ScopedSavedObjectsClientProvider({
defaultClientFactory: defaultClientFactoryMock
});
const firstWrappedClient = Symbol();
const firstWrappedClient = Symbol('first client');
const firstClientWrapperFactoryMock = jest.fn().mockReturnValue(firstWrappedClient);
const secondWrapperClient = Symbol();
const secondWrapperClient = Symbol('second client');
const secondClientWrapperFactoryMock = jest.fn().mockReturnValue(secondWrapperClient);
const request = Symbol();

clientProvider.addClientWrapperFactory(firstClientWrapperFactoryMock);
clientProvider.addClientWrapperFactory(secondClientWrapperFactoryMock);
clientProvider.addClientWrapperFactory(1, secondClientWrapperFactoryMock);
clientProvider.addClientWrapperFactory(0, firstClientWrapperFactoryMock);
const actualClient = clientProvider.getClient(request);

expect(actualClient).toBe(firstWrappedClient);
Expand Down
121 changes: 121 additions & 0 deletions test/api_integration/apis/saved_objects/bulk_create.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

import expect from 'expect.js';

export default function ({ getService }) {
const supertest = getService('supertest');
const es = getService('es');
const esArchiver = getService('esArchiver');

const BULK_REQUESTS = [
{
type: 'visualization',
id: 'dd7caf20-9efd-11e7-acb3-3dab96693fab',
attributes: {
title: 'An existing visualization'
}
},
{
type: 'dashboard',
id: 'a01b2f57-fcfd-4864-b735-09e28f0d815e',
attributes: {
title: 'A great new dashboard'
}
},
];

describe('_bulk_create', () => {
describe('with kibana index', () => {
before(() => esArchiver.load('saved_objects/basic'));
after(() => esArchiver.unload('saved_objects/basic'));

it('should return 200 with individual responses', async () => (
await supertest
.post(`/api/saved_objects/_bulk_create`)
.send(BULK_REQUESTS)
.expect(200)
.then(resp => {
expect(resp.body).to.eql({
saved_objects: [
{
type: 'visualization',
id: 'dd7caf20-9efd-11e7-acb3-3dab96693fab',
error: {
'message': 'version conflict, document already exists',
'statusCode': 409
}
},
{
type: 'dashboard',
id: 'a01b2f57-fcfd-4864-b735-09e28f0d815e',
updated_at: resp.body.saved_objects[1].updated_at,
version: 1,
attributes: {
title: 'A great new dashboard'
}
},
]
});
})
));
});

describe('without kibana index', () => {
before(async () => (
// just in case the kibana server has recreated it
await es.indices.delete({
index: '.kibana',
ignore: [404],
})
));

it('should return 200 with individual responses', async () => (
await supertest
.post('/api/saved_objects/_bulk_create')
.send(BULK_REQUESTS)
.expect(200)
.then(resp => {
expect(resp.body).to.eql({
saved_objects: [
{
type: 'visualization',
id: 'dd7caf20-9efd-11e7-acb3-3dab96693fab',
updated_at: resp.body.saved_objects[0].updated_at,
version: 1,
attributes: {
title: 'An existing visualization'
}
},
{
type: 'dashboard',
id: 'a01b2f57-fcfd-4864-b735-09e28f0d815e',
updated_at: resp.body.saved_objects[1].updated_at,
version: 1,
attributes: {
title: 'A great new dashboard'
}
},
]
});
})
));
});
});
}
1 change: 1 addition & 0 deletions test/api_integration/apis/saved_objects/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

export default function ({ loadTestFile }) {
describe('saved_objects', () => {
loadTestFile(require.resolve('./bulk_create'));
loadTestFile(require.resolve('./bulk_get'));
loadTestFile(require.resolve('./create'));
loadTestFile(require.resolve('./delete'));
Expand Down
1 change: 1 addition & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@

// Forbid unused local variables as the rule was deprecated by ts-lint
"noUnusedLocals": true,

},
"include": [
"src/**/*"
Expand Down
Loading