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

[New platform] HTTP & Security integration #34631

Merged
merged 39 commits into from
Apr 16, 2019

Conversation

mshustov
Copy link
Contributor

@mshustov mshustov commented Apr 5, 2019

Summary

Parent issue: #33775
Short recap of the issue:

Auth interceptor

Provide Authenticate hook for Security plugin to register incoming requests intercepter.
Request intercepter is meant to be registered only once.
Security plugin performs user authentication and authorization checks for incoming requests.
Authenticate may finish Auth operation with one of next outcomes:

  • authenticated() - to allow request to pass through
  • redirected() - to interrupt request handling and redirect to configured url
  • rejected() - to fail the request with specified error.

Third party plugins should have an access to (optional) Security plugin to register their access to authorization information.

Request interceptor

Provide onRequest hook for plugins to register custom logic for incoming requests. Request interceptor doesn't have access to hapi Request object, but KibanaRequest
onRequest may finish Auth operation with one of next outcomes:

  • next() - to pass request to the next handler
  • redirected() - to interrupt request handling and redirect to configured url
  • rejected() - to fail the request with specified error.

Checklist

Use strikethroughs to remove checklist items you don't feel are applicable to this PR.

For maintainers

@elasticmachine
Copy link
Contributor

💚 Build Succeeded

this.server.ext('onRequest', adoptToHapiOnRequestFormat(fn));
};

private registerAuth = async <T>(fn: Authenticate<T>, cookieOptions: CookieOptions) => {
Copy link
Contributor Author

@mshustov mshustov Apr 5, 2019

Choose a reason for hiding this comment

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

NOTE: right now security config defines CookieOptions values but doesn't specify session storage implementation as cookie-storage. Seems to be okay if we don't want to add premature abstraction, later we can change API

interface CookieSessionStorage {
  type: 'cookie'
  options: CookieOptions
}

interface RedisSessionStorage {
  type: 'redis'
  options: ...
}

type SessionStorageParams = CookieSessionStorage | RedisSessionStorage | ...

registerAuth (fn: Authenticate<T>, sessionStorageParams: SessionStorageParams){}

are we going to support this extension point compatibility for external users?

h: ResponseToolkit
): Promise<Lifecycle.ReturnValue> {
try {
const result = await fn(req, sessionStorage.asScoped(req), toolkit);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

note it does not support returning an error nor throwing an error from authenticate function

res.ok({ content: 'ok' })
);
// TODO fix me when registerRouter is available before HTTP server is run
(root as any).server.http.registerRouter(router);
Copy link
Contributor Author

@mshustov mshustov Apr 5, 2019

Choose a reason for hiding this comment

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

note right now we don't have a separation between setup and start phase, consequently, we don't provide any way to add route handler in userland. That seems to be a blocker because even Security cannot add their route handlers to perform login. we have to address that in following PRs

Copy link
Contributor

Choose a reason for hiding this comment

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

With the work going on in #18843, I think we'll be able to get the legacy code on the frontend more compatible with the lifecycle events in the new platform sooner than later. This would unblock adding back the start event.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I believe it's fine to introduce start only on the server side for the time being. That can be implemented without leaking lifecycle semantic to plugins:

  • the server runs setup for core services and plugins.
  • the server runs start for core services only (until [browser] Application service #18843 is ready).
  • the server runs legacy server.

@@ -127,4 +137,31 @@ export class HttpServer {
const routePathStartIndex = routerPath.endsWith('/') && routePath.startsWith('/') ? 1 : 0;
return `${routerPath}${routePath.slice(routePathStartIndex)}`;
}

private registerOnRequest = (fn: OnRequest) => {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

not directly related to Security integration directly, but interesting to check consistency in registerOnRequest and registerAuth API

Copy link
Contributor

Choose a reason for hiding this comment

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

Any reason this is defined as an arrow function instead of a class method?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

this function is exposed as a part of setup contract. Otherwise, we either have to bind it in setup method or expose an unbound version.

sessionStorage.set({ value: user, expires: Date.now() + 1000 });
return t.authenticated({ credentials: user });
} else {
return t.rejected(Boom.unauthorized());
Copy link
Contributor Author

@mshustov mshustov Apr 5, 2019

Choose a reason for hiding this comment

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

are we going to restrict Boom usage? there is an issue, but don't see any discussions #12947

export function createRootWithSettings(...settings: Array<Record<string, any>>) {
export function createRootWithSettings(
settings: Record<string, any>,
cliArgs: Partial<CliArgs> = {}
Copy link
Contributor Author

@mshustov mshustov Apr 5, 2019

Choose a reason for hiding this comment

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

I had to hack createRoot to be able to specify env vars because additional plugins can be used in dev mode only.
even more interesting that we run integration_tests in production mode, but functional tests in development. what is the difference? I'd expect dev/prod/testing/staging parity https://12factor.net/dev-prod-parity

Copy link
Member

Choose a reason for hiding this comment

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

Yeah, agree, we should unify environment these tests run in at some point.

@mshustov
Copy link
Contributor Author

mshustov commented Apr 5, 2019

@epixa @elastic/kibana-security @elastic/kibana-platform may I ask you about preliminary code review?

@elasticmachine
Copy link
Contributor

💚 Build Succeeded

@mshustov mshustov added Feature:New Platform 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.2.0 labels Apr 8, 2019
@elasticmachine
Copy link
Contributor

Pinging @elastic/kibana-platform

this.server.ext('onRequest', adoptToHapiOnRequestFormat(fn));
};

private registerAuth = async <T>(fn: Authenticate<T>, cookieOptions: CookieOptions<T>) => {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Right now we provide an access to the raw Hapi request object. Will be restricted specific data and methods in following PRs

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.

Thanks for the PR @restrry! I have a couple comments, but I don't think there's anything that'll prevent you from moving forward. I didn't do an in-depth review, as you only asked for a preliminary one :)

I know that registering routes is still a WIP, but something we're relying on for Feature Controls is the ability for routes to add tags. These tags inform the security plugin that specific authorization is required above and beyond the initial authentication. We don't necessarily need/want tags specifically going forward, but the ability for a request interceptor to inspect the current route config, and determine if any additional work needs to be done. See api_authorization.ts for an example of this.

I still encourage @azasypkin and/or @kobelb to take a look too, in case I'm overlooking anything.

src/core/server/http/http_server.ts Outdated Show resolved Hide resolved
src/core/server/http/session_storage.ts Outdated Show resolved Hide resolved
src/core/server/http/http_server.ts Outdated Show resolved Hide resolved

const sessionStorage = await createCookieSessionStorageFor<T>(this.server, cookieOptions);

this.server.auth.scheme('login', () => ({
Copy link
Member

Choose a reason for hiding this comment

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

I think hapi will complain if the same scheme is registered more than once. If hapi ends up throwing an error here, then this will be leaking hapi internals to the consumer. Might want to prevent this from being called more than once, and/or wrap the underlying errors.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yes, I'm going to add a check that it is called only once 👍

@mshustov
Copy link
Contributor Author

mshustov commented Apr 9, 2019

I know that registering routes is still a WIP, but something we're relying on for Feature Controls is the ability for routes to add tags. These tags inform the security plugin that specific authorization is required above and beyond the initial authentication. We don't necessarily need/want tags specifically going forward, but the ability for a request interceptor to inspect the current route config, and determine if any additional work needs to be done

that's why the whole hapi request is provided to authenticate function. Later we may restrict scope to only some fields

@elastic elastic deleted a comment from elasticmachine Apr 9, 2019
@mshustov
Copy link
Contributor Author

mshustov commented Apr 16, 2019

Will create separate issues to do in follow-ups:

possible improvements to discuss:

@elasticmachine
Copy link
Contributor

💔 Build Failed

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 w/ planned follow up PRs

request: Request,
sessionStorage: SessionStorage<T>,
t: AuthToolkit
) => Promise<AuthResult>;
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm going to introduce another mechanism to control this header in following PR.

SGTM, I didn't see the prior comment. We could probably construct KibanaRequest with a filtered set of headers for regular route handlers and only expose the Authenication header to AuthenticationHandlers.

@mshustov mshustov dismissed eliperelman’s stale review April 16, 2019 16:28

comments are addressed, Eli is on sick leave to re-review

@elasticmachine
Copy link
Contributor

💔 Build Failed

@mshustov
Copy link
Contributor Author

retest

@elasticmachine
Copy link
Contributor

💚 Build Succeeded

@mshustov mshustov merged commit a202232 into elastic:master Apr 16, 2019
@mshustov mshustov deleted the NP-security-integration branch April 16, 2019 18:47
mshustov added a commit to mshustov/kibana that referenced this pull request Apr 16, 2019
* Add Auth session

* add lifecycles

* add types for hapi-auth-cookie

* expose interceptors from http service

* add integration tests

* update tests

* session storage cleanup

* get SessionStorage type safe

* add redirect, clear cookie security integration tests

* add tests for onRequest

* add tests for onAuth

* register Auth interceptor only once

* refactor redirect tests

* fix typings, change error message, test suit naming

* add integration test for session validation

* add tests for cookie session storage

* update docs

* add integration tests for onRequest

* update docs

* cleanup onRequest integration tests

* Generate docs for AuthToolkit & OnRequestToolkit

* add test for an exception in interceptor

* add test OnRequest interceptors dont share request object

* cleanup

* address comments from @eli

* improve typings for onRequest

* improve plugin typings

* re-generate docs

* only server defines cookie path

* cookieOptions.password --> cookieOptions.encryptionKey

* CookieOption --> SessionStorageCookieOptions

* address comments @joshdover

* resolve conflict leftovers

* update @types/hapi-auth-cookie deps

* update docs
mshustov added a commit that referenced this pull request Apr 16, 2019
* Add Auth session

* add lifecycles

* add types for hapi-auth-cookie

* expose interceptors from http service

* add integration tests

* update tests

* session storage cleanup

* get SessionStorage type safe

* add redirect, clear cookie security integration tests

* add tests for onRequest

* add tests for onAuth

* register Auth interceptor only once

* refactor redirect tests

* fix typings, change error message, test suit naming

* add integration test for session validation

* add tests for cookie session storage

* update docs

* add integration tests for onRequest

* update docs

* cleanup onRequest integration tests

* Generate docs for AuthToolkit & OnRequestToolkit

* add test for an exception in interceptor

* add test OnRequest interceptors dont share request object

* cleanup

* address comments from @eli

* improve typings for onRequest

* improve plugin typings

* re-generate docs

* only server defines cookie path

* cookieOptions.password --> cookieOptions.encryptionKey

* CookieOption --> SessionStorageCookieOptions

* address comments @joshdover

* resolve conflict leftovers

* update @types/hapi-auth-cookie deps

* update docs
kobelb added a commit to kobelb/kibana that referenced this pull request Apr 22, 2019
With the introduction of elastic#34631
OSS Kibana now has a dependency on hapi-auth-cookie, but it's missing
from the package.json
kobelb added a commit that referenced this pull request Apr 22, 2019
With the introduction of #34631
OSS Kibana now has a dependency on hapi-auth-cookie, but it's missing
from the package.json
kobelb added a commit to kobelb/kibana that referenced this pull request Apr 22, 2019
With the introduction of elastic#34631
OSS Kibana now has a dependency on hapi-auth-cookie, but it's missing
from the package.json
kobelb added a commit that referenced this pull request Apr 22, 2019
With the introduction of #34631
OSS Kibana now has a dependency on hapi-auth-cookie, but it's missing
from the package.json
@epixa epixa added the release_note:plugin_api_changes Contains a Plugin API changes section for the breaking plugin API changes section. label Jun 5, 2019
@mshustov mshustov mentioned this pull request Jun 5, 2019
3 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Feature:New Platform release_note:plugin_api_changes Contains a Plugin API changes section for the breaking plugin API changes section. review 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.2.0
Projects
None yet
Development

Successfully merging this pull request may close these issues.

9 participants