Skip to content

Commit

Permalink
Review#2: handle review feedback and add docs for the session invalid…
Browse files Browse the repository at this point in the history
…ate API.
  • Loading branch information
azasypkin committed Mar 17, 2021
1 parent 26cf895 commit 8f1caae
Show file tree
Hide file tree
Showing 7 changed files with 152 additions and 13 deletions.
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"]
[[session-management-api]]
== {kib} session management APIs

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

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.

`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`.
+
.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>>.

==== 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 `[email protected]` only:

[source,sh]
--------------------------------------------------
$ curl -X POST api/security/session/_invalidate
{
"match" : "query",
"query": {
"provider" : { "type": "oidc" },
"username": "[email protected]"
}
}
--------------------------------------------------
// 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]
[[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.

[[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>>.

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -820,11 +820,11 @@ describe('Session', () => {
it('[match=all] clears all sessions even if current initiator request does not have a session', async () => {
mockSessionCookie.get.mockResolvedValue(null);

const mockRequest = httpServerMock.createKibanaRequest();
await expect(session.invalidate(mockRequest, { match: 'all' })).resolves.toBe(10);
await expect(
session.invalidate(httpServerMock.createKibanaRequest(), { match: 'all' })
).resolves.toBe(10);

expect(mockSessionCookie.clear).toHaveBeenCalledTimes(1);
expect(mockSessionCookie.clear).toHaveBeenCalledWith(mockRequest);
expect(mockSessionCookie.clear).not.toHaveBeenCalled();
expect(mockSessionIndex.invalidate).toHaveBeenCalledTimes(1);
expect(mockSessionIndex.invalidate).toHaveBeenCalledWith({ match: 'all' });
});
Expand Down
27 changes: 18 additions & 9 deletions x-pack/plugins/security/server/session_management/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ export class Session {
...publicSessionValue,
...sessionExpirationInfo,
sid,
usernameHash: username && createHash('sha3-256').update(username).digest('hex'),
usernameHash: username && Session.getUsernameHash(username),
content: await this.crypto.encrypt(JSON.stringify({ username, state }), aad),
});

Expand Down Expand Up @@ -242,7 +242,7 @@ export class Session {
...sessionValue.metadata.index,
...publicSessionInfo,
...sessionExpirationInfo,
usernameHash: username && createHash('sha3-256').update(username).digest('hex'),
usernameHash: username && Session.getUsernameHash(username),
content: await this.crypto.encrypt(
JSON.stringify({ username, state }),
sessionCookieValue.aad
Expand Down Expand Up @@ -380,6 +380,11 @@ export class Session {
const sessionCookieValue = await this.options.sessionCookie.get(request);
const sessionLogger = this.getLoggerForSID(sessionCookieValue?.sid);

// We clear session cookie only when the current session should be invalidated since it's the
// only case when this action is explicitly and unequivocally requested. This behavior doesn't
// introduce any risk since even if the current session has been affected the session cookie
// will be automatically invalidated as soon as client attempts to re-use it due to missing
// underlying session index value.
let invalidateIndexValueFilter;
if (filter.match === 'current') {
if (!sessionCookieValue) {
Expand All @@ -391,25 +396,19 @@ export class Session {
invalidateIndexValueFilter = { match: 'sid' as 'sid', sid: sessionCookieValue.sid };
} else if (filter.match === 'all') {
sessionLogger.debug('Invalidating all sessions.');
await this.options.sessionCookie.clear(request);
invalidateIndexValueFilter = filter;
} else {
sessionLogger.debug(
`Invalidating sessions that match query: ${JSON.stringify(
filter.query.username ? { ...filter.query, username: '[REDACTED]' } : filter.query
)}.`
);

// We don't clear session cookie in this case since we cannot always be sure that the query
// will match the current session. This behavior doesn't introduce any risk since even if the
// current session has been affected the session cookie will be automatically invalidated as
// soon as client attempts to re-use it due to missing underlying session index value.
invalidateIndexValueFilter = filter.query.username
? {
...filter,
query: {
provider: filter.query.provider,
usernameHash: createHash('sha3-256').update(filter.query.username).digest('hex'),
usernameHash: Session.getUsernameHash(filter.query.username),
},
}
: filter;
Expand Down Expand Up @@ -461,4 +460,14 @@ export class Session {
private getLoggerForSID(sid?: string) {
return this.options.logger.get(sid?.slice(-10) ?? 'no_session');
}

/**
* Generates a sha3-256 hash for the specified `username`. The hash is intended to be stored in
* the session index to allow querying user specific sessions and don't expose the original
* `username` at the same time.
* @param username Username string to generate hash for.
*/
private static getUsernameHash(username: string) {
return createHash('sha3-256').update(username).digest('hex');
}
}

0 comments on commit 8f1caae

Please sign in to comment.