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

introduce pre-/post-auth request hooks for HttpServer #36690

Merged
merged 21 commits into from
May 29, 2019

Conversation

mshustov
Copy link
Contributor

@mshustov mshustov commented May 20, 2019

Summary

I'm back to the problem. After talking with the Security team we figured out that several lifecycle stages are required.

  • a stage that is performed before auth - onPreAuth. Specificity: can perform request forwarding to another URL, before auth is performed and request hits a route handler. Analog in the LP - server.ext('onRequest',...). There is only one consumer as of now. But some extension points in LP can be moved here as well. one, two
  • auth stage. Specificity: can perform authc/authz logic and store user credentials in memory. State passed to t.authenticated in registerAuth
await registerAuth((req, sessionStorage, t) => t.authenticated(state), cookieOptions);

can be retrieved later as httpSetup.auth.get(request) => {state, status} in both New and Legacy platforms

  • a stage that is performed after auth - onPostAuth. Specificity: has access to auth status. Analog in the LP - server.ext('onPostAuth',...).
  • route can disable auth mechaism
const router = new Router('');
router.get({ path: '/', validate: false, authRequired: false }, handler);
httpSetup.registerRouter(router);

Checklist

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

For maintainers

Dev Docs

Kibana let define custom request interceptors for incoming requests:

  • http.registerAuth(handler, cookieOptions) => Promise<void>
    To define custom authentication and/or authorization mechanism for incoming requests.
    A handler should return a state to associate with the incoming request.
    The state can be retrieved later via http.auth.get(..). Only one AuthenticationHandler can be registered.
  • http.registerOnPreAuth(handler) => void
    To define custom logic to perform for incoming requests.
    Runs the handler before Auth hook performs a check that user has access to requested resources, so it's the only place when you can forward a request to another URL right on the server.
    Can register any number of registerOnPostAuth, which are called in sequence (from the first registered to the last).
  • http.registerOnPreAuth (handler) => void
    To define custom logic to perform for incoming requests. Runs the handler after Auth hook did make sure a user has access to the requested resource.
    The auth state is available at stage via http.auth.get(..)
    Can register any number of registerOnPreAuth, which are called in sequence (from the first registered to the last).

@mshustov
Copy link
Contributor Author

@legrego would you mind to have a look if it's sufficient to cover Security & Spaces use cases?

/** @internal */
class OnRequestResult {
class OnPostAuthResult {
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 not sure if it's out of scope for this PR or not, but the Spaces onPostAuth interceptor also needs to be able to render a hidden app via request takeover.

I'm not sure if the concept of "hidden applications" has been thought through for the new platform or not (@joshdover?), but we'll need equivalent functionality for both Security and Spaces

Copy link
Contributor Author

@mshustov mshustov May 20, 2019

Choose a reason for hiding this comment

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

I'm not sure if the concept of "hidden applications" has been thought through for the new

No. Although, I think it's not a blocker. All Spaces / Security logic that is a blocker for NP plugin migration should be moved to NP asap. Remaining parts still have access to API in NP and we can postpone polishing for a bit. In this case that rendering logic can be expressed as :

const spaces = await newPlatform.plugins.spaces.client.asScoped(request).getAll();
if(spaces.length === 1) ...
if(spaces.length > 0) ...

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 I agree with that, but we might need additional work to support dashboard-only-mode's interceptor) in the LP.

It's relying on "auth scopes" to determine when to render its hidden app, but auth scopes are identifiers attached to the request.auth.credentials object. Will NP's http service allow direct access to such properties in the LP once security moves to NP?

Copy link
Member

Choose a reason for hiding this comment

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

@elastic/kibana-security Do we have a desire to keep the “auth scopes” used for dashboard-only-mode? I’m working with Mikhail to make sure the new platform has the necessary functionality to replace onRequest and onPostAuth. We want to move the authentication logic to the NP ASAP to unblock other plugins from migrating, but part of the auth logic is the concept of scopes, which are used by dashboard only mode in the legacy platform. We don’t have a solution for rendering hidden apps yet (for dashboard mode), and we don’t want to block migrating authc on that if we don’t have to.

Mikhail suggested adding a service in the NP that would allow plugins to associate arbitrary data with a particular request, which would also extend to the LP. I'm thinking we could use that to store the list of roles post-auth in NP, which dashboard-only-mode could use in the LP to determine if it should render its hidden app or not.

It's effectively the same logic, just without the concept of auth scopes. Dasbhoard-only-mode is the only consumer of the auth scopes service, and AFAIK, we don't have plans to use it elsewhere in the near future. It would be one less service to migrate to the NP, too. One of the goals in the NP is to restrict access to auth data server-side. Currently, any plugin can inspect the request headers and auth details, but Mikhail is working on abstracting that out so that plugins don't require direct access anymore.

Copy link
Contributor

Choose a reason for hiding this comment

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

Removing "auth scopes" seems entirely reasonable to me, as long as we can emulate the existing behavior of dashboard-only-mode using the mechanism that you've described. I'm not a huge fan of the way that Hapi does it's auth rules, so I'd much prefer we use our own abstractions.

Copy link
Contributor Author

@mshustov mshustov May 22, 2019

Choose a reason for hiding this comment

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

We can extend core API:

  • registerAuth already receive credentials which we can store internally
const authenticate: AuthenticationHandler = async (req, sessionStorage, t) => {
 if (req.headers.authorization) {
    sessionStorage.set({ value: user, expires: Date.now() + sessionDurationMs });
    return t.authenticated({ credentials: user });
  } else {
    return t.rejected(Boom.unauthorized());
  }
};
  • add a method that returns passed credentials and auth status
interface Auth {
  status: 'authenticated' | 'unauthenticated' | 'unknown';
  credentials: unknown;
}
HttpCoreSetup {
  ...
  getAuth: (request: KibanaRequest | Request, basePath: string) => Auth
}

Oleg's comment regarding statuses #34631 (comment)

@kobelb what do you think about this api? or you have another abstraction in mind?

Copy link
Contributor

Choose a reason for hiding this comment

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

Looks good to me!

@elasticmachine
Copy link
Contributor

💔 Build Failed

@mshustov
Copy link
Contributor Author

@elastic/kibana-security I also just realized that when we move Security to Ne platform we need to find a solution to set a cookie from Legacy.
Now if a request is not handled on NP, it is proxied to LP. Therefore, the response doesn't contain any headers (including set-cookie) set on NP.

@mshustov mshustov force-pushed the issue-33775-introduce-pre-post-auth branch from caef93a to e0a607b Compare May 23, 2019 11:46
@legrego
Copy link
Member

legrego commented May 23, 2019

I also just realized that when we move Security to Ne platform we need to find a solution to set a cookie from Legacy.
Now if a request is not handled on NP, it is proxied to LP. Therefore, the response doesn't contain any headers (including set-cookie) set on NP.

I'll keep that in mind when I start the authc migration. I might be able to use the getAuth function you proposed here to implement this in the LP.

@mshustov
Copy link
Contributor Author

@legrego signed cookie is set via sessionStorage.set(). so you don't have direct access to it.
this problem is not a blocker for Spaces. is it? we can elaborate on this part later

@legrego
Copy link
Member

legrego commented May 23, 2019

signed cookie is set via sessionStorage.set(). so you don't have direct access to it. this problem is not a blocker for Spaces. is it? we can elaborate on this part later

I was thinking the security plugin could maintain a parallel implementation for LP -- essentially keeping what it has today for session management in the case of requests proxied to LP.

I honestly haven't given this a ton of thought yet, so I might be wildly off base. Either way, this isn't a blocker for the first phase of spaces migration.

@elasticmachine
Copy link
Contributor

💔 Build Failed

@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! release_note:skip Skip the PR/issue when compiling release notes v7.3.0 labels May 23, 2019
@elasticmachine
Copy link
Contributor

Pinging @elastic/kibana-platform

@mshustov mshustov marked this pull request as ready for review May 23, 2019 14:59
@mshustov mshustov requested a review from a team as a code owner May 23, 2019 14:59
@elasticmachine
Copy link
Contributor

💔 Build Failed

for (const router of this.registeredRouters) {
for (const route of router.getRoutes()) {
const isAuthRequired = Boolean(this.authRegistered && route.authRequired);
Copy link
Contributor Author

@mshustov mshustov May 24, 2019

Choose a reason for hiding this comment

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

are we fine to introduce authRequired flag as unprotected route definition?

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 that's reasonable, as long as the default value is true

@mshustov mshustov requested review from kobelb and legrego and removed request for kobelb May 27, 2019 13:51
registerAuth: <T>(
fn: AuthenticationHandler<T>,
cookieOptions: SessionStorageCookieOptions<T>
) => this.registerAuth(fn, cookieOptions, config.basePath),
getBasePathFor: this.getBasePathFor.bind(this, config),
setBasePathFor: this.setBasePathFor.bind(this),
auth: {
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: HttpServer in the current state is mix of infrastructure layer & application layer logic, we can restructure it later, as soon as finish with #36804 to avoid conflicts

@elasticmachine
Copy link
Contributor

💔 Build Failed

@elasticmachine
Copy link
Contributor

💚 Build Succeeded

@legrego
Copy link
Member

legrego commented May 28, 2019

meantime we can add onResponse hook to New Platform that allows us to redirect to an LP route, where page can be served from. I outlined PR here restrry/kibana@issue-33775-introduce-pre-post-auth...restrry:pre-response-handler

meantime we can add onResponse hook to New Platform that allows us to redirect to an LP route, where page can be served from. I outlined PR here restrry/kibana@issue-33775-introduce-pre-post-auth...restrry:pre-response-handler

I think I'd prefer the second option, where NP delegates to LP for rendering in this case. I'm totally fine making that a different PR

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.

Design looks good to me, only really have nitpicky comments for ya.

src/core/server/http/http_server.ts Show resolved Hide resolved
src/core/server/http/lifecycle/on_post_auth.ts Outdated Show resolved Hide resolved
src/core/server/http/lifecycle/on_pre_auth.ts Outdated Show resolved Hide resolved

/** @public */
export function adoptToHapiAuthFormat<T = any>(
fn: AuthenticationHandler<T>,
sessionStorage: SessionStorageFactory<T>
sessionStorage: SessionStorageFactory<T>,
onSuccess: (req: Request, state: unknown) => void = noop
Copy link
Contributor

Choose a reason for hiding this comment

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

Any practical reason to widen the type of state from object to unknown here? I'd expect them to match.

Copy link
Contributor Author

@mshustov mshustov May 29, 2019

Choose a reason for hiding this comment

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

they aren't. state contain authorization information which shouldn't be cached in cookies.
that was discussed with @legrego in DM

Right now, the cookie only stores authentication information, and we delegate all authorization to Elasticsearch at “runtime”. Storing this info in a cookie would mean caching authorization details, which we don’t want to do.

we can add another generic type to specify onSuccess allowed type.
but this require some hassle to pass type declaration to https://github.com/restrry/kibana/blob/b529a1ae1c6198d0c90125bcb01309e658ae34bf/src/core/server/http/http_server.ts#L131

so I decided to handle it as a block box for core

@mshustov mshustov added release_note:plugin_api_changes Contains a Plugin API changes section for the breaking plugin API changes section. and removed release_note:skip Skip the PR/issue when compiling release notes labels May 29, 2019
@elasticmachine
Copy link
Contributor

💚 Build Succeeded

@mshustov mshustov merged commit b0c0165 into elastic:master May 29, 2019
jkakavas pushed a commit to jkakavas/kibana that referenced this pull request May 30, 2019
* introduce pre-,post-auth stages

* cleanup integration_tests. now contracts available in tests

* auth per route is configurable

* Unify lifecycle results structure

* expose api to store auth state and status via http service

* update tests

* update docs

* use full name, auth should not mutate request

* move basePath functionality under namespace

* regenerate docs

* Revert "move basePath functionality under namespace"

This reverts commit 9599d32.

* Revert "regenerate docs"

This reverts commit 1799d3b.

* regenerate docs

* updated yarn.lock no idea why

* extract AuthStateStorage to a separate entity

* get rid of nested ifs

* describe what is the difference between hooks

* re-wording
mshustov added a commit to mshustov/kibana that referenced this pull request Jun 6, 2019
* introduce pre-,post-auth stages

* cleanup integration_tests. now contracts available in tests

* auth per route is configurable

* Unify lifecycle results structure

* expose api to store auth state and status via http service

* update tests

* update docs

* use full name, auth should not mutate request

* move basePath functionality under namespace

* regenerate docs

* Revert "move basePath functionality under namespace"

This reverts commit 9599d32.

* Revert "regenerate docs"

This reverts commit 1799d3b.

* regenerate docs

* updated yarn.lock no idea why

* extract AuthStateStorage to a separate entity

* get rid of nested ifs

* describe what is the difference between hooks

* re-wording
mshustov added a commit that referenced this pull request Jun 7, 2019
…38265)

* introduce pre-/post-auth request hooks for HttpServer (#36690)

* introduce pre-,post-auth stages

* cleanup integration_tests. now contracts available in tests

* auth per route is configurable

* Unify lifecycle results structure

* expose api to store auth state and status via http service

* update tests

* update docs

* use full name, auth should not mutate request

* move basePath functionality under namespace

* regenerate docs

* Revert "move basePath functionality under namespace"

This reverts commit 9599d32.

* Revert "regenerate docs"

This reverts commit 1799d3b.

* regenerate docs

* updated yarn.lock no idea why

* extract AuthStateStorage to a separate entity

* get rid of nested ifs

* describe what is the difference between hooks

* re-wording

* fix typings
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.3.0
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants