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

NP Security HTTP Interceptors #39477

Merged
merged 54 commits into from Oct 24, 2019
Merged

Conversation

kobelb
Copy link
Contributor

@kobelb kobelb commented Jun 21, 2019

The following implements a very rudimentary redirect whenever we receive a 401 response. This does not currently address two exceptional situations which are in the LP, where we ignore the 401s:

  1. We have a hard-coded list of API regexs which we always ignore 401s from
    '/api/security/v1/login',
    '/api/security/v1/users/.*/password'
  2. We have a hard-coded list of page URLs which we explicitly ignore all 401s on
    return path === '/login' || path === '/logout' || path === '/logged_out' || path === '/status';

With regard to the hard-coded APIs, we're using this for two endpoints "login" and "change password". For login, what if we were to add an option to the NP http service to allow consumers to specify credentials: 'omit' as an option. That way when we see a 401 from a request where we're using credentials: 'omit' we just don't do the redirect. We don't really want or need the cookie sent to the login endpoint, because they'll be getting a new cookie on a successful login. For the change password endpoint, I think we can just change the 401 we return when the user tries to change their own password and doesn't provide the correct old password to a 403. You're forbidden from changing your password if you don't know the old password. I don't really understand the historical reasons for returning a 401 here in the first place.

With regard to the page URLs, these are consumed a number of places. They're generally used to ensure that the end-user is logged in before doing things like showing telemetry banners, or polling for reports, etc. This one I don't have a great answer for. All of the pages except for "/status" are security pages and the consumers are in x-pack. The security plugin could potentially expose an API to allow consumers to determine the user is currently on an "unauthenticated page". I'm open to alternatives here.

Resolves #48858

Depends on new functionality #41990
Blocked by bugs #42990 #42311 #42307

@kobelb kobelb added the Team:Security Team focused on: Auth, Users, Roles, Spaces, Audit Logging, and more! label Jun 21, 2019
@elasticmachine
Copy link
Contributor

Pinging @elastic/kibana-security

@kobelb kobelb added the Feature:Security/Authentication Platform Security - Authentication label Jun 21, 2019
@elasticmachine
Copy link
Contributor

💔 Build Failed

@elasticmachine
Copy link
Contributor

💔 Build Failed

@elasticmachine
Copy link
Contributor

💔 Build Failed

@elasticmachine
Copy link
Contributor

💔 Build Failed

@elasticmachine
Copy link
Contributor

💔 Build Failed

@elasticmachine
Copy link
Contributor

💔 Build Failed

@elasticmachine
Copy link
Contributor

💔 Build Failed

@elasticmachine
Copy link
Contributor

💔 Build Failed

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

import { BasePath } from 'src/core/public/http/base_path_service';
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@azasypkin @legrego how do you all feel about us writing tests using the NP core services, instead of mocking them out? When it's painful, or impossible, to use a concrete instance, I definitely think it makes sense to use a mock. However, in situations like these it really isn't that painful, and seems to add a level of additional confidence that the code actually works.

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 in favor of that approach. I prefer concrete instances when it's not too painful to create or maintain, for the same reasons as you.

Copy link
Member

Choose a reason for hiding this comment

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

Both approaches sound good to me too at this stage, but only because of additional confidence that the code actually works point. I'm afraid using real services may not scale very well and we'll end up with a mock in one test and real service in another.

The issue here is that not all core services are exposed to plugins as is, moreover they may have different shape for different life-cycle events: what you can do with HTTP service in setup may be drastically different from what you can do with it in start (e.g. this or this). That means that some of the core services won't be "public" (btw, if I'm not mistaken we're only supposed to import from src/core/public or src/core/public/mocks, I'm surprised that linter doesn't complain about src/core/public/http/base_path_service).

Having said that, core should provide mocks for all of its services and keep them in sync. If we don't have a mock for the core service that is used in a plugin or it's hard to use it in our tests (e.g. it doesn't have reasonable defaults for mock return values) - platform team should treat it as a bug 🙂

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'm afraid using real services may not scale very well and we'll end up with a mock in one test and real service in another.

I share this fear as well. It seems like a lot of the existing NP tests use the mocks which the NP provides.

The issue here is that not all core services are exposed to plugins as is, moreover they may have different shape for different life-cycle events: what you can do with HTTP service in setup may be drastically different from what you can do with it in start (e.g. this or this). That means that some of the core services won't be "public" (btw, if I'm not mistaken we're only supposed to import from src/core/public or src/core/public/mocks, I'm surprised that linter doesn't complain about src/core/public/http/base_path_service).

It definitely felt like I was reaching "too far" into /src/core/public to get access to this. Do you happen to have a recommendation for where I should be getting the core mock from?

Copy link
Member

Choose a reason for hiding this comment

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

I believe we're supposed to get all the mocks from either src/core/server/mocks or src/core/public/mocks. So for base_path it'd be something like this I guess:

import { coreMock } from 'src/core/public/mocks';
const basePath = coreMock.createSetup().http.basePath;

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks @azasypkin! I'll change this to use the mocks and reevaluate the other tests as well

Copy link
Contributor Author

@kobelb kobelb Aug 19, 2019

Choose a reason for hiding this comment

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

I just re-wrote the tests for this using only the mocks, and it's significantly "uglier" and harder to understand... @joshdover how do you feel about us using a concrete instance of BasePath for tests like these?

Concrete instance: https://github.com/elastic/kibana/blob/32d938591cdfbfd3651068e97cc82aa613f66e0c/x-pack/plugins/security/public/session_expired.test.ts
Mocked: https://github.com/elastic/kibana/blob/824086c168aec5cc55c04e5866ceaafdb2ec12f9/x-pack/plugins/security/public/session_expired.test.ts

Copy link
Member

@joshdover joshdover Aug 19, 2019

Choose a reason for hiding this comment

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

The basePath APIs are so small and don't interact with anything meaningful. It seems to me that we should replace this with the real implementation in coreMock and make coreMock configurable so you can pass in a basePath when you create the mock.

Normally, I'd say it's bad to mix concrete implementations and mocks like this, but given how little code we're talking about and how common I think this scenario is, I think this mixing would be fine.

In summary: delete the basePath mock completely, replace it with the real implementation, allow coreMock to take a basePath argument.

@elasticmachine
Copy link
Contributor

💚 Build Succeeded


import { HttpSetup } from 'src/core/public';

export class AnonymousPaths {
Copy link
Member

Choose a reason for hiding this comment

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

Shouldn't this implement IAnonymousPaths?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yup, I will make it so.

src/core/public/http/anonymous_paths.ts Outdated Show resolved Hide resolved
export class AnonymousPaths {
private paths: Set<string>;

constructor(private basePath: HttpSetup['basePath']) {
Copy link
Member

Choose a reason for hiding this comment

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

nit: seems simpler to just use IBasePath directly rather than coupling this to HttpSetup

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Agreed, this is a carry-over from AnonymousPaths being a security plugin class. I will update it.

src/core/public/http/anonymous_paths.ts Outdated Show resolved Hide resolved
}

public register(path: string) {
if (!path.startsWith('/')) {
Copy link
Member

Choose a reason for hiding this comment

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

Could normalizePath handle this?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It definitely can. It felt somewhat awkward to allow consumers to do anonymousPaths.register('login') and felt more explicit to require them to do anonymousPaths.register('/login'). Requiring the path to start with / makes it more obvious that the paths are from the "root" as opposed to relative to anything. This is largely subjective and since it's a platform API, my opinion is just an opinion and I will make it however you prefer.

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 all for making things more robust if it's easy to change. Don't consider it a blocker for this PR though

public setup(core: CoreSetup, deps: {}) {
const { http, notifications, injectedMetadata } = core;
const { basePath, anonymousPaths } = http;
anonymousPaths.register('/login');
Copy link
Member

Choose a reason for hiding this comment

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

What we could do is have a option on the ApplicationService that specifies an app as "anonymous" and then use that to derive these paths.

Maybe we take a look at that after #41981 is complete for the login, logout, and status apps.


// if the request was omitting credentials it's to an anonymous endpoint
// (for example to login) and we don't wish to ever redirect
// TODO: Why is httpErrorResponse.request potentially undefined?
Copy link
Member

Choose a reason for hiding this comment

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

Remove TODO?

kobelb and others added 2 commits October 22, 2019 12:31
Co-Authored-By: Josh Dover <me@joshdover.com>
Co-Authored-By: Josh Dover <me@joshdover.com>
@elasticmachine
Copy link
Contributor

💚 Build Succeeded

@kobelb
Copy link
Contributor Author

kobelb commented Oct 22, 2019

What we could do is have a option on the ApplicationService that specifies an app as "anonymous" and then use that to derive these paths.
Maybe we take a look at that after #41981 is complete for the login, logout, and status apps.

I like that idea!

Remove TODO?

Good catch, removing...

@elastic elastic deleted a comment from elasticmachine Oct 22, 2019
@elasticmachine
Copy link
Contributor

💚 Build Succeeded

@kobelb
Copy link
Contributor Author

kobelb commented Oct 23, 2019

@legrego - all your feedback should be addressed.
@joshdover - only #39477 (comment) is potentially left to address, your input would be appreciated
@elastic/kibana-app would you care to review the snapshots I updated?

@elasticmachine
Copy link
Contributor

💚 Build Succeeded

Copy link
Contributor

@TinaHeiligers TinaHeiligers left a comment

Choose a reason for hiding this comment

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

The snapshots look ok to me.
LGTM

@kobelb kobelb merged commit a149497 into elastic:master Oct 24, 2019
@kobelb kobelb deleted the np/security-http-interceptors branch October 24, 2019 15:59
kobelb added a commit to kobelb/kibana that referenced this pull request Oct 24, 2019
* We have a NP plugin! :celebration:

* Redirecting to login on all 401s

* Adding commented out code for when credentials are omitted

* Fixing types

* Respond 403 when user changes password with incorrect current password

* Adding AnonymousPaths where we ignore all 401s

* Adding anonymous path tests

* Extracted a dedicated SessionExpires class and added tests

* Fixing plugin after refactoring to add SessionExpired

* Beginning to work on the session timeout interceptor

* Fixing UnauthorizedResponseInterceptor anonymous path test

* Removing test anonymous path

* Trying to improve readability

* Displaying session logout warning

* Mocking out the base path

* Revert "Mocking out the base path"

This reverts commit 824086c.

* Changing coreMock to use a concrete instance of BasePath

* Adding session timeout interceptor tests

* Adding session timeout tests

* Adding more tests for short session timeouts

* Moving some files to a session folder

* More thrashing around: renaming and reorganizing

* Renaming Interceptor to HttpInterceptor

* Fixing some type errors

* Fixing legacy chrome API tests

* Fixing other tests to use the concrete instance of BasePath

* Adjusting some types

* Putting DeeplyMocked back, I don't get how DeeplyMockedKeys works

* Moving anonymousPaths to public core http

* Reading sessionTimeout from injected vars and supporting null timeout

* Doesn't extend session when there is no response

* Updating docs and snapshots

* Casting sessionTimeout injectedVar to "number | null"

* Fixing i18n issues

* Update x-pack/plugins/security/public/plugin.ts

Co-Authored-By: Larry Gregory <lgregorydev@gmail.com>

* Adding milliseconds postfix to SessionTimeout private fields

* Even better anonymous paths, with some validation

* Adjusting public method docs for IAnonymousPaths

* Adjusting spelling of base-path to basePath

* Update x-pack/plugins/security/public/session/session_timeout.tsx

Co-Authored-By: Larry Gregory <lgregorydev@gmail.com>

* Update src/core/public/http/anonymous_paths.ts

Co-Authored-By: Josh Dover <me@joshdover.com>

* Update src/core/public/http/anonymous_paths.ts

Co-Authored-By: Josh Dover <me@joshdover.com>

* AnonymousPaths implements IAnonymousPaths and uses IBasePath

* Removing DeeplyMocked

* Removing TODOs

* Fixing types...

* Now, ever more normal
kobelb added a commit that referenced this pull request Oct 24, 2019
* We have a NP plugin! :celebration:

* Redirecting to login on all 401s

* Adding commented out code for when credentials are omitted

* Fixing types

* Respond 403 when user changes password with incorrect current password

* Adding AnonymousPaths where we ignore all 401s

* Adding anonymous path tests

* Extracted a dedicated SessionExpires class and added tests

* Fixing plugin after refactoring to add SessionExpired

* Beginning to work on the session timeout interceptor

* Fixing UnauthorizedResponseInterceptor anonymous path test

* Removing test anonymous path

* Trying to improve readability

* Displaying session logout warning

* Mocking out the base path

* Revert "Mocking out the base path"

This reverts commit 824086c.

* Changing coreMock to use a concrete instance of BasePath

* Adding session timeout interceptor tests

* Adding session timeout tests

* Adding more tests for short session timeouts

* Moving some files to a session folder

* More thrashing around: renaming and reorganizing

* Renaming Interceptor to HttpInterceptor

* Fixing some type errors

* Fixing legacy chrome API tests

* Fixing other tests to use the concrete instance of BasePath

* Adjusting some types

* Putting DeeplyMocked back, I don't get how DeeplyMockedKeys works

* Moving anonymousPaths to public core http

* Reading sessionTimeout from injected vars and supporting null timeout

* Doesn't extend session when there is no response

* Updating docs and snapshots

* Casting sessionTimeout injectedVar to "number | null"

* Fixing i18n issues

* Update x-pack/plugins/security/public/plugin.ts

Co-Authored-By: Larry Gregory <lgregorydev@gmail.com>

* Adding milliseconds postfix to SessionTimeout private fields

* Even better anonymous paths, with some validation

* Adjusting public method docs for IAnonymousPaths

* Adjusting spelling of base-path to basePath

* Update x-pack/plugins/security/public/session/session_timeout.tsx

Co-Authored-By: Larry Gregory <lgregorydev@gmail.com>

* Update src/core/public/http/anonymous_paths.ts

Co-Authored-By: Josh Dover <me@joshdover.com>

* Update src/core/public/http/anonymous_paths.ts

Co-Authored-By: Josh Dover <me@joshdover.com>

* AnonymousPaths implements IAnonymousPaths and uses IBasePath

* Removing DeeplyMocked

* Removing TODOs

* Fixing types...

* Now, ever more normal
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Feature:Security/Authentication Platform Security - Authentication release_note:skip Skip the PR/issue when compiling release notes Team:Security Team focused on: Auth, Users, Roles, Spaces, Audit Logging, and more! v7.6.0 v8.0.0
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Session timeout notification doesn't appear on Space Selector page
6 participants