Skip to content

Commit

Permalink
Using HapiJS's scopes to perform authorization on api endpoints
Browse files Browse the repository at this point in the history
  • Loading branch information
kobelb committed Mar 11, 2019
1 parent e79d63b commit f73810c
Show file tree
Hide file tree
Showing 7 changed files with 61 additions and 38 deletions.
7 changes: 6 additions & 1 deletion src/legacy/core_plugins/console/server/proxy_route.js
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
8 changes: 4 additions & 4 deletions x-pack/plugins/infra/server/kibana.index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,15 +34,15 @@ export const initServerWithKibana = (kbnServer: KbnServer) => {
catalogue: ['infraops'],
privileges: {
all: {
api: ['infra/graphql'],
api: ['infra'],
savedObject: {
all: ['infrastructure-ui-source'],
read: ['config'],
},
ui: ['show', 'configureSource'],
},
read: {
api: ['infra/graphql'],
api: ['infra'],
savedObject: {
all: [],
read: ['config', 'infrastructure-ui-source'],
Expand All @@ -63,15 +63,15 @@ export const initServerWithKibana = (kbnServer: KbnServer) => {
catalogue: ['infralogging'],
privileges: {
all: {
api: ['infra/graphql'],
api: ['infra'],
savedObject: {
all: ['infrastructure-ui-source'],
read: ['config'],
},
ui: ['show', 'configureSource'],
},
read: {
api: ['infra/graphql'],
api: ['infra'],
savedObject: {
all: [],
read: ['config', 'infrastructure-ui-source'],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,11 @@ export class InfraKibanaBackendFrameworkAdapter implements InfraBackendFramework
}),
path: routePath,
route: {
tags: ['access:graphql'],
auth: {
access: {
scope: 'infra',
},
},
},
},
plugin: graphqlHapi,
Expand All @@ -71,7 +75,11 @@ export class InfraKibanaBackendFrameworkAdapter implements InfraBackendFramework
}),
path: `${routePath}/graphiql`,
route: {
tags: ['access:graphql'],
auth: {
access: {
scope: 'infra',
},
},
},
},
plugin: graphiqlHapi,
Expand Down
44 changes: 27 additions & 17 deletions x-pack/plugins/security/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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;
Expand All @@ -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;
});
}
Expand Down
4 changes: 2 additions & 2 deletions x-pack/plugins/xpack_main/server/lib/register_oss_features.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,15 +111,15 @@ const kibanaFeatures: Feature[] = [
catalogue: ['console', 'searchprofiler', 'grokdebugger'],
privileges: {
all: {
api: ['console/execute'],
api: ['console'],
savedObject: {
all: [],
read: ['config'],
},
ui: ['show'],
},
read: {
api: ['console/execute'],
api: ['console'],
savedObject: {
all: [],
read: ['config'],
Expand Down
4 changes: 2 additions & 2 deletions x-pack/test/api_integration/apis/console/feature_controls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -211,7 +211,7 @@ export default function securityTests({ getService }: KibanaFunctionalTestDefaul
.auth(user1.username, user1.password)
.set('kbn-xsrf', 'xxx')
.send()
.expect(404);
.expect(403);
});
});
});
Expand Down
20 changes: 10 additions & 10 deletions x-pack/test/api_integration/apis/infra/feature_controls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand All @@ -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) => {
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
});
});
});
Expand Down

0 comments on commit f73810c

Please sign in to comment.