diff --git a/src/legacy/core_plugins/console/server/proxy_route.js b/src/legacy/core_plugins/console/server/proxy_route.js index 753927fe80a70..489d75850b433 100644 --- a/src/legacy/core_plugins/console/server/proxy_route.js +++ b/src/legacy/core_plugins/console/server/proxy_route.js @@ -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() diff --git a/x-pack/plugins/infra/server/kibana.index.ts b/x-pack/plugins/infra/server/kibana.index.ts index 108179e6bb593..20480aa93b313 100644 --- a/x-pack/plugins/infra/server/kibana.index.ts +++ b/x-pack/plugins/infra/server/kibana.index.ts @@ -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'], diff --git a/x-pack/plugins/infra/server/lib/adapters/framework/kibana_framework_adapter.ts b/x-pack/plugins/infra/server/lib/adapters/framework/kibana_framework_adapter.ts index 9eb576e895906..4f4f45fbc4e53 100644 --- a/x-pack/plugins/infra/server/lib/adapters/framework/kibana_framework_adapter.ts +++ b/x-pack/plugins/infra/server/lib/adapters/framework/kibana_framework_adapter.ts @@ -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, diff --git a/x-pack/plugins/security/index.js b/x-pack/plugins/security/index.js index 7cf383b42a2ed..54eb3d1c8ac32 100644 --- a/x-pack/plugins/security/index.js +++ b/x-pack/plugins/security/index.js @@ -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; }); } diff --git a/x-pack/plugins/xpack_main/server/lib/register_oss_features.ts b/x-pack/plugins/xpack_main/server/lib/register_oss_features.ts index 82de9be2d12be..9b870ae3a9683 100644 --- a/x-pack/plugins/xpack_main/server/lib/register_oss_features.ts +++ b/x-pack/plugins/xpack_main/server/lib/register_oss_features.ts @@ -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'], diff --git a/x-pack/test/api_integration/apis/console/feature_controls.ts b/x-pack/test/api_integration/apis/console/feature_controls.ts index f0273e4e26858..aa81447e4f4da 100644 --- a/x-pack/test/api_integration/apis/console/feature_controls.ts +++ b/x-pack/test/api_integration/apis/console/feature_controls.ts @@ -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); }); }); }); diff --git a/x-pack/test/api_integration/apis/infra/feature_controls.ts b/x-pack/test/api_integration/apis/infra/feature_controls.ts index 7a3d62b30d43e..008fa85ebbf8f 100644 --- a/x-pack/test/api_integration/apis/infra/feature_controls.ts +++ b/x-pack/test/api_integration/apis/infra/feature_controls.ts @@ -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); }); }); });