mirror of
https://github.com/elastic/kibana.git
synced 2025-06-27 18:51:07 -04:00
Hide remote indices role configuration when not supported (#155490)
Resolves #155389 ## Summary Adds feature flag to automatically hide remote index privileges section when not supported by cluster.
This commit is contained in:
parent
f95ebdfb31
commit
cfc01d5444
8 changed files with 130 additions and 119 deletions
|
@ -9,11 +9,12 @@ import type { HttpStart } from '@kbn/core/public';
|
|||
|
||||
import type { RoleMapping } from '../../../common/model';
|
||||
|
||||
interface CheckRoleMappingFeaturesResponse {
|
||||
export interface CheckRoleMappingFeaturesResponse {
|
||||
canManageRoleMappings: boolean;
|
||||
canUseInlineScripts: boolean;
|
||||
canUseStoredScripts: boolean;
|
||||
hasCompatibleRealms: boolean;
|
||||
canUseRemoteIndices: boolean;
|
||||
}
|
||||
|
||||
type DeleteRoleMappingsResponse = Array<{
|
||||
|
|
|
@ -140,11 +140,13 @@ function getProps({
|
|||
role,
|
||||
canManageSpaces = true,
|
||||
spacesEnabled = true,
|
||||
canUseRemoteIndices = true,
|
||||
}: {
|
||||
action: 'edit' | 'clone';
|
||||
role?: Role;
|
||||
canManageSpaces?: boolean;
|
||||
spacesEnabled?: boolean;
|
||||
canUseRemoteIndices?: boolean;
|
||||
}) {
|
||||
const rolesAPIClient = rolesAPIClientMock.create();
|
||||
rolesAPIClient.getRole.mockResolvedValue(role);
|
||||
|
@ -171,12 +173,15 @@ function getProps({
|
|||
const { fatalErrors } = coreMock.createSetup();
|
||||
const { http, docLinks, notifications } = coreMock.createStart();
|
||||
http.get.mockImplementation(async (path: any) => {
|
||||
if (!spacesEnabled) {
|
||||
throw { response: { status: 404 } }; // eslint-disable-line no-throw-literal
|
||||
}
|
||||
if (path === '/api/spaces/space') {
|
||||
if (!spacesEnabled) {
|
||||
throw { response: { status: 404 } }; // eslint-disable-line no-throw-literal
|
||||
}
|
||||
return buildSpaces();
|
||||
}
|
||||
if (path === '/internal/security/_check_role_mapping_features') {
|
||||
return { canUseRemoteIndices };
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
|
@ -265,6 +270,8 @@ describe('<EditRolePage />', () => {
|
|||
expect(wrapper.find(SpaceAwarePrivilegeSection)).toHaveLength(1);
|
||||
expect(wrapper.find('[data-test-subj="userCannotManageSpacesCallout"]')).toHaveLength(0);
|
||||
expect(wrapper.find('input[data-test-subj="roleFormNameInput"]').prop('disabled')).toBe(true);
|
||||
expect(wrapper.find('IndexPrivileges[indexType="indices"]')).toHaveLength(1);
|
||||
expect(wrapper.find('IndexPrivileges[indexType="remote_indices"]')).toHaveLength(1);
|
||||
expectReadOnlyFormButtons(wrapper);
|
||||
});
|
||||
|
||||
|
@ -291,6 +298,8 @@ describe('<EditRolePage />', () => {
|
|||
expect(wrapper.find(SpaceAwarePrivilegeSection)).toHaveLength(1);
|
||||
expect(wrapper.find('[data-test-subj="userCannotManageSpacesCallout"]')).toHaveLength(0);
|
||||
expect(wrapper.find('input[data-test-subj="roleFormNameInput"]').prop('disabled')).toBe(true);
|
||||
expect(wrapper.find('IndexPrivileges[indexType="indices"]')).toHaveLength(1);
|
||||
expect(wrapper.find('IndexPrivileges[indexType="remote_indices"]')).toHaveLength(1);
|
||||
expectSaveFormButtons(wrapper);
|
||||
});
|
||||
|
||||
|
@ -308,6 +317,8 @@ describe('<EditRolePage />', () => {
|
|||
expect(wrapper.find('input[data-test-subj="roleFormNameInput"]').prop('disabled')).toBe(
|
||||
false
|
||||
);
|
||||
expect(wrapper.find('IndexPrivileges[indexType="indices"]')).toHaveLength(1);
|
||||
expect(wrapper.find('IndexPrivileges[indexType="remote_indices"]')).toHaveLength(1);
|
||||
expectSaveFormButtons(wrapper);
|
||||
});
|
||||
|
||||
|
@ -480,6 +491,8 @@ describe('<EditRolePage />', () => {
|
|||
expect(wrapper.find(SimplePrivilegeSection)).toHaveLength(1);
|
||||
expect(wrapper.find('[data-test-subj="userCannotManageSpacesCallout"]')).toHaveLength(0);
|
||||
expect(wrapper.find('input[data-test-subj="roleFormNameInput"]').prop('disabled')).toBe(true);
|
||||
expect(wrapper.find('IndexPrivileges[indexType="indices"]')).toHaveLength(1);
|
||||
expect(wrapper.find('IndexPrivileges[indexType="remote_indices"]')).toHaveLength(1);
|
||||
expectReadOnlyFormButtons(wrapper);
|
||||
});
|
||||
|
||||
|
@ -507,6 +520,8 @@ describe('<EditRolePage />', () => {
|
|||
expect(wrapper.find(SimplePrivilegeSection)).toHaveLength(1);
|
||||
expect(wrapper.find('[data-test-subj="userCannotManageSpacesCallout"]')).toHaveLength(0);
|
||||
expect(wrapper.find('input[data-test-subj="roleFormNameInput"]').prop('disabled')).toBe(true);
|
||||
expect(wrapper.find('IndexPrivileges[indexType="indices"]')).toHaveLength(1);
|
||||
expect(wrapper.find('IndexPrivileges[indexType="remote_indices"]')).toHaveLength(1);
|
||||
expectSaveFormButtons(wrapper);
|
||||
});
|
||||
|
||||
|
@ -524,6 +539,8 @@ describe('<EditRolePage />', () => {
|
|||
expect(wrapper.find('input[data-test-subj="roleFormNameInput"]').prop('disabled')).toBe(
|
||||
false
|
||||
);
|
||||
expect(wrapper.find('IndexPrivileges[indexType="indices"]')).toHaveLength(1);
|
||||
expect(wrapper.find('IndexPrivileges[indexType="remote_indices"]')).toHaveLength(1);
|
||||
expectSaveFormButtons(wrapper);
|
||||
});
|
||||
|
||||
|
@ -612,6 +629,19 @@ describe('<EditRolePage />', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('hides remote index privileges section when not supported', async () => {
|
||||
const wrapper = mountWithIntl(
|
||||
<KibanaContextProvider services={coreStart}>
|
||||
<EditRolePage {...getProps({ action: 'edit', canUseRemoteIndices: false })} />
|
||||
</KibanaContextProvider>
|
||||
);
|
||||
|
||||
await waitForRender(wrapper);
|
||||
|
||||
expect(wrapper.find('IndexPrivileges[indexType="indices"]')).toHaveLength(1);
|
||||
expect(wrapper.find('IndexPrivileges[indexType="remote_indices"]')).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('registers fatal error if features endpoint fails unexpectedly', async () => {
|
||||
const error = { response: { status: 500 } };
|
||||
const getFeatures = jest.fn().mockRejectedValue(error);
|
||||
|
|
|
@ -21,6 +21,7 @@ import {
|
|||
} from '@elastic/eui';
|
||||
import type { ChangeEvent, FocusEvent, FunctionComponent, HTMLProps } from 'react';
|
||||
import React, { Fragment, useCallback, useEffect, useRef, useState } from 'react';
|
||||
import useAsync from 'react-use/lib/useAsync';
|
||||
|
||||
import type { IHttpFetchError } from '@kbn/core-http-browser';
|
||||
import type {
|
||||
|
@ -56,6 +57,7 @@ import {
|
|||
prepareRoleClone,
|
||||
} from '../../../../common/model';
|
||||
import { useCapabilities } from '../../../components/use_capabilities';
|
||||
import type { CheckRoleMappingFeaturesResponse } from '../../role_mappings/role_mappings_api_client';
|
||||
import type { UserAPIClient } from '../../users';
|
||||
import type { IndicesAPIClient } from '../indices_api_client';
|
||||
import { KibanaPrivileges } from '../model';
|
||||
|
@ -86,6 +88,12 @@ interface Props {
|
|||
spacesApiUi?: SpacesApiUi;
|
||||
}
|
||||
|
||||
function useFeatureCheck(http: HttpStart) {
|
||||
return useAsync(() =>
|
||||
http.get<CheckRoleMappingFeaturesResponse>('/internal/security/_check_role_mapping_features')
|
||||
);
|
||||
}
|
||||
|
||||
function useRunAsUsers(
|
||||
userAPIClient: PublicMethodsOf<UserAPIClient>,
|
||||
fatalErrors: FatalErrorsSetup
|
||||
|
@ -311,6 +319,7 @@ export const EditRolePage: FunctionComponent<Props> = ({
|
|||
const privileges = usePrivileges(privilegesAPIClient, fatalErrors);
|
||||
const spaces = useSpaces(http, fatalErrors);
|
||||
const features = useFeatures(getFeatures, fatalErrors);
|
||||
const featureCheckState = useFeatureCheck(http);
|
||||
const [role, setRole] = useRole(
|
||||
rolesAPIClient,
|
||||
fatalErrors,
|
||||
|
@ -329,7 +338,15 @@ export const EditRolePage: FunctionComponent<Props> = ({
|
|||
}
|
||||
}, [hasReadOnlyPrivileges, isEditingExistingRole]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
if (!role || !runAsUsers || !indexPatternsTitles || !privileges || !spaces || !features) {
|
||||
if (
|
||||
!role ||
|
||||
!runAsUsers ||
|
||||
!indexPatternsTitles ||
|
||||
!privileges ||
|
||||
!spaces ||
|
||||
!features ||
|
||||
!featureCheckState.value
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -457,6 +474,7 @@ export const EditRolePage: FunctionComponent<Props> = ({
|
|||
builtinESPrivileges={builtInESPrivileges}
|
||||
license={license}
|
||||
docLinks={docLinks}
|
||||
canUseRemoteIndices={featureCheckState.value?.canUseRemoteIndices}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -200,89 +200,5 @@ exports[`it renders without crashing 1`] = `
|
|||
}
|
||||
}
|
||||
/>
|
||||
<EuiSpacer />
|
||||
<EuiSpacer />
|
||||
<EuiTitle
|
||||
size="xs"
|
||||
>
|
||||
<h3>
|
||||
<FormattedMessage
|
||||
defaultMessage="Remote index privileges"
|
||||
id="xpack.security.management.editRole.elasticSearchPrivileges.remoteIndexPrivilegesTitle"
|
||||
values={Object {}}
|
||||
/>
|
||||
</h3>
|
||||
</EuiTitle>
|
||||
<EuiSpacer
|
||||
size="s"
|
||||
/>
|
||||
<EuiText
|
||||
color="subdued"
|
||||
size="s"
|
||||
>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
defaultMessage="Control access to the data in remote clusters. "
|
||||
id="xpack.security.management.editRole.elasticSearchPrivileges.controlAccessToRemoteClusterDataDescription"
|
||||
values={Object {}}
|
||||
/>
|
||||
<EuiLink
|
||||
className="editRole__learnMore"
|
||||
href="https://www.elastic.co/guide/en/elasticsearch/reference/mocked-test-branch/security-privileges.html#privileges-list-indices"
|
||||
target="_blank"
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Learn more"
|
||||
id="xpack.security.management.editRole.elasticSearchPrivileges.learnMoreLinkText"
|
||||
values={Object {}}
|
||||
/>
|
||||
</EuiLink>
|
||||
</p>
|
||||
</EuiText>
|
||||
<IndexPrivileges
|
||||
availableIndexPrivileges={
|
||||
Array [
|
||||
"all",
|
||||
"read",
|
||||
"write",
|
||||
"index",
|
||||
]
|
||||
}
|
||||
editable={true}
|
||||
indexType="remote_indices"
|
||||
indicesAPIClient={
|
||||
Object {
|
||||
"getFields": [MockFunction],
|
||||
}
|
||||
}
|
||||
license={
|
||||
Object {
|
||||
"features$": Observable {
|
||||
"_subscribe": [Function],
|
||||
},
|
||||
"getFeatures": [MockFunction],
|
||||
"hasAtLeast": [MockFunction],
|
||||
"isEnabled": [MockFunction],
|
||||
"isLicenseAvailable": [MockFunction],
|
||||
}
|
||||
}
|
||||
onChange={[MockFunction]}
|
||||
role={
|
||||
Object {
|
||||
"elasticsearch": Object {
|
||||
"cluster": Array [],
|
||||
"indices": Array [],
|
||||
"run_as": Array [],
|
||||
},
|
||||
"kibana": Array [],
|
||||
"name": "",
|
||||
}
|
||||
}
|
||||
validator={
|
||||
RoleValidator {
|
||||
"shouldValidate": undefined,
|
||||
}
|
||||
}
|
||||
/>
|
||||
</CollapsiblePanel>
|
||||
`;
|
||||
|
|
|
@ -63,8 +63,13 @@ test('it renders index privileges section', () => {
|
|||
expect(wrapper.find('IndexPrivileges[indexType="indices"]')).toHaveLength(1);
|
||||
});
|
||||
|
||||
test('it renders remote index privileges section', () => {
|
||||
test('it does not render remote index privileges section by default', () => {
|
||||
const wrapper = shallowWithIntl(<ElasticsearchPrivileges {...getProps()} />);
|
||||
expect(wrapper.find('IndexPrivileges[indexType="remote_indices"]')).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('it renders remote index privileges section when `canUseRemoteIndices` is enabled', () => {
|
||||
const wrapper = shallowWithIntl(<ElasticsearchPrivileges {...getProps()} canUseRemoteIndices />);
|
||||
expect(wrapper.find('IndexPrivileges[indexType="remote_indices"]')).toHaveLength(1);
|
||||
});
|
||||
|
||||
|
|
|
@ -40,6 +40,7 @@ interface Props {
|
|||
validator: RoleValidator;
|
||||
builtinESPrivileges: BuiltinESPrivileges;
|
||||
indexPatterns: string[];
|
||||
canUseRemoteIndices?: boolean;
|
||||
}
|
||||
|
||||
export class ElasticsearchPrivileges extends Component<Props, {}> {
|
||||
|
@ -62,6 +63,7 @@ export class ElasticsearchPrivileges extends Component<Props, {}> {
|
|||
indexPatterns,
|
||||
license,
|
||||
builtinESPrivileges,
|
||||
canUseRemoteIndices,
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
|
@ -170,37 +172,42 @@ export class ElasticsearchPrivileges extends Component<Props, {}> {
|
|||
availableIndexPrivileges={builtinESPrivileges.index}
|
||||
editable={editable}
|
||||
/>
|
||||
<EuiSpacer />
|
||||
<EuiSpacer />
|
||||
|
||||
<EuiTitle size="xs">
|
||||
<h3>
|
||||
<FormattedMessage
|
||||
id="xpack.security.management.editRole.elasticSearchPrivileges.remoteIndexPrivilegesTitle"
|
||||
defaultMessage="Remote index privileges"
|
||||
{canUseRemoteIndices && (
|
||||
<>
|
||||
<EuiSpacer />
|
||||
<EuiSpacer />
|
||||
|
||||
<EuiTitle size="xs">
|
||||
<h3>
|
||||
<FormattedMessage
|
||||
id="xpack.security.management.editRole.elasticSearchPrivileges.remoteIndexPrivilegesTitle"
|
||||
defaultMessage="Remote index privileges"
|
||||
/>
|
||||
</h3>
|
||||
</EuiTitle>
|
||||
<EuiSpacer size="s" />
|
||||
<EuiText size="s" color="subdued">
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.security.management.editRole.elasticSearchPrivileges.controlAccessToRemoteClusterDataDescription"
|
||||
defaultMessage="Control access to the data in remote clusters. "
|
||||
/>
|
||||
{this.learnMore(docLinks.links.security.indicesPrivileges)}
|
||||
</p>
|
||||
</EuiText>
|
||||
<IndexPrivileges
|
||||
indexType="remote_indices"
|
||||
role={role}
|
||||
indicesAPIClient={indicesAPIClient}
|
||||
validator={validator}
|
||||
license={license}
|
||||
onChange={onChange}
|
||||
availableIndexPrivileges={builtinESPrivileges.index}
|
||||
editable={editable}
|
||||
/>
|
||||
</h3>
|
||||
</EuiTitle>
|
||||
<EuiSpacer size="s" />
|
||||
<EuiText size="s" color="subdued">
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.security.management.editRole.elasticSearchPrivileges.controlAccessToRemoteClusterDataDescription"
|
||||
defaultMessage="Control access to the data in remote clusters. "
|
||||
/>
|
||||
{this.learnMore(docLinks.links.security.indicesPrivileges)}
|
||||
</p>
|
||||
</EuiText>
|
||||
<IndexPrivileges
|
||||
indexType="remote_indices"
|
||||
role={role}
|
||||
indicesAPIClient={indicesAPIClient}
|
||||
validator={validator}
|
||||
license={license}
|
||||
onChange={onChange}
|
||||
availableIndexPrivileges={builtinESPrivileges.index}
|
||||
editable={editable}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -21,6 +21,9 @@ interface TestOptions {
|
|||
}
|
||||
|
||||
const defaultXpackUsageResponse = {
|
||||
remote_clusters: {
|
||||
size: 0,
|
||||
},
|
||||
security: {
|
||||
realms: {
|
||||
native: {
|
||||
|
@ -94,6 +97,7 @@ describe('GET role mappings feature check', () => {
|
|||
canUseInlineScripts: true,
|
||||
canUseStoredScripts: true,
|
||||
hasCompatibleRealms: true,
|
||||
canUseRemoteIndices: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
@ -117,10 +121,31 @@ describe('GET role mappings feature check', () => {
|
|||
canUseInlineScripts: true,
|
||||
canUseStoredScripts: true,
|
||||
hasCompatibleRealms: true,
|
||||
canUseRemoteIndices: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
getFeatureCheckTest(
|
||||
'indicates canUseRemoteIndices=false when cluster does not support remote indices',
|
||||
{
|
||||
xpackUsageResponse: () => ({
|
||||
...defaultXpackUsageResponse,
|
||||
remote_clusters: undefined,
|
||||
}),
|
||||
asserts: {
|
||||
statusCode: 200,
|
||||
result: {
|
||||
canManageRoleMappings: true,
|
||||
canUseInlineScripts: true,
|
||||
canUseStoredScripts: true,
|
||||
hasCompatibleRealms: true,
|
||||
canUseRemoteIndices: false,
|
||||
},
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
getFeatureCheckTest('disallows stored scripts when disabled', {
|
||||
nodeSettingsResponse: () => ({
|
||||
nodes: {
|
||||
|
@ -140,6 +165,7 @@ describe('GET role mappings feature check', () => {
|
|||
canUseInlineScripts: true,
|
||||
canUseStoredScripts: false,
|
||||
hasCompatibleRealms: true,
|
||||
canUseRemoteIndices: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
@ -163,12 +189,14 @@ describe('GET role mappings feature check', () => {
|
|||
canUseInlineScripts: false,
|
||||
canUseStoredScripts: true,
|
||||
hasCompatibleRealms: true,
|
||||
canUseRemoteIndices: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
getFeatureCheckTest('indicates incompatible realms when only native and file are enabled', {
|
||||
xpackUsageResponse: () => ({
|
||||
...defaultXpackUsageResponse,
|
||||
security: {
|
||||
realms: {
|
||||
native: {
|
||||
|
@ -189,6 +217,7 @@ describe('GET role mappings feature check', () => {
|
|||
canUseInlineScripts: true,
|
||||
canUseStoredScripts: true,
|
||||
hasCompatibleRealms: false,
|
||||
canUseRemoteIndices: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
@ -219,6 +248,7 @@ describe('GET role mappings feature check', () => {
|
|||
canUseInlineScripts: true,
|
||||
canUseStoredScripts: true,
|
||||
hasCompatibleRealms: false,
|
||||
canUseRemoteIndices: false,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -24,6 +24,9 @@ interface NodeSettingsResponse {
|
|||
}
|
||||
|
||||
interface XPackUsageResponse {
|
||||
remote_clusters?: {
|
||||
size: number;
|
||||
};
|
||||
security: {
|
||||
realms: {
|
||||
[realmName: string]: {
|
||||
|
@ -128,6 +131,7 @@ async function getEnabledRoleMappingsFeatures(esClient: ElasticsearchClient, log
|
|||
hasCompatibleRealms,
|
||||
canUseStoredScripts,
|
||||
canUseInlineScripts,
|
||||
canUseRemoteIndices: !!xpackUsage.remote_clusters,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue