-
Notifications
You must be signed in to change notification settings - Fork 8.3k
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
POC to migrate Code to use Feature Controls #35093
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,7 +5,9 @@ | |
*/ | ||
|
||
import { Server } from 'hapi'; | ||
import { i18n } from '@kbn/i18n'; | ||
import fetch from 'node-fetch'; | ||
import { XPackMainPlugin } from '../../xpack_main/xpack_main'; | ||
import { checkRepos } from './check_repos'; | ||
import { LspIndexerFactory, RepositoryIndexInitializerFactory, tryMigrateIndices } from './indexer'; | ||
import { EsClient, Esqueue } from './lib/esqueue'; | ||
|
@@ -77,6 +79,36 @@ async function getCodeNodeUuid(url: string, log: Logger) { | |
export function init(server: Server, options: any) { | ||
const log = new Logger(server); | ||
const serverOptions = new ServerOptions(options, server.config()); | ||
const xpackMainPlugin: XPackMainPlugin = server.plugins.xpack_main; | ||
xpackMainPlugin.registerFeature({ | ||
id: 'code', | ||
name: i18n.translate('xpack.code.featureRegistry.codeFeatureName', { | ||
defaultMessage: 'Code', | ||
}), | ||
icon: 'codeApp', | ||
navLinkId: 'code', | ||
app: ['code', 'kibana'], | ||
catalogue: [], | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If you add an entry to the Feature Catalogue, then you should add the ID of that entry to this array. This catalogue is what populates the Kibana Home page with the various features that users can enjoy. |
||
privileges: { | ||
all: { | ||
api: ['code_user', 'code_admin'], | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This grants users with the |
||
savedObject: { | ||
all: [], | ||
read: ['config'], | ||
}, | ||
ui: ['show', 'user', 'admin'], | ||
}, | ||
read: { | ||
api: ['code_user'], | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This grants users with the |
||
savedObject: { | ||
all: [], | ||
read: ['config'], | ||
}, | ||
ui: ['show', 'user'], | ||
}, | ||
}, | ||
}); | ||
|
||
// @ts-ignore | ||
const kbnServer = this.kbnServer; | ||
kbnServer.ready().then(async () => { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,7 +7,6 @@ | |
import { Server } from 'hapi'; | ||
// @ts-ignore | ||
import HapiAuthCookie from 'hapi-auth-cookie'; | ||
import sinon from 'sinon'; | ||
import { SecureRoute } from './security'; | ||
|
||
const createMockServer = async () => { | ||
|
@@ -26,9 +25,6 @@ const createMockServer = async () => { | |
|
||
const secureRoute = new SecureRoute(server); | ||
secureRoute.install(); | ||
// @ts-ignore | ||
const stub = sinon.stub(secureRoute, 'isSecurityEnabledInEs'); | ||
stub.returns(true); | ||
server.securedRoute({ | ||
method: 'GET', | ||
path: '/test', | ||
|
@@ -66,7 +62,7 @@ async function checkWithRoles(server: any, roles: any) { | |
return response; | ||
} | ||
|
||
it('should response 403 when roles check failed', async () => { | ||
it.skip('should response 403 when roles check failed', async () => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Skipping this test, but this entire suite should be revisited. |
||
const server = await createMockServer(); | ||
const response = await checkWithRoles(server, []); | ||
expect(response.statusCode).toBe(403); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,16 +4,12 @@ | |
* you may not use this file except in compliance with the Elastic License. | ||
*/ | ||
|
||
import Boom from 'boom'; | ||
import { Lifecycle, Request, ResponseToolkit, Server, ServerRoute } from 'hapi'; | ||
import { Server, ServerRoute, RouteOptions } from 'hapi'; | ||
|
||
export interface SecuredRoute extends ServerRoute { | ||
requireRoles?: string[]; | ||
requireAdmin?: boolean; | ||
} | ||
|
||
export const ADMIN_ROLE = 'code_admin'; | ||
|
||
declare module 'hapi' { | ||
interface Server { | ||
securedRoute(route: SecuredRoute): void; | ||
|
@@ -26,56 +22,23 @@ export class SecureRoute { | |
public install() { | ||
const self = this; | ||
function securedRoute(route: SecuredRoute) { | ||
if (route.handler) { | ||
const originHandler = route.handler as Lifecycle.Method; | ||
route.handler = async (req: Request, h: ResponseToolkit, err?: Error) => { | ||
if (self.isSecurityEnabledInEs()) { | ||
let requiredRoles = route.requireRoles || []; | ||
if (route.requireAdmin) { | ||
requiredRoles = requiredRoles.concat([ADMIN_ROLE]); | ||
} | ||
if (requiredRoles.length > 0) { | ||
if (!req.auth.isAuthenticated) { | ||
throw Boom.unauthorized('not login.'); | ||
} else { | ||
// @ts-ignore | ||
const userRoles = new Set(req.auth.credentials.roles || []); | ||
const authorized = | ||
userRoles.has('superuser') || | ||
requiredRoles.every((value: string) => userRoles.has(value)); | ||
if (!authorized) { | ||
throw Boom.forbidden('not authorized user.'); | ||
} | ||
} | ||
} | ||
} | ||
return await originHandler(req, h, err); | ||
}; | ||
} | ||
const routeOptions: RouteOptions = (route.options || {}) as RouteOptions; | ||
routeOptions.tags = [ | ||
...(routeOptions.tags || []), | ||
`access:code_${route.requireAdmin ? 'admin' : 'user'}`, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Tags the route with either |
||
]; | ||
|
||
self.server.route({ | ||
handler: route.handler, | ||
method: route.method, | ||
options: route.options, | ||
options: routeOptions, | ||
path: route.path, | ||
}); | ||
} | ||
|
||
// FIXME: don't attach to the server this way. Use server.decorate or similar. | ||
this.server.securedRoute = securedRoute; | ||
} | ||
|
||
private isSecurityEnabledInEs() { | ||
const xpackInfo = this.server.plugins.xpack_main.info; | ||
if (!xpackInfo.isAvailable()) { | ||
throw Boom.serverUnavailable('x-pack info is not available yet.'); | ||
} | ||
if ( | ||
!xpackInfo.feature('security').isEnabled() || | ||
!xpackInfo.feature('security').isAvailable() | ||
) { | ||
return false; | ||
} | ||
return true; | ||
} | ||
} | ||
|
||
export function enableSecurity(server: Server) { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -11,8 +11,8 @@ export class WithRequest { | |
public readonly callWithRequest: (endpoint: string, clientOptions?: AnyObject) => Promise<any>; | ||
|
||
constructor(readonly req: Request) { | ||
this.callWithRequest = req.server.plugins.elasticsearch | ||
.getCluster('data') | ||
.callWithRequest.bind(null, req); | ||
// FIXME: I swapped out `callWithRequest` for `callWithInternalUser` to test and verify functionality, but you'll want to refactor this since | ||
// the names no longer make sense. | ||
this.callWithRequest = req.server.plugins.elasticsearch.getCluster('data').callWithInternalUser; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Important In order to test my changes, I simply changed the call being made here from Either way, this will need refactoring, since it is no longer "WithRequest". A couple other notes:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Update: We should not blindly use
|
||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's likely that you don't need any of these
userProfile
actions anymore. I left it in place because I didn't feel comfortable refactoring. All you essentially need is theinitialState
above, however you wish to incorporate that into the application.