mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
Using HapiJS's scopes to perform authorization on api endpoints
This commit is contained in:
parent
e79d63b5d1
commit
f73810c22d
7 changed files with 61 additions and 38 deletions
|
@ -65,12 +65,17 @@ export const createProxyRoute = ({
|
|||
path: '/api/console/proxy',
|
||||
method: 'POST',
|
||||
config: {
|
||||
tags: ['access:execute'],
|
||||
payload: {
|
||||
output: 'stream',
|
||||
parse: false
|
||||
},
|
||||
|
||||
auth: {
|
||||
access: {
|
||||
scope: 'console'
|
||||
}
|
||||
},
|
||||
|
||||
validate: {
|
||||
query: Joi.object().keys({
|
||||
method: Joi.string()
|
||||
|
|
|
@ -34,7 +34,7 @@ export const initServerWithKibana = (kbnServer: KbnServer) => {
|
|||
catalogue: ['infraops'],
|
||||
privileges: {
|
||||
all: {
|
||||
api: ['infra/graphql'],
|
||||
api: ['infra'],
|
||||
savedObject: {
|
||||
all: ['infrastructure-ui-source'],
|
||||
read: ['config'],
|
||||
|
@ -42,7 +42,7 @@ export const initServerWithKibana = (kbnServer: KbnServer) => {
|
|||
ui: ['show', 'configureSource'],
|
||||
},
|
||||
read: {
|
||||
api: ['infra/graphql'],
|
||||
api: ['infra'],
|
||||
savedObject: {
|
||||
all: [],
|
||||
read: ['config', 'infrastructure-ui-source'],
|
||||
|
@ -63,7 +63,7 @@ export const initServerWithKibana = (kbnServer: KbnServer) => {
|
|||
catalogue: ['infralogging'],
|
||||
privileges: {
|
||||
all: {
|
||||
api: ['infra/graphql'],
|
||||
api: ['infra'],
|
||||
savedObject: {
|
||||
all: ['infrastructure-ui-source'],
|
||||
read: ['config'],
|
||||
|
@ -71,7 +71,7 @@ export const initServerWithKibana = (kbnServer: KbnServer) => {
|
|||
ui: ['show', 'configureSource'],
|
||||
},
|
||||
read: {
|
||||
api: ['infra/graphql'],
|
||||
api: ['infra'],
|
||||
savedObject: {
|
||||
all: [],
|
||||
read: ['config', 'infrastructure-ui-source'],
|
||||
|
|
|
@ -57,7 +57,11 @@ export class InfraKibanaBackendFrameworkAdapter implements InfraBackendFramework
|
|||
}),
|
||||
path: routePath,
|
||||
route: {
|
||||
tags: ['access:graphql'],
|
||||
auth: {
|
||||
access: {
|
||||
scope: 'infra',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
plugin: graphqlHapi,
|
||||
|
@ -71,7 +75,11 @@ export class InfraKibanaBackendFrameworkAdapter implements InfraBackendFramework
|
|||
}),
|
||||
path: `${routePath}/graphiql`,
|
||||
route: {
|
||||
tags: ['access:graphql'],
|
||||
auth: {
|
||||
access: {
|
||||
scope: 'infra',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
plugin: graphiqlHapi,
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
*/
|
||||
|
||||
import Boom from 'boom';
|
||||
import { flatten, get, uniq } from 'lodash';
|
||||
import { resolve } from 'path';
|
||||
import { getUserProvider } from './server/lib/get_user';
|
||||
import { initAuthenticateApi } from './server/routes/api/v1/authenticate';
|
||||
|
@ -219,6 +220,32 @@ export const security = (kibana) => new kibana.Plugin({
|
|||
};
|
||||
});
|
||||
|
||||
server.plugins.security.registerAuthScopeGetter(async (request) => {
|
||||
if (!request.path.startsWith('/api/')) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { actions, checkPrivilegesDynamicallyWithRequest } = server.plugins.security.authorization;
|
||||
const checkPrivileges = checkPrivilegesDynamicallyWithRequest(request);
|
||||
|
||||
const access = get(request, 'route.settings.auth.access');
|
||||
if (!access) {
|
||||
return;
|
||||
}
|
||||
|
||||
const scopes = uniq(access.reduce((acc, entry) => ([...acc, ...flatten(Object.values(entry.scope))]), []));
|
||||
if (scopes.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const actionsToScopeMap = new Map(scopes.map(scope => ([actions.api.get(scope), scope])));
|
||||
|
||||
const checkPrivilegesResponse = await checkPrivileges(Array.from(actionsToScopeMap.keys()));
|
||||
const hasScopes = Object.entries(checkPrivilegesResponse.privileges)
|
||||
.filter(([, value]) => value)
|
||||
.map(([action]) => actionsToScopeMap.get(action));
|
||||
return hasScopes;
|
||||
});
|
||||
|
||||
server.ext('onPostAuth', async function (req, h) {
|
||||
const path = req.path;
|
||||
|
@ -243,23 +270,6 @@ export const security = (kibana) => new kibana.Plugin({
|
|||
}
|
||||
}
|
||||
|
||||
// Enforce API restrictions for associated applications
|
||||
if (path.startsWith('/api/')) {
|
||||
const { tags = [] } = req.route.settings;
|
||||
|
||||
const actionTags = tags.filter(tag => tag.startsWith('access:'));
|
||||
|
||||
if (actionTags.length > 0) {
|
||||
const feature = path.split('/', 3)[2];
|
||||
const apiActions = actionTags.map(tag => actions.api.get(`${feature}/${tag.split(':', 2)[1]}`));
|
||||
|
||||
const checkPrivilegesResponse = await checkPrivileges(apiActions);
|
||||
if (!checkPrivilegesResponse.hasAllRequested) {
|
||||
return Boom.notFound();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return h.continue;
|
||||
});
|
||||
}
|
||||
|
|
|
@ -111,7 +111,7 @@ const kibanaFeatures: Feature[] = [
|
|||
catalogue: ['console', 'searchprofiler', 'grokdebugger'],
|
||||
privileges: {
|
||||
all: {
|
||||
api: ['console/execute'],
|
||||
api: ['console'],
|
||||
savedObject: {
|
||||
all: [],
|
||||
read: ['config'],
|
||||
|
@ -119,7 +119,7 @@ const kibanaFeatures: Feature[] = [
|
|||
ui: ['show'],
|
||||
},
|
||||
read: {
|
||||
api: ['console/execute'],
|
||||
api: ['console'],
|
||||
savedObject: {
|
||||
all: [],
|
||||
read: ['config'],
|
||||
|
|
|
@ -137,7 +137,7 @@ export default function securityTests({ getService }: KibanaFunctionalTestDefaul
|
|||
.auth(username, password)
|
||||
.set('kbn-xsrf', 'xxx')
|
||||
.send()
|
||||
.expect(404);
|
||||
.expect(403);
|
||||
} finally {
|
||||
await security.role.delete(roleName);
|
||||
await security.user.delete(username);
|
||||
|
@ -211,7 +211,7 @@ export default function securityTests({ getService }: KibanaFunctionalTestDefaul
|
|||
.auth(user1.username, user1.password)
|
||||
.set('kbn-xsrf', 'xxx')
|
||||
.send()
|
||||
.expect(404);
|
||||
.expect(403);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -26,11 +26,11 @@ const featureControlsTests: KbnTestProvider = ({ getService }) => {
|
|||
const spaces: SpacesService = getService('spaces');
|
||||
const clientFactory = getService('infraOpsGraphQLClientFactory');
|
||||
|
||||
const expectGraphQL404 = (result: any) => {
|
||||
const expectGraphQL403 = (result: any) => {
|
||||
expect(result.response).to.be(undefined);
|
||||
expect(result.error).not.to.be(undefined);
|
||||
expect(result.error).to.have.property('networkError');
|
||||
expect(result.error.networkError).to.have.property('statusCode', 404);
|
||||
expect(result.error.networkError).to.have.property('statusCode', 403);
|
||||
};
|
||||
|
||||
const expectGraphQLResponse = (result: any) => {
|
||||
|
@ -39,10 +39,10 @@ const featureControlsTests: KbnTestProvider = ({ getService }) => {
|
|||
expect(result.response.data).to.be.an('object');
|
||||
};
|
||||
|
||||
const expectGraphIQL404 = (result: any) => {
|
||||
const expectGraphIQL403 = (result: any) => {
|
||||
expect(result.error).to.be(undefined);
|
||||
expect(result.response).not.to.be(undefined);
|
||||
expect(result.response).to.have.property('statusCode', 404);
|
||||
expect(result.response).to.have.property('statusCode', 403);
|
||||
};
|
||||
|
||||
const expectGraphIQLResponse = (result: any) => {
|
||||
|
@ -106,10 +106,10 @@ const featureControlsTests: KbnTestProvider = ({ getService }) => {
|
|||
});
|
||||
|
||||
const graphQLResult = await executeGraphQLQuery(username, password);
|
||||
expectGraphQL404(graphQLResult);
|
||||
expectGraphQL403(graphQLResult);
|
||||
|
||||
const graphQLIResult = await executeGraphIQLRequest(username, password);
|
||||
expectGraphIQL404(graphQLIResult);
|
||||
expectGraphIQL403(graphQLIResult);
|
||||
} finally {
|
||||
await security.role.delete(roleName);
|
||||
await security.user.delete(username);
|
||||
|
@ -187,10 +187,10 @@ const featureControlsTests: KbnTestProvider = ({ getService }) => {
|
|||
});
|
||||
|
||||
const graphQLResult = await executeGraphQLQuery(username, password);
|
||||
expectGraphQL404(graphQLResult);
|
||||
expectGraphQL403(graphQLResult);
|
||||
|
||||
const graphQLIResult = await executeGraphIQLRequest(username, password);
|
||||
expectGraphIQL404(graphQLIResult);
|
||||
expectGraphIQL403(graphQLIResult);
|
||||
} finally {
|
||||
await security.role.delete(roleName);
|
||||
await security.user.delete(username);
|
||||
|
@ -285,10 +285,10 @@ const featureControlsTests: KbnTestProvider = ({ getService }) => {
|
|||
|
||||
it(`user_1 can't access APIs in space_3`, async () => {
|
||||
const graphQLResult = await executeGraphQLQuery(username, password, space3Id);
|
||||
expectGraphQL404(graphQLResult);
|
||||
expectGraphQL403(graphQLResult);
|
||||
|
||||
const graphQLIResult = await executeGraphIQLRequest(username, password, space3Id);
|
||||
expectGraphIQL404(graphQLIResult);
|
||||
expectGraphIQL403(graphQLIResult);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue