[Stack Monitoring] Verify remote cluster client role when CCS is enabled (#140738) (#140801)

* [Stack Monitoring] Verify remote cluster client role when CCS is enabled (#129546)

* Only show UI hint if CCS is enabled

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
(cherry picked from commit f014ca46bc)

Co-authored-by: Milton Hultgren <milton.hultgren@elastic.co>
This commit is contained in:
Kibana Machine 2022-09-15 07:44:16 -06:00 committed by GitHub
parent 47e112cf25
commit 7b2eae6ae5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 56 additions and 4 deletions

View file

@ -13,6 +13,7 @@ export interface ExternalConfig {
showCgroupMetricsLogstash: boolean;
renderReactApp: boolean;
staleStatusThresholdSeconds: number;
isCcsEnabled: boolean;
}
export const ExternalConfigContext = createContext({} as ExternalConfig);

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import React, { useState } from 'react';
import React, { useContext, useState } from 'react';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
import { EuiPanel, EuiCallOut, EuiButton } from '@elastic/eui';
@ -14,8 +14,10 @@ import { Redirect } from 'react-router-dom';
import { useKibana } from '@kbn/kibana-react-plugin/public';
import { ComponentProps } from '../../route_init';
import { MonitoringStartPluginDependencies } from '../../../types';
import { ExternalConfigContext } from '../../contexts/external_config_context';
export const AccessDeniedPage: React.FC<ComponentProps> = () => {
const { isCcsEnabled } = useContext(ExternalConfigContext);
const { services } = useKibana<MonitoringStartPluginDependencies>();
const [hasAccess, setHasAccess] = useState<boolean>(false);
@ -62,6 +64,14 @@ export const AccessDeniedPage: React.FC<ComponentProps> = () => {
the monitoring cluster."
/>
</p>
{isCcsEnabled && (
<p>
<FormattedMessage
id="xpack.monitoring.accessDenied.noRemoteClusterClientDescription"
defaultMessage="Since Cross Cluster Search is enabled (`monitoring.ui.ccs.enabled` is set to `true`), make sure your cluster has the `remote_cluster_client` role on at least one node."
/>
</p>
)}
<p>
<EuiButton href="../app/home">
<FormattedMessage

View file

@ -149,6 +149,7 @@ export class MonitoringPlugin
'staleStatusThresholdSeconds',
monitoring.ui.kibana.reporting.stale_status_threshold_seconds,
],
['isCcsEnabled', monitoring.ui.ccs.enabled],
];
}

View file

@ -9,6 +9,7 @@ import { forbidden } from '@hapi/boom';
import { i18n } from '@kbn/i18n';
import { getStatusCode } from './handle_error';
import { ErrorTypes } from '../../types';
import { NO_REMOTE_CLIENT_ROLE_ERROR } from '../../routes/api/v1/check_access/check_access';
export function isAuthError(err: ErrorTypes) {
const statusCode = getStatusCode(err);
@ -30,9 +31,15 @@ export function handleAuthError(err: ErrorTypes) {
defaultMessage: 'Invalid authentication for monitoring cluster',
});
} else {
message = i18n.translate('xpack.monitoring.errors.insufficientUserErrorMessage', {
defaultMessage: 'Insufficient user permissions for monitoring data',
});
if (err.message === NO_REMOTE_CLIENT_ROLE_ERROR) {
message = i18n.translate('xpack.monitoring.errors.noRemoteClientRoleErrorMessage', {
defaultMessage: 'Cluster has no remote_cluster_client role',
});
} else {
message = i18n.translate('xpack.monitoring.errors.insufficientUserErrorMessage', {
defaultMessage: 'Insufficient user permissions for monitoring data',
});
}
}
return forbidden(message);

View file

@ -5,6 +5,7 @@
* 2.0.
*/
import Boom from '@hapi/boom';
import { verifyMonitoringAuth } from '../../../../lib/elasticsearch/verify_monitoring_auth';
import { handleError } from '../../../../lib/errors';
import { LegacyRequest, MonitoringCore } from '../../../../types';
@ -24,6 +25,11 @@ export function checkAccessRoute(server: MonitoringCore) {
const response: { has_access?: boolean } = {};
try {
await verifyMonitoringAuth(req);
if (server.config.ui.ccs.enabled) {
await verifyClusterHasRemoteClusterClientRole(req);
}
response.has_access = true; // response data is ignored
} catch (err) {
throw handleError(err, req);
@ -32,3 +38,30 @@ export function checkAccessRoute(server: MonitoringCore) {
},
});
}
interface NodesResponse {
nodes: {
[uuid: string]: {
roles: string[];
};
};
}
export const NO_REMOTE_CLIENT_ROLE_ERROR = 'Cluster has no remote_cluster_client role';
async function verifyClusterHasRemoteClusterClientRole(req: LegacyRequest) {
const { callWithRequest } = req.server.plugins.elasticsearch.getCluster('monitoring');
const response: NodesResponse = await callWithRequest(req, 'transport.request', {
method: 'GET',
path: '/_nodes',
});
for (const node of Object.values(response.nodes)) {
if (node.roles.includes('remote_cluster_client')) {
return;
}
}
throw Boom.forbidden(NO_REMOTE_CLIENT_ROLE_ERROR);
}