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

Move base feature controls functionality from XPack Main plugin to a dedicated XPack Features plugin #44664

Merged
merged 10 commits into from
Sep 9, 2019

Conversation

azasypkin
Copy link
Member

@azasypkin azasypkin commented Sep 3, 2019

We're moving Security Authorization system to the New Platform and it depends on the "features" functionality. To minimize a number of dependencies on the Legacy Platform plugins we're introducing "Features" plugin that will solely responsible for the base Feature Controls functionality.

NOTE: it's easier to review this PR commit by commit because of git mv. Not much of new code is written here, I just moved some code and split some large files into smaller ones.

Blocked by: #44855

@azasypkin azasypkin added Team:Core Core services & architecture: plugins, logging, config, saved objects, http, ES client, i18n, etc Feature:New Platform Team:Security Team focused on: Auth, Users, Roles, Spaces, Audit Logging, and more! Feature:Security/Feature Controls Platform Security - Spaces & Role Mgmt feature controls v7.5.0 labels Sep 3, 2019
@azasypkin azasypkin requested review from a team as code owners September 3, 2019 14:43
@elasticmachine
Copy link
Contributor

Pinging @elastic/kibana-platform

@elasticmachine
Copy link
Contributor

Pinging @elastic/kibana-security

@azasypkin azasypkin added the release_note:skip Skip the PR/issue when compiling release notes label Sep 3, 2019
@@ -38,6 +38,7 @@
/src/legacy/server/saved_objects/ @elastic/kibana-platform
/src/legacy/ui/public/saved_objects @elastic/kibana-platform
/config/kibana.yml @elastic/kibana-platform
/x-pack/plugins/features/ @elastic/kibana-platform
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

note: Platform team will own this plugin as soon as we merge it.

@@ -12,11 +12,11 @@ Note: You cannot access this endpoint via the Console in {kib}.
===== Request

To retrieve all features, issue a GET request to the
/api/features/v1 endpoint.
/features/api endpoint.
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

note: it feels like not a big deal to change URL in a minor for this particular API.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree, this is a negligible change. Is the /${pluginId}/api format a new standard? I know that endpoints have to be prefixed with the plugin id as part of the NP migration, but the /api suffix feels strange to me. Reading this, I'd expect this to retrieve a feature with id api, instead of retrieving all registered features.

We had a similar problem when we wrote the spaces api, but I'm not thrilled with what we did there either...

/api/spaces/space retrieves all spaces
/api/spaces/space/foo retrieves a space with id foo.

We don't need the latter for this API call, since we don't support retrieving a single feature by id. If we follow the spaces pattern though (updating for the NP), we get something like:

/features/feature

...which isn't great either.

Maybe
/features/api/feature?

I'm more than open to other suggestions

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the /${pluginId}/api format a new standard?

It's not clear yet, that's why I'd like to discuss it here and in #44620. I need to introduce NP routes in a couple PRs right now and was thinking whether we should separate API and view routes somehow, that's how this /api/ part appeared.

I'd expect this to retrieve a feature with id api, instead of retrieving all registered features.

Heh, that's a good point!

Maybe /features/api/feature?

Yeah, or something verbose like /features/api/all-features, /features/api/feature/{id}, still thinking, don't have good enough ideas yet....

I was also thinking about features/_api/, _features/api/, features.api/ to kind of stress on the difference between these two semantically different parts of the URL. We can also say that features/* are always API calls and features/{views|app|pages}/* are for {views|app|pages}.

@restrry it's probably a good time to pick some API naming style that we can follow :) Did you guys had a chance to discuss that already?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Once #44855 is merged I'll change URL from /api/features/v1 to /api/features.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed to /api/features

@@ -0,0 +1,8 @@
{
Copy link
Member Author

@azasypkin azasypkin Sep 3, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

note: we need to get access to the timelion.ui.enabled setting from the Features plugin so introducing NP timelion plugin felt natural to me, but I'm open to any better ideas!

"id": "features",
"version": "8.0.0",
"kibanaVersion": "kibana",
"optionalPlugins": ["timelion"],
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

note: just because we want to know value of timelion.ui.enabled.

export class Plugin {
private readonly logger: Logger;

private legacyAPI?: LegacyAPI;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

note: same approach we used in NP Security plugin, but happy to hear any better suggestions.

return deepFreeze({
registerFeature: featureRegistry.register.bind(featureRegistry),
getFeatures: featureRegistry.getAll.bind(featureRegistry),
getFeaturesUICapabilities: () => uiCapabilitiesForFeatures(featureRegistry.getAll()),
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: I expose this just because it's used in uiCapabilities method of the XPack Main Plugin definition. Josh do we have anything like this in NP world so that I can use it here and don't expose to LP?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On the server-side, the NP doesn't have anything related to UI capabilities yet.

The ApplicationService on the client-side does currently expose the injected ui capabilities for the current user. We will change this to fetch UI capabilities (#36319) based on the registered applications once security's HTTP interceptors are moved over.

Whenever the fetch endpoint for this is moved over to the New Platform, I expect the New Platform will need to expose a way for this plugin to inject additional capabilities. We can make that change at a later time.

"common.ui.savedObjects.howToSaveAsNewDescription": "Kibana の以前のバージョンでは、{savedObjectName} の名前を変更すると新しい名前でコピーが作成されました。今後この操作を行うには、「新規 {savedObjectName} として保存」を使用します。",
"common.ui.savedObjects.overwriteRejectedDescription": "上書き確認が拒否されました",
"common.ui.savedObjects.saveAsNewLabel": "新規 {savedObjectName} として保存",
"common.ui.savedObjects.saveDuplicateRejectedDescription": "重複ファイルの保存確認が拒否されました",
"kibana-react.savedObjects.saveModal.cancelButtonLabel": "キャンセル",
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

note: this has been automatically rearranged by i18n script.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be worthwhile to find/replace xpack.main.featureRegistry.* with xpack.features.* in the translated files, since we know that the translated text doesn't need to change? Or is that not advised?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We never did this since translators are supposed to receive en.json for every release, and in this case these labels will be recognized as already translated even though IDs changed (so that we're not charged additionally). But let's double check with @Bamieh

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently we send a json diff file in english and one for every language we have, so renaming these ID's will be synced with translators. We started doing this to sync translations introduced in PRs as we used to ignore them.

I'd vote for renaming these ids as it will also help keep kibana translated even before we sync with the translators which only happens a few days before every FF release.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, will do that than. I usually advised against manual edits in files with translations and treated them almost as "auto-generated" as it's easy to make something wrong there :)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

if (!request.path.startsWith('/api/') || !mode.useRbacForRequest(request)) {
// if the api doesn't include or end with "/api/" or we aren't using RBAC for this request,
// just continue
if (
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

note: temp fix, we need to discuss what to do here with plugin-id prefixed NP API routes.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm trying to remember why we had the startsWith('/api/') check to begin with...I don't think it's necessary for us to check for that path part in addition to the existence of the access:foo tag. We should support securing any route with an appropriately configured access: tag, even if it doesn't have /api/ within its path.

This may have just been a holdover from an earlier implementation for securing API endpoints - @kobelb do you remember differently?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reverted this change since we can keep /api/features now.

@elasticmachine
Copy link
Contributor

💔 Build Failed

@elasticmachine
Copy link
Contributor

💚 Build Succeeded

@azasypkin
Copy link
Member Author

@joshdover @legrego would really appreciate your feedback on this PR (will tag you for review)!

@legrego
Copy link
Member

legrego commented Sep 3, 2019

ACK will review today/tomorrow

Copy link
Member

@legrego legrego left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Made a first pass at the code today (mostly questions) -- I'll test this out some more tomorrow, but I wanted to give you something to look at before my day starts

@@ -12,11 +12,11 @@ Note: You cannot access this endpoint via the Console in {kib}.
===== Request

To retrieve all features, issue a GET request to the
/api/features/v1 endpoint.
/features/api endpoint.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree, this is a negligible change. Is the /${pluginId}/api format a new standard? I know that endpoints have to be prefixed with the plugin id as part of the NP migration, but the /api suffix feels strange to me. Reading this, I'd expect this to retrieve a feature with id api, instead of retrieving all registered features.

We had a similar problem when we wrote the spaces api, but I'm not thrilled with what we did there either...

/api/spaces/space retrieves all spaces
/api/spaces/space/foo retrieves a space with id foo.

We don't need the latter for this API call, since we don't support retrieving a single feature by id. If we follow the spaces pattern though (updating for the NP), we get something like:

/features/feature

...which isn't great either.

Maybe
/features/api/feature?

I'm more than open to other suggestions

import { first } from 'rxjs/operators';
import { TypeOf } from '@kbn/config-schema';
import { PluginInitializerContext, RecursiveReadonly } from '../../../../src/core/server';
import { deepFreeze } from '../../../../src/core/utils';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't know we could import from src/core/utils. Last time I tried within x-pack, it was disallowed/not working. Is this something we support for x-pack plugins too, or is it just for OSS plugins?

Copy link
Member Author

@azasypkin azasypkin Sep 4, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this something we support for x-pack plugins too, or is it just for OSS plugins?

We use it for Security plugin already, so it should be supported: https://github.com/elastic/kibana/blob/master/x-pack/plugins/security/server/plugin.ts#L16

* you may not use this file except in compliance with the Elastic License.
*/

import Joi from 'joi';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question: Is joi still the validation framework of choice, or should we be using @kbn/config-schema?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just kept what we had before, but I believe we should eventually migrate to @kbn/config-schema assuming everything we have in these schemas is supported by @kbn/config-schema

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I think it's fine if we change this later.


import Joi from 'joi';

// Each feature gets its own property on the UICapabilities object,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I think this comment belongs with line 15 below.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ha, right, thanks!

}

/**
* Describes a set of APIs that is available in the legacy platform only and required by this plugin
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit

Suggested change
* Describes a set of APIs that is available in the legacy platform only and required by this plugin
* Describes a set of APIs that are available in the legacy platform only and required by this plugin

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed! Out of curiosity and to improve my grammar skills: does *set* .... that *is* .... sound too unnatural comparing to set of *things* that *are*? :)

Logger,
PluginInitializerContext,
RecursiveReadonly,
} from '../../../../src/core/server';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we be using the from 'src/core/server' alias instead of the relative import?

Copy link
Member Author

@azasypkin azasypkin Sep 4, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe I heard somewhere that we should use relative paths instead of this alias when I was working Security NP plugin since it's exactly same approach we'd use to import from other plugins, but maybe something has changed since then. @elastic/kibana-platform what would be the correct approach to import from the core?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah ok, I'm interested to see what Platform says here. I'm ok with relative imports if that's the direction we want to go, but third-party plugins wouldn't be able to take advantage of that, right?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see the relevant discussion is still happening here: #40446 (comment)

registerLegacyAPI: (legacyAPI: LegacyAPI) => {
this.legacyAPI = legacyAPI;

// Register OSS features.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we registering OSS features here because we need the saved object types from the legacy API? It was a little confusing when I first read this, as it feels out of place right now. I'm OK leaving it as-is since it's temporary.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we registering OSS features here because we need the saved object types from the legacy API?

Yep. And it's confusing, definitely agree. Initially I had additional setup contract registerOSSFeatures method, but it was also confusing as "something" that is outside of Features control is supposed to initialize Features plugin at "some" point 🤷‍♂️

Hopefully we'll get SO on server side in NP soon and get rid of this hack.

"common.ui.savedObjects.howToSaveAsNewDescription": "Kibana の以前のバージョンでは、{savedObjectName} の名前を変更すると新しい名前でコピーが作成されました。今後この操作を行うには、「新規 {savedObjectName} として保存」を使用します。",
"common.ui.savedObjects.overwriteRejectedDescription": "上書き確認が拒否されました",
"common.ui.savedObjects.saveAsNewLabel": "新規 {savedObjectName} として保存",
"common.ui.savedObjects.saveDuplicateRejectedDescription": "重複ファイルの保存確認が拒否されました",
"kibana-react.savedObjects.saveModal.cancelButtonLabel": "キャンセル",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be worthwhile to find/replace xpack.main.featureRegistry.* with xpack.features.* in the translated files, since we know that the translated text doesn't need to change? Or is that not advised?

// functions or removal of exports should be considered as a breaking change. Ideally we should
// reduce number of such exports to zero and provide everything we want to expose via Setup/Start
// run-time contracts.
export { uiCapabilitiesRegex } from './feature_schema';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For what it's worth, we should probably move uiCapabilitiesRegex into src/core, as UI Capabilities are a part of OSS now. You can leave it here for the time being, but if we're concerned about API surface, that's one way to reduce it 😄.

question: your comment about reducing these exports to zero doesn't include the types we export below, right? We can still export types from our plugins outside of the lifecycle methods?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For what it's worth, we should probably move uiCapabilitiesRegex into src/core, as UI Capabilities are a part of OSS now. You can leave it here for the time being, but if we're concerned about API surface, that's one way to reduce it

Yeah, I tried to do that several times while working on this, but failed to do so every time 😆 I just can't find a proper place for it, it was kind of confusing that it sits in the core, but is not used there at all and it's not clear how exactly it's used while reading core code. If you can help me to find a justification I'll happily move that to the core 🙂

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: your comment about reducing these exports to zero doesn't include the types we export below, right? We can still export types from our plugins outside of the lifecycle methods?

Correct!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I tried to do that several times while working on this, but failed to do so every time 😆 I just can't find a proper place for it, it was kind of confusing that it sits in the core, but is not used there at all and it's not clear how exactly it's used while reading core code. If you can help me to find a justification I'll happily move that to the core 🙂

It doesn't exist today, but I could see this being useful when we build the list of capabilities. Currently, plugins can provide any capabilities they'd like, even if they violate the regex. We're just trusting that they don't right now.

It's potentially also useful when we resolve capabilities. Core might want to prevent registered Capabilities Modifiers from returning a capability which violates the regex. In theory, the modifiers shouldn't be adding or removing any capabilities; rather, they should just be flipping the boolean flags...

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, good to know, thanks. Yeah, it makes a lot of sense to have it in core then.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved to the core.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nah, had to revert and move back to the Features plugin. There is something weird happening when we require regex from ui/capabilities in server code. It's probably expected, but I'll let platform team to handle that when they want to move that to core :)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for trying!

@azasypkin
Copy link
Member Author

@joshdover @legrego thanks for the feedback! I think I've handled everything you mentioned so far, so PR is ready for another pass.

@elasticmachine

This comment has been minimized.

@elasticmachine
Copy link
Contributor

💔 Build Failed

@elasticmachine
Copy link
Contributor

💚 Build Succeeded

Copy link
Member

@legrego legrego left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Everything seems to be working great! Just a doc update needed from my perspective.

@@ -142,7 +142,7 @@ init(server) {
const xpackMainPlugin = server.plugins.xpack_main;
xpackMainPlugin.registerFeature({
id: 'dev_tools',
name: i18n.translate('xpack.main.featureRegistry.devToolsFeatureName', {
name: i18n.translate('xpack.features.devToolsFeatureName', {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file has a few references to the legacy filepath that we'll want to update:

Registering a feature consists of the following fields. For more information, consult the {repo}blob/{branch}/x-pack/legacy/plugins/xpack_main/server/lib/feature_registry/feature_registry.ts[feature registry interface].

|{repo}blob/{branch}/x-pack/legacy/plugins/xpack_main/server/lib/feature_registry/feature_registry.ts[`FeatureWithAllOrReadPrivileges`].

For a full explanation of fields and options, consult the {repo}blob/{branch}/x-pack/legacy/plugins/xpack_main/server/lib/feature_registry/feature_registry.ts[feature registry interface].

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch!

Copy link
Member

@legrego legrego left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM on green CI. Thanks!

@elasticmachine
Copy link
Contributor

💔 Build Failed

@azasypkin
Copy link
Member Author

14:55:32 │ warn Failure loading service "webdriver"
14:55:32 │ERROR Error: Server terminated early with status 1

Looks like unrelated issue.

@azasypkin
Copy link
Member Author

retest

@elasticmachine
Copy link
Contributor

💚 Build Succeeded

Copy link
Contributor

@joshdover joshdover left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, thanks @azasypkin!

@elasticmachine
Copy link
Contributor

💚 Build Succeeded

@azasypkin
Copy link
Member Author

7.x/7.5.0: 6341714

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
backported Feature:New Platform Feature:Security/Feature Controls Platform Security - Spaces & Role Mgmt feature controls release_note:skip Skip the PR/issue when compiling release notes Team:Core Core services & architecture: plugins, logging, config, saved objects, http, ES client, i18n, etc Team:Security Team focused on: Auth, Users, Roles, Spaces, Audit Logging, and more! v7.5.0
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants