Deprecate Anonymous Authentication Credentials (#131636)

* Adds deprecation warnings for apiKey and elasticsearch_anonymous_user credentials of  anonymous authentication providers.
Adds telemetry for usage of anonymous authentication credential type.

* Update x-pack/plugins/security/server/config_deprecations.ts

Co-authored-by: Joe Portner <5295965+jportner@users.noreply.github.com>

* Update x-pack/plugins/security/server/config_deprecations.ts

Co-authored-by: Joe Portner <5295965+jportner@users.noreply.github.com>

* Update x-pack/plugins/security/server/config_deprecations.ts

Co-authored-by: Joe Portner <5295965+jportner@users.noreply.github.com>

* Updated all docs to remove deprecated anon auth features, fixed doc link logic and typos.

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
Co-authored-by: Joe Portner <5295965+jportner@users.noreply.github.com>
This commit is contained in:
Jeramy Soucy 2022-05-18 19:27:40 +02:00 committed by GitHub
parent 64689c0f9e
commit 7d8aae5f8a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 378 additions and 63 deletions

View file

@ -112,34 +112,20 @@ In addition to <<authentication-provider-settings,the settings that are valid fo
NOTE: You can configure only one anonymous provider per {kib} instance.
xpack.security.authc.providers.anonymous.<provider-name>.credentials {ess-icon}::
Credentials that {kib} should use internally to authenticate anonymous requests to {es}. Possible values are: username and password, API key, or the constant `elasticsearch_anonymous_user` if you want to leverage {ref}/anonymous-access.html[{es} anonymous access].
Credentials that {kib} should use internally to authenticate anonymous requests to {es}.
+
For example:
+
[source,yaml]
----------------------------------------
# Username and password credentials
xpack.security.authc.providers.anonymous.anonymous1:
credentials:
username: "anonymous_service_account"
password: "anonymous_service_account_password"
# API key (concatenated and base64-encoded)
xpack.security.authc.providers.anonymous.anonymous1:
credentials:
apiKey: "VnVhQ2ZHY0JDZGJrUW0tZTVhT3g6dWkybHAyYXhUTm1zeWFrdzl0dk5udw=="
# API key (as returned from Elasticsearch API)
xpack.security.authc.providers.anonymous.anonymous1:
credentials:
apiKey.id: "VuaCfGcBCdbkQm-e5aOx"
apiKey.key: "ui2lp2axTNmsyakw9tvNnw"
# Elasticsearch anonymous access
xpack.security.authc.providers.anonymous.anonymous1:
credentials: "elasticsearch_anonymous_user"
----------------------------------------
For more information, refer to <<anonymous-authentication>>.
[float]
[[http-authentication-settings]]
==== HTTP authentication settings

View file

@ -332,13 +332,11 @@ Anyone with access to the network {kib} is exposed to will be able to access {ki
Anonymous authentication gives users access to {kib} without requiring them to provide credentials. This can be useful if you want your users to skip the login step when you embed dashboards in another application or set up a demo {kib} instance in your internal network, while still keeping other security features intact.
To enable anonymous authentication in {kib}, you must decide what credentials the anonymous service account {kib} should use internally to authenticate anonymous requests.
To enable anonymous authentication in {kib}, you must specify the credentials the anonymous service account {kib} should use internally to authenticate anonymous requests.
NOTE: You can configure only one anonymous authentication provider per {kib} instance.
There are three ways to specify these credentials:
If you have a user who can authenticate to {es} using username and password, for instance from the Native or LDAP security realms, you can also use these credentials to impersonate the anonymous users. Here is how your `kibana.yml` might look if you use username and password credentials:
You must have a user account that can authenticate to {es} using a username and password, for instance from the Native or LDAP security realms, so that you can use these credentials to impersonate the anonymous users. Here is how your `kibana.yml` might look:
[source,yaml]
-----------------------------------------------
@ -350,45 +348,6 @@ xpack.security.authc.providers:
password: "anonymous_service_account_password"
-----------------------------------------------
If using username and password credentials isn't desired or feasible, then you can create a dedicated <<api-keys, API key>> for the anonymous service account. In this case, your `kibana.yml` might look like this:
[source,yaml]
-----------------------------------------------
xpack.security.authc.providers:
anonymous.anonymous1:
order: 0
credentials:
apiKey: "VnVhQ2ZHY0JDZGJrUW0tZTVhT3g6dWkybHAyYXhUTm1zeWFrdzl0dk5udw=="
-----------------------------------------------
The previous configuration snippet uses an API key string that is the result of base64-encoding of the `id` and `api_key` fields returned from the {es} API, joined by a colon. You can also specify these fields separately, and {kib} will do the concatenation and base64-encoding for you:
[source,yaml]
-----------------------------------------------
xpack.security.authc.providers:
anonymous.anonymous1:
order: 0
credentials:
apiKey.id: "VuaCfGcBCdbkQm-e5aOx"
apiKey.key: "ui2lp2axTNmsyakw9tvNnw"
-----------------------------------------------
It's also possible to use {kib} anonymous access in conjunction with the {es} anonymous access.
Prior to configuring {kib}, ensure that anonymous access is enabled and properly configured in {es}. See {ref}/anonymous-access.html[Enabling anonymous access] for more information.
Here is how your `kibana.yml` might look like if you want to use {es} anonymous access to impersonate anonymous users in {kib}:
[source,yaml]
-----------------------------------------------
xpack.security.authc.providers:
anonymous.anonymous1:
order: 0
credentials: "elasticsearch_anonymous_user" <1>
-----------------------------------------------
<1> The `elasticsearch_anonymous_user` is a special constant that indicates you want to use the {es} anonymous user.
[float]
===== Anonymous access and other types of authentication

View file

@ -295,4 +295,92 @@ describe('Config Deprecations', () => {
]
`);
});
it(`warns that 'xpack.security.authc.providers.anonymous.<provider-name>.credentials.apiKey' is deprecated`, () => {
const config = {
xpack: {
security: {
authc: {
providers: {
anonymous: {
anonymous1: {
credentials: {
apiKey: 'VnVhQ2ZHY0JDZGJrUW0tZTVhT3g6dWkybHAyYXhUTm1zeWFrdzl0dk5udw==',
},
},
},
},
},
},
},
};
const { messages, configPaths } = applyConfigDeprecations(cloneDeep(config));
expect(messages).toMatchInlineSnapshot(`
Array [
"Support for apiKey is being removed from the 'anonymous' authentication provider. Use username and password credentials.",
]
`);
expect(configPaths).toEqual([
'xpack.security.authc.providers.anonymous.anonymous1.credentials.apiKey',
]);
});
it(`warns that 'xpack.security.authc.providers.anonymous.<provider-name>.credentials.apiKey' with id and key is deprecated`, () => {
const config = {
xpack: {
security: {
authc: {
providers: {
anonymous: {
anonymous1: {
credentials: {
apiKey: { id: 'VuaCfGcBCdbkQm-e5aOx', key: 'ui2lp2axTNmsyakw9tvNnw' },
},
},
},
},
},
},
},
};
const { messages, configPaths } = applyConfigDeprecations(cloneDeep(config));
expect(messages).toMatchInlineSnapshot(`
Array [
"Support for apiKey is being removed from the 'anonymous' authentication provider. Use username and password credentials.",
]
`);
expect(configPaths).toEqual([
'xpack.security.authc.providers.anonymous.anonymous1.credentials.apiKey',
]);
});
it(`warns that 'xpack.security.authc.providers.anonymous.<provider-name>.credentials' of 'elasticsearch_anonymous_user' is deprecated`, () => {
const config = {
xpack: {
security: {
authc: {
providers: {
anonymous: {
anonymous1: {
credentials: 'elasticsearch_anonymous_user',
},
},
},
},
},
},
};
const { messages, configPaths } = applyConfigDeprecations(cloneDeep(config));
expect(messages).toMatchInlineSnapshot(`
Array [
"Support for 'elasticsearch_anonymous_user' is being removed from the 'anonymous' authentication provider. Use username and password credentials.",
]
`);
expect(configPaths).toEqual([
'xpack.security.authc.providers.anonymous.anonymous1.credentials',
]);
});
});

View file

@ -35,7 +35,7 @@ export const securityConfigDeprecationProvider: ConfigDeprecationProvider = ({
(settings, _fromPath, addDeprecation, { branch }) => {
if (Array.isArray(settings?.xpack?.security?.authc?.providers)) {
// TODO: remove when docs support "main"
const docsBranch = branch === 'main' ? 'master' : 'main';
const docsBranch = branch === 'main' ? 'master' : 'branch';
addDeprecation({
configPath: 'xpack.security.authc.providers',
title: i18n.translate('xpack.security.deprecations.authcProvidersTitle', {
@ -62,7 +62,7 @@ export const securityConfigDeprecationProvider: ConfigDeprecationProvider = ({
},
(settings, _fromPath, addDeprecation, { branch }) => {
// TODO: remove when docs support "main"
const docsBranch = branch === 'main' ? 'master' : 'main';
const docsBranch = branch === 'main' ? 'master' : 'branch';
const hasProviderType = (providerType: string) => {
const providers = settings?.xpack?.security?.authc?.providers;
@ -106,7 +106,7 @@ export const securityConfigDeprecationProvider: ConfigDeprecationProvider = ({
},
(settings, _fromPath, addDeprecation, { branch }) => {
// TODO: remove when docs support "main"
const docsBranch = branch === 'main' ? 'master' : 'main';
const docsBranch = branch === 'main' ? 'master' : 'branch';
const samlProviders = (settings?.xpack?.security?.authc?.providers?.saml ?? {}) as Record<
string,
any
@ -138,4 +138,57 @@ export const securityConfigDeprecationProvider: ConfigDeprecationProvider = ({
});
}
},
(settings, _fromPath, addDeprecation, { branch }) => {
// TODO: remove when docs support "main"
const docsBranch = branch === 'main' ? 'master' : 'branch';
const anonProviders = (settings?.xpack?.security?.authc?.providers?.anonymous ?? {}) as Record<
string,
any
>;
const credTypeElasticsearchAnonUser = 'elasticsearch_anonymous_user';
const credTypeApiKey = 'apiKey';
for (const provider of Object.entries(anonProviders)) {
if (
!!provider[1].credentials.apiKey ||
provider[1].credentials === credTypeElasticsearchAnonUser
) {
const isApiKey: boolean = !!provider[1].credentials.apiKey;
addDeprecation({
configPath: `xpack.security.authc.providers.anonymous.${provider[0]}.credentials${
isApiKey ? '.apiKey' : ''
}`,
title: i18n.translate(
'xpack.security.deprecations.anonymousApiKeyOrElasticsearchAnonUserTitle',
{
values: {
credType: isApiKey ? `${credTypeApiKey}` : `'${credTypeElasticsearchAnonUser}'`,
},
defaultMessage: `Use of {credType} for "xpack.security.authc.providers.anonymous.credentials" is deprecated.`,
}
),
message: i18n.translate(
'xpack.security.deprecations.anonymousApiKeyOrElasticsearchAnonUserMessage',
{
values: {
credType: isApiKey ? `${credTypeApiKey}` : `'${credTypeElasticsearchAnonUser}'`,
},
defaultMessage: `Support for {credType} is being removed from the 'anonymous' authentication provider. Use username and password credentials.`,
}
),
level: 'warning',
documentationUrl: `https://www.elastic.co/guide/en/kibana/${docsBranch}/kibana-authentication.html#anonymous-authentication`,
correctiveActions: {
manualSteps: [
i18n.translate('xpack.security.deprecations.anonAuthCredentials.manualSteps1', {
defaultMessage:
'Change the anonymous authentication provider to use username and password credentials.',
}),
],
},
});
}
}
},
];

View file

@ -49,6 +49,7 @@ describe('Security UsageCollector', () => {
sessionIdleTimeoutInMinutes: 480,
sessionLifespanInMinutes: 43200,
sessionCleanupInMinutes: 60,
anonymousCredentialType: undefined,
};
describe('initialization', () => {
@ -109,6 +110,7 @@ describe('Security UsageCollector', () => {
sessionIdleTimeoutInMinutes: 0,
sessionLifespanInMinutes: 0,
sessionCleanupInMinutes: 0,
anonymousCredentialType: undefined,
});
});
@ -465,4 +467,197 @@ describe('Security UsageCollector', () => {
});
});
});
describe('anonymous auth credentials', () => {
it('reports anonymous credential of apiKey with id and key as api_key', async () => {
const config = createSecurityConfig(
ConfigSchema.validate({
authc: {
providers: {
anonymous: {
anonymous1: {
order: 1,
credentials: {
apiKey: { id: 'VuaCfGcBCdbkQm-e5aOx', key: 'ui2lp2axTNmsyakw9tvNnw' },
},
},
},
},
},
})
);
const usageCollection = usageCollectionPluginMock.createSetupContract();
const license = createSecurityLicense({ isLicenseAvailable: true, allowAuditLogging: false });
registerSecurityUsageCollector({ usageCollection, config, license });
const usage = await usageCollection
.getCollectorByType('security')
?.fetch(collectorFetchContext);
expect(usage).toEqual({
...DEFAULT_USAGE,
enabledAuthProviders: ['anonymous'],
anonymousCredentialType: 'api_key',
});
});
it('reports anonymous credential of apiKey as api_key', async () => {
const config = createSecurityConfig(
ConfigSchema.validate({
authc: {
providers: {
anonymous: {
anonymous1: {
order: 1,
credentials: {
apiKey: 'VnVhQ2ZHY0JDZGJrUW0tZTVhT3g6dWkybHAyYXhUTm1zeWFrdzl0dk5udw==',
},
},
},
},
},
})
);
const usageCollection = usageCollectionPluginMock.createSetupContract();
const license = createSecurityLicense({ isLicenseAvailable: true, allowAuditLogging: false });
registerSecurityUsageCollector({ usageCollection, config, license });
const usage = await usageCollection
.getCollectorByType('security')
?.fetch(collectorFetchContext);
expect(usage).toEqual({
...DEFAULT_USAGE,
enabledAuthProviders: ['anonymous'],
anonymousCredentialType: 'api_key',
});
});
it(`reports anonymous credential of 'elasticsearch_anonymous_user' as elasticsearch_anonymous_user`, async () => {
const config = createSecurityConfig(
ConfigSchema.validate({
authc: {
providers: {
anonymous: {
anonymous1: {
order: 1,
credentials: 'elasticsearch_anonymous_user',
},
},
},
},
})
);
const usageCollection = usageCollectionPluginMock.createSetupContract();
const license = createSecurityLicense({ isLicenseAvailable: true, allowAuditLogging: false });
registerSecurityUsageCollector({ usageCollection, config, license });
const usage = await usageCollection
.getCollectorByType('security')
?.fetch(collectorFetchContext);
expect(usage).toEqual({
...DEFAULT_USAGE,
enabledAuthProviders: ['anonymous'],
anonymousCredentialType: 'elasticsearch_anonymous_user',
});
});
it('reports anonymous credential of username and password as usernanme_password', async () => {
const config = createSecurityConfig(
ConfigSchema.validate({
authc: {
providers: {
anonymous: {
anonymous1: {
order: 1,
credentials: {
username: 'anonymous_service_account',
password: 'anonymous_service_account_password',
},
},
},
},
},
})
);
const usageCollection = usageCollectionPluginMock.createSetupContract();
const license = createSecurityLicense({ isLicenseAvailable: true, allowAuditLogging: false });
registerSecurityUsageCollector({ usageCollection, config, license });
const usage = await usageCollection
.getCollectorByType('security')
?.fetch(collectorFetchContext);
expect(usage).toEqual({
...DEFAULT_USAGE,
enabledAuthProviders: ['anonymous'],
anonymousCredentialType: 'username_password',
});
});
it('reports lack of anonymous credential as undefined', async () => {
const config = createSecurityConfig(ConfigSchema.validate({}));
const usageCollection = usageCollectionPluginMock.createSetupContract();
const license = createSecurityLicense({ isLicenseAvailable: true, allowAuditLogging: false });
registerSecurityUsageCollector({ usageCollection, config, license });
const usage = await usageCollection
.getCollectorByType('security')
?.fetch(collectorFetchContext);
expect(usage).toEqual({
...DEFAULT_USAGE,
enabledAuthProviders: ['basic'],
anonymousCredentialType: undefined,
});
});
it('reports the enabled anonymous credential of username and password as usernanme_password', async () => {
const config = createSecurityConfig(
ConfigSchema.validate({
authc: {
providers: {
anonymous: {
anonymous1: {
order: 1,
enabled: false,
credentials: {
apiKey: 'VnVhQ2ZHY0JDZGJrUW0tZTVhT3g6dWkybHAyYXhUTm1zeWFrdzl0dk5udw==',
},
},
anonymous2: {
order: 2,
credentials: {
username: 'anonymous_service_account',
password: 'anonymous_service_account_password',
},
},
anonymous3: {
order: 3,
enabled: false,
credentials: {
apiKey: { id: 'VuaCfGcBCdbkQm-e5aOx', key: 'ui2lp2axTNmsyakw9tvNnw' },
},
},
},
},
},
})
);
const usageCollection = usageCollectionPluginMock.createSetupContract();
const license = createSecurityLicense({ isLicenseAvailable: true, allowAuditLogging: false });
registerSecurityUsageCollector({ usageCollection, config, license });
const usage = await usageCollection
.getCollectorByType('security')
?.fetch(collectorFetchContext);
expect(usage).toEqual({
...DEFAULT_USAGE,
enabledAuthProviders: ['anonymous'],
anonymousCredentialType: 'username_password',
});
});
});
});

View file

@ -20,6 +20,7 @@ interface Usage {
sessionIdleTimeoutInMinutes: number;
sessionLifespanInMinutes: number;
sessionCleanupInMinutes: number;
anonymousCredentialType: string | undefined;
}
interface Deps {
@ -122,6 +123,13 @@ export function registerSecurityUsageCollector({ usageCollection, config, licens
'The session cleanup interval that is configured, in minutes (0 if disabled).',
},
},
anonymousCredentialType: {
type: 'keyword',
_meta: {
description:
'The credential type that is configured for the anonymous authentication provider.',
},
},
},
fetch: () => {
const { allowRbac, allowAccessAgreement, allowAuditLogging } = license.getFeatures();
@ -136,6 +144,7 @@ export function registerSecurityUsageCollector({ usageCollection, config, licens
sessionIdleTimeoutInMinutes: 0,
sessionLifespanInMinutes: 0,
sessionCleanupInMinutes: 0,
anonymousCredentialType: undefined,
};
}
@ -163,6 +172,24 @@ export function registerSecurityUsageCollector({ usageCollection, config, licens
const sessionLifespanInMinutes = sessionExpirations.lifespan?.asMinutes() ?? 0;
const sessionCleanupInMinutes = config.session.cleanupInterval?.asMinutes() ?? 0;
const anonProviders = config.authc.providers.anonymous ?? ({} as Record<string, any>);
const foundProvider = Object.entries(anonProviders).find(
([_, provider]) => !!provider.credentials && provider.enabled
);
const credElasticAnonUser = 'elasticsearch_anonymous_user';
const credApiKey = 'api_key';
const credUsernamePassword = 'username_password';
let anonymousCredentialType;
if (foundProvider) {
if (!!foundProvider[1].credentials.apiKey) anonymousCredentialType = credApiKey;
else if (foundProvider[1].credentials === credElasticAnonUser)
anonymousCredentialType = credElasticAnonUser;
else if (!!foundProvider[1].credentials.username && !!foundProvider[1].credentials.password)
anonymousCredentialType = credUsernamePassword;
}
return {
auditLoggingEnabled,
loginSelectorEnabled,
@ -173,6 +200,7 @@ export function registerSecurityUsageCollector({ usageCollection, config, licens
sessionIdleTimeoutInMinutes,
sessionLifespanInMinutes,
sessionCleanupInMinutes,
anonymousCredentialType,
};
},
});

View file

@ -12561,6 +12561,12 @@
"_meta": {
"description": "The session cleanup interval that is configured, in minutes (0 if disabled)."
}
},
"anonymousCredentialType": {
"type": "keyword",
"_meta": {
"description": "The credential type that is configured for the anonymous authentication provider."
}
}
}
},