-
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
introduce pre-/post-auth request hooks for HttpServer #36690
introduce pre-/post-auth request hooks for HttpServer #36690
Conversation
@legrego would you mind to have a look if it's sufficient to cover Security & Spaces use cases? |
/** @internal */ | ||
class OnRequestResult { | ||
class OnPostAuthResult { |
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.
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
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.
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) ...
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.
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?
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.
@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.
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.
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.
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.
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?
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.
Looks good to me!
💔 Build Failed |
@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. |
caef93a
to
e0a607b
Compare
I'll keep that in mind when I start the authc migration. I might be able to use the |
@legrego signed cookie is set via |
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. |
💔 Build Failed |
Pinging @elastic/kibana-platform |
💔 Build Failed |
for (const router of this.registeredRouters) { | ||
for (const route of router.getRoutes()) { | ||
const isAuthRequired = Boolean(this.authRegistered && route.authRequired); |
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.
are we fine to introduce authRequired
flag as unprotected route definition?
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.
I think that's reasonable, as long as the default value is true
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: { |
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.
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
💔 Build Failed |
💚 Build Succeeded |
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 |
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.
Design looks good to me, only really have nitpicky comments for ya.
|
||
/** @public */ | ||
export function adoptToHapiAuthFormat<T = any>( | ||
fn: AuthenticationHandler<T>, | ||
sessionStorage: SessionStorageFactory<T> | ||
sessionStorage: SessionStorageFactory<T>, | ||
onSuccess: (req: Request, state: unknown) => void = noop |
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.
Any practical reason to widen the type of state
from object to unknown
here? I'd expect them to match.
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.
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
💚 Build Succeeded |
* 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
* 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
…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
Summary
I'm back to the problem. After talking with the Security team we figured out that several lifecycle stages are required.
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, twot.authenticated
inregisterAuth
can be retrieved later as
httpSetup.auth.get(request) => {state, status}
in both New and Legacy platformsonPostAuth
. Specificity: has access to auth status. Analog in the LP -server.ext('onPostAuth',...)
.Checklist
Use
strikethroughsto 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).