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

Expose session invalidation API. #92376

Merged
merged 16 commits into from
Mar 24, 2021
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions docs/api/session-management.asciidoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[role="xpack"]
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: copy-pasting from the role-management APIs docs 🙈

[[session-management-api]]
Copy link
Contributor

Choose a reason for hiding this comment

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

Will you be adding more APIs under the "Kibana user session management" section? If not, it would be better to make the "Invalidate user sessions API" a standalone page similar to "Shorten URL" in the TOC.

Is Kibana needed? Or, can the title simply be "User session management APIs"?

Copy link
Member Author

Choose a reason for hiding this comment

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

Will you be adding more APIs under the "Kibana user session management" section?

We don't have capacity yet, but I can see that we'll want to add user session enumeration APIs that will support session management UI and automation workflows for the admins in the future.

Is Kibana needed? Or, can the title simply be "User session management APIs"?

I'm not sure to be honest. User session is a Kibana-only thing and I basically used the same convention we used for Spaces that is also a Kibana-only thing (Kibana Spaces APIs). I think Kibana is redundant here since this is a section of the Kibana docs after all, but what do you think?

Copy link
Contributor

Choose a reason for hiding this comment

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

I think Kibana is not needed.

== {kib} session management APIs

Allows managing {kib} <<xpack-security-session-management, user sessions>>.
Copy link
Contributor

Choose a reason for hiding this comment

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

The sentence "Allows managing Kibana user sessions" isn't needed. It's covered in the title and in the sentence that follows it.


The following {kib} session management APIs are available:

* <<session-management-api-invalidate, Invalidate sessions API>> to invalidate {kib} user sessions

include::session-management/invalidate.asciidoc[]
108 changes: 108 additions & 0 deletions docs/api/session-management/invalidate.asciidoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
[[session-management-api-invalidate]]
=== Invalidate sessions API
++++
<titleabbrev>Invalidate sessions</titleabbrev>
++++

experimental[] Invalidates {kib} user sessions that match provided query.

[[session-management-api-invalidate-prereqs]]
==== Prerequisite

To use the invalidate sessions API, you must be a `superuser`.

[[session-management-api-invalidate-request]]
==== Request

`POST <kibana host>:<port>/api/security/session/_invalidate`

[role="child_attributes"]
[[session-management-api-invalidate-request-body]]
==== Request body

`match`::
(Required, string) Specifies how {kib} should determine which sessions should be invalidated. Can either be `all` to invalidate all existing sessions, or `query` to only invalidate sessions that match the query specified in the additional `query` parameter.
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
(Required, string) Specifies how {kib} should determine which sessions should be invalidated. Can either be `all` to invalidate all existing sessions, or `query` to only invalidate sessions that match the query specified in the additional `query` parameter.
(Required, string) Specifies how {kib} determines which sessions to invalidate. Can either be `all` to invalidate all existing sessions, or `query` to only invalidate sessions that match the query specified in the additional `query` parameter.


`query`::
(Optional, object) Specifies the query that {kib} should use to match the sessions that should be invalidated when `match` parameter is set to `query`. This parameter is forbidden if `match` is set to `all`.
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
(Optional, object) Specifies the query that {kib} should use to match the sessions that should be invalidated when `match` parameter is set to `query`. This parameter is forbidden if `match` is set to `all`.
(Optional, object) Specifies the query that {kib} uses to match the sessions to invalidate when the `match` parameter is set to `query`. This parameter is forbidden if `match` is set to `all`.

Copy link
Contributor

Choose a reason for hiding this comment

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

Does the last sentence mean "You cannot use this parameter if match is set to all.

Copy link
Member Author

Choose a reason for hiding this comment

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

Does the last sentence mean "You cannot use this parameter if match is set to all.

That's correct. Will use You cannot use this parameter if match is set to all. instead, thanks!

+
.Properties of `query`
[%collapsible%open]
=====
`provider` :::
(Required, object) Contains required `type` and optional `name` string properties to match sessions that were created by the specific <<authentication-security-settings, authentication provider>>.

`username` :::
(Optional, string) If specified, {kib} will only invalidate sessions that belong to a specific user.
=====

[[session-management-api-invalidate-response-body]]
==== Response body

`total`::
(number) The number of successfully invalidated sessions.

[[session-management-api-invalidate-response-codes]]
==== Response codes

`200`::
Indicates a successful call.

`403`::
Indicates that the user may not be authorized to invalidate sessions for other users, refer to <<session-management-api-invalidate-prereqs, Prerequisite section>>.
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
Indicates that the user may not be authorized to invalidate sessions for other users, refer to <<session-management-api-invalidate-prereqs, Prerequisite section>>.
Indicates that the user may not be authorized to invalidate sessions for other users. Refer to <<session-management-api-invalidate-prereqs, prerequisites>>.


==== Examples

Invalidate all existing sessions:

[source,sh]
--------------------------------------------------
$ curl -X POST api/security/session/_invalidate
{
"match" : "all"
}
--------------------------------------------------
// KIBANA

Invalidate sessions that were created by any <<saml, SAML authentication providers>> only:

[source,sh]
--------------------------------------------------
$ curl -X POST api/security/session/_invalidate
{
"match" : "query",
"query": {
"provider" : { "type": "saml" }
}
}
--------------------------------------------------
// KIBANA

Invalidate sessions that were created by the <<saml, SAML authentication provider>> with the name `saml1` only:

[source,sh]
--------------------------------------------------
$ curl -X POST api/security/session/_invalidate
{
"match" : "query",
"query": {
"provider" : { "type": "saml", "name": "saml1" }
}
}
--------------------------------------------------
// KIBANA

Invalidate sessions that were created by any <<oidc, OpenID Connect authentication providers>> for the user with the name `user@my-oidc-sso.com` only:

[source,sh]
--------------------------------------------------
$ curl -X POST api/security/session/_invalidate
{
"match" : "query",
"query": {
"provider" : { "type": "oidc" },
"username": "user@my-oidc-sso.com"
}
}
--------------------------------------------------
// KIBANA
1 change: 1 addition & 0 deletions docs/user/api.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ curl -X POST \
include::{kib-repo-dir}/api/features.asciidoc[]
include::{kib-repo-dir}/api/spaces-management.asciidoc[]
include::{kib-repo-dir}/api/role-management.asciidoc[]
include::{kib-repo-dir}/api/session-management.asciidoc[]
include::{kib-repo-dir}/api/saved-objects.asciidoc[]
include::{kib-repo-dir}/api/alerts.asciidoc[]
include::{kib-repo-dir}/api/actions-and-connectors.asciidoc[]
Expand Down
8 changes: 8 additions & 0 deletions docs/user/security/authentication/index.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,14 @@ NOTE: *Public URL* is available only when anonymous access is configured and you
+
For more information, refer to <<embedding, Embed {kib} content in a web page>>.

[float]
Copy link
Member Author

@azasypkin azasypkin Mar 17, 2021

Choose a reason for hiding this comment

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

note: I initially planned to document invalidate API in the follow-up and combine it with the slightly related small (:crossed_fingers:) change we were talking about here, sooo that's my excuse for this change in this PR 🙂

[[anonymous-access-session]]
===== Anonymous access session

{kib} maintains a separate <<xpack-security-session-management, session>> for every anonymous user, as it does for any other authentication mechanism. This way {kib} can maintain a personalized experience even for the users who didn't provide any personal credentials.

You can configure both <<session-idle-timeout, session idle timeout>> and <<session-lifespan, session lifespan>> for the anonymous sessions as you'd do for any other session with the only exception that idle timeout is explicitly disabled for the anonymous sessions by default. That means that the global <<security-session-and-cookie-settings, `xpack.security.session.idleTimeout`>> setting won't affect anonymous sessions. If you want to change the idle timeout for the anonymous sessions, you must configure the provider-level <<anonymous-authentication-provider-settings, `xpack.security.authc.providers.anonymous.<provider-name>.session.idleTimeout`>> setting instead.
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
You can configure both <<session-idle-timeout, session idle timeout>> and <<session-lifespan, session lifespan>> for the anonymous sessions as you'd do for any other session with the only exception that idle timeout is explicitly disabled for the anonymous sessions by default. That means that the global <<security-session-and-cookie-settings, `xpack.security.session.idleTimeout`>> setting won't affect anonymous sessions. If you want to change the idle timeout for the anonymous sessions, you must configure the provider-level <<anonymous-authentication-provider-settings, `xpack.security.authc.providers.anonymous.<provider-name>.session.idleTimeout`>> setting instead.
You can configure <<session-idle-timeout, session idle timeout>> and <<session-lifespan, session lifespan>> for anonymous sessions the same as you do for any other session with the exception that idle timeout is explicitly disabled for anonymous sessions by default. The global <<security-session-and-cookie-settings, `xpack.security.session.idleTimeout`>> setting doesn't affect anonymous sessions. To change the idle timeout for anonymous sessions, you must configure the provider-level <<anonymous-authentication-provider-settings, `xpack.security.authc.providers.anonymous.<provider-name>.session.idleTimeout`>> setting.


[[http-authentication]]
==== HTTP authentication

Expand Down
2 changes: 2 additions & 0 deletions docs/user/security/session-management.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ When you log in, {kib} creates a session that is used to authenticate subsequent

When your session expires, or you log out, {kib} will invalidate your cookie and remove session information from the index. {kib} also periodically invalidates and removes any expired sessions that weren't explicitly invalidated.

To manage user sessions programmatically, {kib} exposes a set of <<session-management-api, session management APIs>>.
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
To manage user sessions programmatically, {kib} exposes a set of <<session-management-api, session management APIs>>.
To manage user sessions programmatically, {kib} exposes <<session-management-api, session management APIs>>.


[[session-idle-timeout]]
==== Session idle timeout

Expand Down
50 changes: 25 additions & 25 deletions x-pack/plugins/security/server/authentication/authenticator.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -585,8 +585,8 @@ describe('Authenticator', () => {
expect(mockOptions.session.create).not.toHaveBeenCalled();
expect(mockOptions.session.update).not.toHaveBeenCalled();
expect(mockOptions.session.extend).not.toHaveBeenCalled();
expect(mockOptions.session.clear).toHaveBeenCalledTimes(1);
expect(mockOptions.session.clear).toHaveBeenCalledWith(request);
expect(mockOptions.session.invalidate).toHaveBeenCalledTimes(1);
expect(mockOptions.session.invalidate).toHaveBeenCalledWith(request, { match: 'current' });
});

it('clears session if provider asked to do so in `succeeded` result.', async () => {
Expand All @@ -605,8 +605,8 @@ describe('Authenticator', () => {
expect(mockOptions.session.create).not.toHaveBeenCalled();
expect(mockOptions.session.update).not.toHaveBeenCalled();
expect(mockOptions.session.extend).not.toHaveBeenCalled();
expect(mockOptions.session.clear).toHaveBeenCalledTimes(1);
expect(mockOptions.session.clear).toHaveBeenCalledWith(request);
expect(mockOptions.session.invalidate).toHaveBeenCalledTimes(1);
expect(mockOptions.session.invalidate).toHaveBeenCalledWith(request, { match: 'current' });
});

it('clears session if provider asked to do so in `redirected` result.', async () => {
Expand All @@ -624,8 +624,8 @@ describe('Authenticator', () => {
expect(mockOptions.session.create).not.toHaveBeenCalled();
expect(mockOptions.session.update).not.toHaveBeenCalled();
expect(mockOptions.session.extend).not.toHaveBeenCalled();
expect(mockOptions.session.clear).toHaveBeenCalledTimes(1);
expect(mockOptions.session.clear).toHaveBeenCalledWith(request);
expect(mockOptions.session.invalidate).toHaveBeenCalledTimes(1);
expect(mockOptions.session.invalidate).toHaveBeenCalledWith(request, { match: 'current' });
});

describe('with Access Agreement', () => {
Expand Down Expand Up @@ -1191,7 +1191,7 @@ describe('Authenticator', () => {
expect(mockOptions.session.create).not.toHaveBeenCalled();
expect(mockOptions.session.update).not.toHaveBeenCalled();
expect(mockOptions.session.extend).not.toHaveBeenCalled();
expect(mockOptions.session.clear).not.toHaveBeenCalled();
expect(mockOptions.session.invalidate).not.toHaveBeenCalled();
});

it('extends session for non-system API calls.', async () => {
Expand All @@ -1213,7 +1213,7 @@ describe('Authenticator', () => {
expect(mockOptions.session.extend).toHaveBeenCalledWith(request, mockSessVal);
expect(mockOptions.session.create).not.toHaveBeenCalled();
expect(mockOptions.session.update).not.toHaveBeenCalled();
expect(mockOptions.session.clear).not.toHaveBeenCalled();
expect(mockOptions.session.invalidate).not.toHaveBeenCalled();
});

it('does not touch session for system API calls if authentication fails with non-401 reason.', async () => {
Expand All @@ -1234,7 +1234,7 @@ describe('Authenticator', () => {
expect(mockOptions.session.create).not.toHaveBeenCalled();
expect(mockOptions.session.update).not.toHaveBeenCalled();
expect(mockOptions.session.extend).not.toHaveBeenCalled();
expect(mockOptions.session.clear).not.toHaveBeenCalled();
expect(mockOptions.session.invalidate).not.toHaveBeenCalled();
});

it('does not touch session for non-system API calls if authentication fails with non-401 reason.', async () => {
Expand All @@ -1255,7 +1255,7 @@ describe('Authenticator', () => {
expect(mockOptions.session.create).not.toHaveBeenCalled();
expect(mockOptions.session.update).not.toHaveBeenCalled();
expect(mockOptions.session.extend).not.toHaveBeenCalled();
expect(mockOptions.session.clear).not.toHaveBeenCalled();
expect(mockOptions.session.invalidate).not.toHaveBeenCalled();
});

it('replaces existing session with the one returned by authentication provider for system API requests', async () => {
Expand All @@ -1281,7 +1281,7 @@ describe('Authenticator', () => {
});
expect(mockOptions.session.create).not.toHaveBeenCalled();
expect(mockOptions.session.extend).not.toHaveBeenCalled();
expect(mockOptions.session.clear).not.toHaveBeenCalled();
expect(mockOptions.session.invalidate).not.toHaveBeenCalled();
});

it('replaces existing session with the one returned by authentication provider for non-system API requests', async () => {
Expand All @@ -1307,7 +1307,7 @@ describe('Authenticator', () => {
});
expect(mockOptions.session.create).not.toHaveBeenCalled();
expect(mockOptions.session.extend).not.toHaveBeenCalled();
expect(mockOptions.session.clear).not.toHaveBeenCalled();
expect(mockOptions.session.invalidate).not.toHaveBeenCalled();
});

it('clears session if provider failed to authenticate system API request with 401 with active session.', async () => {
Expand All @@ -1324,8 +1324,8 @@ describe('Authenticator', () => {
AuthenticationResult.failed(Boom.unauthorized())
);

expect(mockOptions.session.clear).toHaveBeenCalledTimes(1);
expect(mockOptions.session.clear).toHaveBeenCalledWith(request);
expect(mockOptions.session.invalidate).toHaveBeenCalledTimes(1);
expect(mockOptions.session.invalidate).toHaveBeenCalledWith(request, { match: 'current' });
expect(mockOptions.session.create).not.toHaveBeenCalled();
expect(mockOptions.session.update).not.toHaveBeenCalled();
expect(mockOptions.session.extend).not.toHaveBeenCalled();
Expand All @@ -1345,8 +1345,8 @@ describe('Authenticator', () => {
AuthenticationResult.failed(Boom.unauthorized())
);

expect(mockOptions.session.clear).toHaveBeenCalledTimes(1);
expect(mockOptions.session.clear).toHaveBeenCalledWith(request);
expect(mockOptions.session.invalidate).toHaveBeenCalledTimes(1);
expect(mockOptions.session.invalidate).toHaveBeenCalledWith(request, { match: 'current' });
expect(mockOptions.session.create).not.toHaveBeenCalled();
expect(mockOptions.session.update).not.toHaveBeenCalled();
expect(mockOptions.session.extend).not.toHaveBeenCalled();
Expand All @@ -1364,8 +1364,8 @@ describe('Authenticator', () => {
AuthenticationResult.redirectTo('some-url', { state: null })
);

expect(mockOptions.session.clear).toHaveBeenCalledTimes(1);
expect(mockOptions.session.clear).toHaveBeenCalledWith(request);
expect(mockOptions.session.invalidate).toHaveBeenCalledTimes(1);
expect(mockOptions.session.invalidate).toHaveBeenCalledWith(request, { match: 'current' });
expect(mockOptions.session.create).not.toHaveBeenCalled();
expect(mockOptions.session.update).not.toHaveBeenCalled();
expect(mockOptions.session.extend).not.toHaveBeenCalled();
Expand All @@ -1382,7 +1382,7 @@ describe('Authenticator', () => {
AuthenticationResult.notHandled()
);

expect(mockOptions.session.clear).not.toHaveBeenCalled();
expect(mockOptions.session.invalidate).not.toHaveBeenCalled();
expect(mockOptions.session.create).not.toHaveBeenCalled();
expect(mockOptions.session.update).not.toHaveBeenCalled();
expect(mockOptions.session.extend).not.toHaveBeenCalled();
Expand All @@ -1399,7 +1399,7 @@ describe('Authenticator', () => {
AuthenticationResult.notHandled()
);

expect(mockOptions.session.clear).not.toHaveBeenCalled();
expect(mockOptions.session.invalidate).not.toHaveBeenCalled();
expect(mockOptions.session.create).not.toHaveBeenCalled();
expect(mockOptions.session.update).not.toHaveBeenCalled();
expect(mockOptions.session.extend).not.toHaveBeenCalled();
Expand Down Expand Up @@ -1789,7 +1789,7 @@ describe('Authenticator', () => {
DeauthenticationResult.notHandled()
);

expect(mockOptions.session.clear).not.toHaveBeenCalled();
expect(mockOptions.session.invalidate).not.toHaveBeenCalled();
});

it('clears session and returns whatever authentication provider returns.', async () => {
Expand All @@ -1804,7 +1804,7 @@ describe('Authenticator', () => {
);

expect(mockBasicAuthenticationProvider.logout).toHaveBeenCalledTimes(1);
expect(mockOptions.session.clear).toHaveBeenCalled();
expect(mockOptions.session.invalidate).toHaveBeenCalled();
});

it('if session does not exist but provider name is valid, returns whatever authentication provider returns.', async () => {
Expand All @@ -1823,7 +1823,7 @@ describe('Authenticator', () => {

expect(mockBasicAuthenticationProvider.logout).toHaveBeenCalledTimes(1);
expect(mockBasicAuthenticationProvider.logout).toHaveBeenCalledWith(request, null);
expect(mockOptions.session.clear).toHaveBeenCalled();
expect(mockOptions.session.invalidate).toHaveBeenCalled();
});

it('if session does not exist and provider name is not available, returns whatever authentication provider returns.', async () => {
Expand All @@ -1840,7 +1840,7 @@ describe('Authenticator', () => {

expect(mockBasicAuthenticationProvider.logout).toHaveBeenCalledTimes(1);
expect(mockBasicAuthenticationProvider.logout).toHaveBeenCalledWith(request);
expect(mockOptions.session.clear).not.toHaveBeenCalled();
expect(mockOptions.session.invalidate).not.toHaveBeenCalled();
});

it('returns `notHandled` if session does not exist and provider name is invalid', async () => {
Expand All @@ -1852,7 +1852,7 @@ describe('Authenticator', () => {
);

expect(mockBasicAuthenticationProvider.logout).not.toHaveBeenCalled();
expect(mockOptions.session.clear).toHaveBeenCalled();
expect(mockOptions.session.invalidate).toHaveBeenCalled();
});
});

Expand Down
Loading