mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
[Fleet] Allow to move agent policy to another space. (#189663)
This commit is contained in:
parent
727c9b46ae
commit
c2fc468638
46 changed files with 892 additions and 110 deletions
|
@ -132,6 +132,7 @@ export const APP_API_ROUTES = {
|
|||
HEALTH_CHECK_PATTERN: `${API_ROOT}/health_check`,
|
||||
CHECK_PERMISSIONS_PATTERN: `${API_ROOT}/check-permissions`,
|
||||
GENERATE_SERVICE_TOKEN_PATTERN: `${API_ROOT}/service_tokens`,
|
||||
AGENT_POLICIES_SPACES: `${INTERNAL_ROOT}/agent_policies_spaces`,
|
||||
// deprecated since 8.0
|
||||
GENERATE_SERVICE_TOKEN_PATTERN_DEPRECATED: `${API_ROOT}/service-tokens`,
|
||||
};
|
||||
|
|
|
@ -294,6 +294,7 @@ export const appRoutesService = {
|
|||
getCheckPermissionsPath: () => APP_API_ROUTES.CHECK_PERMISSIONS_PATTERN,
|
||||
getRegenerateServiceTokenPath: () => APP_API_ROUTES.GENERATE_SERVICE_TOKEN_PATTERN,
|
||||
postHealthCheckPath: () => APP_API_ROUTES.HEALTH_CHECK_PATTERN,
|
||||
getAgentPoliciesSpacesPath: () => APP_API_ROUTES.AGENT_POLICIES_SPACES,
|
||||
};
|
||||
|
||||
export const enrollmentAPIKeyRouteService = {
|
||||
|
|
|
@ -21,6 +21,7 @@ export interface NewAgentPolicy {
|
|||
id?: string;
|
||||
name: string;
|
||||
namespace: string;
|
||||
space_ids?: string[];
|
||||
description?: string;
|
||||
is_default?: boolean;
|
||||
is_default_fleet_server?: boolean; // Optional when creating a policy
|
||||
|
@ -53,7 +54,7 @@ export interface GlobalDataTag {
|
|||
// SO definition for this type is declared in server/types/interfaces
|
||||
export interface AgentPolicy extends Omit<NewAgentPolicy, 'id'> {
|
||||
id: string;
|
||||
space_id?: string | undefined;
|
||||
space_ids?: string[] | undefined;
|
||||
status: ValueOf<AgentPolicyStatus>;
|
||||
package_policies?: PackagePolicy[];
|
||||
is_managed: boolean; // required for created policy
|
||||
|
|
|
@ -101,7 +101,7 @@ export interface UpdatePackagePolicy extends NewPackagePolicy {
|
|||
// SO definition for this type is declared in server/types/interfaces
|
||||
export interface PackagePolicy extends Omit<NewPackagePolicy, 'inputs'> {
|
||||
id: string;
|
||||
spaceId?: string;
|
||||
spaceIds?: string[];
|
||||
inputs: PackagePolicyInput[];
|
||||
version?: string;
|
||||
agents?: number;
|
||||
|
|
|
@ -34,7 +34,7 @@ export type EnrollmentSettingsFleetServerPolicy = Pick<
|
|||
| 'has_fleet_server'
|
||||
| 'fleet_server_host_id'
|
||||
| 'download_source_id'
|
||||
| 'space_id'
|
||||
| 'space_ids'
|
||||
>;
|
||||
|
||||
export interface GetEnrollmentSettingsResponse {
|
||||
|
|
|
@ -41,6 +41,8 @@ import {
|
|||
useGetAgentPolicies,
|
||||
useLicense,
|
||||
useUIExtension,
|
||||
useLink,
|
||||
useFleetStatus,
|
||||
} from '../../../../hooks';
|
||||
|
||||
import { AgentPolicyPackageBadge } from '../../../../components';
|
||||
|
@ -60,6 +62,7 @@ import {
|
|||
} from './hooks';
|
||||
|
||||
import { CustomFields } from './custom_fields';
|
||||
import { SpaceSelector } from './space_selector';
|
||||
|
||||
interface Props {
|
||||
agentPolicy: Partial<NewAgentPolicy | AgentPolicy>;
|
||||
|
@ -75,7 +78,11 @@ export const AgentPolicyAdvancedOptionsContent: React.FunctionComponent<Props> =
|
|||
validation,
|
||||
disabled = false,
|
||||
}) => {
|
||||
const useSpaceAwareness = ExperimentalFeaturesService.get()?.useSpaceAwareness ?? false;
|
||||
const { docLinks } = useStartServices();
|
||||
const { spaceId } = useFleetStatus();
|
||||
|
||||
const { getAbsolutePath } = useLink();
|
||||
const AgentTamperProtectionWrapper = useUIExtension(
|
||||
'endpoint',
|
||||
'endpoint-agent-tamper-protection'
|
||||
|
@ -257,6 +264,63 @@ export const AgentPolicyAdvancedOptionsContent: React.FunctionComponent<Props> =
|
|||
/>
|
||||
</EuiFormRow>
|
||||
</EuiDescribedFormGroup>
|
||||
{useSpaceAwareness ? (
|
||||
<EuiDescribedFormGroup
|
||||
fullWidth
|
||||
title={
|
||||
<h3>
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.agentPolicyForm.spaceFieldLabel"
|
||||
defaultMessage="Space"
|
||||
/>
|
||||
</h3>
|
||||
}
|
||||
description={
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.agentPolicyForm.spaceDescription"
|
||||
defaultMessage="Select a space for this policy or create a new one. {link}"
|
||||
values={{
|
||||
link: (
|
||||
<EuiLink
|
||||
target="_blank"
|
||||
href={getAbsolutePath('/app/management/kibana/spaces/create')}
|
||||
external
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.agentPolicyForm.createSpaceLink"
|
||||
defaultMessage="Create space"
|
||||
/>
|
||||
</EuiLink>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<EuiFormRow
|
||||
fullWidth
|
||||
key="space"
|
||||
error={
|
||||
touchedFields.description && validation.description ? validation.description : null
|
||||
}
|
||||
isDisabled={disabled}
|
||||
isInvalid={Boolean(touchedFields.description && validation.description)}
|
||||
>
|
||||
<SpaceSelector
|
||||
isDisabled={disabled}
|
||||
value={
|
||||
'space_ids' in agentPolicy && agentPolicy.space_ids
|
||||
? agentPolicy.space_ids
|
||||
: [spaceId || 'default']
|
||||
}
|
||||
onChange={(newValue) => {
|
||||
updateAgentPolicy({
|
||||
space_ids: newValue,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</EuiDescribedFormGroup>
|
||||
) : null}
|
||||
<EuiDescribedFormGroup
|
||||
fullWidth
|
||||
title={
|
||||
|
|
|
@ -0,0 +1,76 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { type EuiComboBoxOptionOption, EuiHealth } from '@elastic/eui';
|
||||
import { EuiComboBox } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import React, { useMemo } from 'react';
|
||||
|
||||
import { useAgentPoliciesSpaces } from '../../../../../../hooks';
|
||||
|
||||
export interface SpaceSelectorProps {
|
||||
value: string[];
|
||||
onChange: (newVal: string[]) => void;
|
||||
isDisabled?: boolean;
|
||||
}
|
||||
|
||||
export const SpaceSelector: React.FC<SpaceSelectorProps> = ({ value, onChange, isDisabled }) => {
|
||||
const res = useAgentPoliciesSpaces();
|
||||
|
||||
const renderOption = React.useCallback(
|
||||
(option: any, searchValue: string, contentClassName: string) => (
|
||||
<EuiHealth color={option.color}>
|
||||
<span className={contentClassName}>{option.label}</span>
|
||||
</EuiHealth>
|
||||
),
|
||||
[]
|
||||
);
|
||||
|
||||
const options: Array<EuiComboBoxOptionOption<string>> = useMemo(() => {
|
||||
return (
|
||||
res.data?.items.map((item: any) => ({
|
||||
label: item.name,
|
||||
key: item.id,
|
||||
...item,
|
||||
})) ?? []
|
||||
);
|
||||
}, [res.data]);
|
||||
|
||||
const selectedOptions: Array<EuiComboBoxOptionOption<string>> = useMemo(() => {
|
||||
if (res.isInitialLoading) {
|
||||
return [];
|
||||
}
|
||||
return value.map((v) => {
|
||||
const existingOption = options.find((opt) => opt.key === v);
|
||||
|
||||
return existingOption
|
||||
? existingOption
|
||||
: {
|
||||
label: v,
|
||||
key: v,
|
||||
};
|
||||
});
|
||||
}, [options, value, res.isInitialLoading]);
|
||||
|
||||
return (
|
||||
<EuiComboBox
|
||||
data-test-subj={'spaceSelectorComboBox'}
|
||||
aria-label={i18n.translate('xpack.fleet.agentPolicies.spaceSelectorLabel', {
|
||||
defaultMessage: 'Spaces',
|
||||
})}
|
||||
fullWidth
|
||||
options={options}
|
||||
renderOption={renderOption}
|
||||
selectedOptions={selectedOptions}
|
||||
isDisabled={res.isInitialLoading || isDisabled}
|
||||
isClearable={false}
|
||||
onChange={(newOptions) => {
|
||||
onChange(newOptions.map(({ key }) => key as string));
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
|
@ -45,6 +45,7 @@ const pickAgentPolicyKeysToSend = (agentPolicy: AgentPolicy) =>
|
|||
'name',
|
||||
'description',
|
||||
'namespace',
|
||||
'space_ids',
|
||||
'monitoring_enabled',
|
||||
'unenroll_timeout',
|
||||
'inactivity_timeout',
|
||||
|
|
|
@ -22,3 +22,4 @@ export * from './download_source';
|
|||
export * from './fleet_server_hosts';
|
||||
export * from './fleet_proxies';
|
||||
export * from './health_check';
|
||||
export * from './spaces';
|
||||
|
|
22
x-pack/plugins/fleet/public/hooks/use_request/spaces.ts
Normal file
22
x-pack/plugins/fleet/public/hooks/use_request/spaces.ts
Normal file
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
|
||||
import { API_VERSIONS, appRoutesService } from '../../../common';
|
||||
|
||||
import { sendRequestForRq } from './use_request';
|
||||
|
||||
export function useAgentPoliciesSpaces() {
|
||||
return useQuery(['fleet-get-spaces'], async () => {
|
||||
return sendRequestForRq({
|
||||
method: 'get',
|
||||
path: appRoutesService.getAgentPoliciesSpacesPath(),
|
||||
version: API_VERSIONS.internal.v1,
|
||||
});
|
||||
});
|
||||
}
|
|
@ -19,6 +19,8 @@ import { licensingMock } from '@kbn/licensing-plugin/server/mocks';
|
|||
import { encryptedSavedObjectsMock } from '@kbn/encrypted-saved-objects-plugin/server/mocks';
|
||||
import { securityMock } from '@kbn/security-plugin/server/mocks';
|
||||
import { cloudMock } from '@kbn/cloud-plugin/public/mocks';
|
||||
import { SPACES_EXTENSION_ID } from '@kbn/core-saved-objects-server';
|
||||
import type { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server';
|
||||
|
||||
import type { PackagePolicyClient } from '../services/package_policy_service';
|
||||
import type { AgentPolicyServiceInterface } from '../services';
|
||||
|
@ -59,7 +61,11 @@ export interface MockedFleetAppContext extends FleetAppContext {
|
|||
|
||||
export const createAppContextStartContractMock = (
|
||||
configOverrides: Partial<FleetConfigType> = {},
|
||||
isServerless: boolean = false
|
||||
isServerless: boolean = false,
|
||||
soClients: Partial<{
|
||||
internal?: SavedObjectsClientContract;
|
||||
withoutSpaceExtensions?: SavedObjectsClientContract;
|
||||
}> = {}
|
||||
): MockedFleetAppContext => {
|
||||
const config = {
|
||||
agents: { enabled: true, elasticsearch: {} },
|
||||
|
@ -70,12 +76,26 @@ export const createAppContextStartContractMock = (
|
|||
|
||||
const config$ = of(config);
|
||||
|
||||
const mockedSavedObject = savedObjectsServiceMock.createStartContract();
|
||||
|
||||
const internalSoClient = soClients.internal ?? savedObjectsClientMock.create();
|
||||
const internalSoClientWithoutSpaceExtension =
|
||||
soClients.withoutSpaceExtensions ?? savedObjectsClientMock.create();
|
||||
|
||||
mockedSavedObject.getScopedClient.mockImplementation((request, options) => {
|
||||
if (options?.excludedExtensions?.includes(SPACES_EXTENSION_ID)) {
|
||||
return internalSoClientWithoutSpaceExtension;
|
||||
}
|
||||
|
||||
return internalSoClient;
|
||||
});
|
||||
|
||||
return {
|
||||
elasticsearch: elasticsearchServiceMock.createStart(),
|
||||
data: dataPluginMock.createStartContract(),
|
||||
encryptedSavedObjectsStart: encryptedSavedObjectsMock.createStart(),
|
||||
encryptedSavedObjectsSetup: encryptedSavedObjectsMock.createSetup({ canEncrypt: true }),
|
||||
savedObjects: savedObjectsServiceMock.createStartContract(),
|
||||
savedObjects: mockedSavedObject,
|
||||
securityCoreStart: securityServiceMock.createStart(),
|
||||
securitySetup: securityMock.createSetup(),
|
||||
securityStart: securityMock.createStart(),
|
||||
|
@ -116,6 +136,7 @@ export const createFleetRequestHandlerContextMock = (): jest.Mocked<
|
|||
> => {
|
||||
return {
|
||||
authz: createFleetAuthzMock(),
|
||||
getAllSpaces: jest.fn(),
|
||||
agentClient: {
|
||||
asCurrentUser: agentServiceMock.createClient(),
|
||||
asInternalUser: agentServiceMock.createClient(),
|
||||
|
|
|
@ -150,6 +150,7 @@ export interface FleetStartDeps {
|
|||
telemetry?: TelemetryPluginStart;
|
||||
savedObjectsTagging: SavedObjectTaggingStart;
|
||||
taskManager: TaskManagerStartContract;
|
||||
spaces: SpacesPluginStart;
|
||||
}
|
||||
|
||||
export interface FleetAppContext {
|
||||
|
@ -257,6 +258,7 @@ export class FleetPlugin
|
|||
private kibanaInstanceId: FleetAppContext['kibanaInstanceId'];
|
||||
private httpSetup?: HttpServiceSetup;
|
||||
private securitySetup!: SecurityPluginSetup;
|
||||
private spacesPluginsStart?: SpacesPluginStart;
|
||||
private encryptedSavedObjectsSetup?: EncryptedSavedObjectsPluginSetup;
|
||||
private readonly telemetryEventsSender: TelemetryEventsSender;
|
||||
private readonly fleetStatus$: BehaviorSubject<ServiceStatus>;
|
||||
|
@ -517,6 +519,7 @@ export class FleetPlugin
|
|||
.getSavedObjects()
|
||||
.getScopedClient(request, { excludedExtensions: [SECURITY_EXTENSION_ID] });
|
||||
|
||||
const spacesPluginsStart = this.spacesPluginsStart;
|
||||
return {
|
||||
get agentClient() {
|
||||
const agentService = plugin.setupAgentService(esClient.asInternalUser, soClient);
|
||||
|
@ -554,7 +557,9 @@ export class FleetPlugin
|
|||
get spaceId() {
|
||||
return deps.spaces?.spacesService?.getSpaceId(request) ?? DEFAULT_SPACE_ID;
|
||||
},
|
||||
|
||||
getAllSpaces() {
|
||||
return spacesPluginsStart!.spacesService.createSpacesClient(request).getAll();
|
||||
},
|
||||
get limitedToPackages() {
|
||||
if (routeAuthz && routeAuthz.granted) {
|
||||
return routeAuthz.scopeDataToPackages;
|
||||
|
@ -600,6 +605,7 @@ export class FleetPlugin
|
|||
}
|
||||
|
||||
public start(core: CoreStart, plugins: FleetStartDeps): FleetStartContract {
|
||||
this.spacesPluginsStart = plugins.spaces;
|
||||
const messageSigningService = new MessageSigningService(
|
||||
this.initializerContext.logger,
|
||||
plugins.encryptedSavedObjects.getClient({
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
|
||||
import type { TypeOf } from '@kbn/config-schema';
|
||||
import type { RequestHandler, ResponseHeaders } from '@kbn/core/server';
|
||||
import type { KibanaRequest, RequestHandler, ResponseHeaders } from '@kbn/core/server';
|
||||
import pMap from 'p-map';
|
||||
import { safeDump } from 'js-yaml';
|
||||
|
||||
|
@ -28,6 +28,7 @@ import type {
|
|||
FleetRequestHandler,
|
||||
BulkGetAgentPoliciesRequestSchema,
|
||||
AgentPolicy,
|
||||
FleetRequestHandlerContext,
|
||||
} from '../../types';
|
||||
|
||||
import type {
|
||||
|
@ -47,8 +48,10 @@ import {
|
|||
defaultFleetErrorHandler,
|
||||
AgentPolicyNotFoundError,
|
||||
FleetUnauthorizedError,
|
||||
FleetError,
|
||||
} from '../../errors';
|
||||
import { createAgentPolicyWithPackages } from '../../services/agent_policy_create';
|
||||
import { updateAgentPolicySpaces } from '../../services/spaces/agent_policy';
|
||||
|
||||
export async function populateAssignedAgentsCount(
|
||||
agentClient: AgentClient,
|
||||
|
@ -97,6 +100,24 @@ function sanitizeItemForReadAgentOnly(item: AgentPolicy): AgentPolicy {
|
|||
};
|
||||
}
|
||||
|
||||
export async function checkAgentPoliciesAllPrivilegesForSpaces(
|
||||
request: KibanaRequest,
|
||||
context: FleetRequestHandlerContext,
|
||||
spaceIds: string[]
|
||||
) {
|
||||
const security = appContextService.getSecurity();
|
||||
const spaces = await (await context.fleet).getAllSpaces();
|
||||
const allSpaceId = spaces.map((s) => s.id);
|
||||
const res = await security.authz.checkPrivilegesWithRequest(request).atSpaces(allSpaceId, {
|
||||
kibana: [security.authz.actions.api.get(`fleet-agent-policies-all`)],
|
||||
});
|
||||
|
||||
return allSpaceId.filter(
|
||||
(id) =>
|
||||
res.privileges.kibana.find((privilege) => privilege.resource === id)?.authorized ?? false
|
||||
);
|
||||
}
|
||||
|
||||
export const getAgentPoliciesHandler: FleetRequestHandler<
|
||||
undefined,
|
||||
TypeOf<typeof GetAgentPoliciesRequestSchema.query>
|
||||
|
@ -218,26 +239,54 @@ export const createAgentPolicyHandler: FleetRequestHandler<
|
|||
const user = appContextService.getSecurityCore().authc.getCurrentUser(request) || undefined;
|
||||
const withSysMonitoring = request.query.sys_monitoring ?? false;
|
||||
const monitoringEnabled = request.body.monitoring_enabled;
|
||||
const { has_fleet_server: hasFleetServer, force, ...newPolicy } = request.body;
|
||||
const {
|
||||
has_fleet_server: hasFleetServer,
|
||||
force,
|
||||
space_ids: spaceIds,
|
||||
...newPolicy
|
||||
} = request.body;
|
||||
const spaceId = fleetContext.spaceId;
|
||||
const authorizationHeader = HTTPAuthorizationHeader.parseFromRequest(request, user?.username);
|
||||
|
||||
try {
|
||||
let authorizedSpaces: string[] | undefined;
|
||||
if (spaceIds?.length) {
|
||||
authorizedSpaces = await checkAgentPoliciesAllPrivilegesForSpaces(request, context, spaceIds);
|
||||
for (const requestedSpaceId of spaceIds) {
|
||||
if (!authorizedSpaces.includes(requestedSpaceId)) {
|
||||
throw new FleetError(
|
||||
`No enough permissions to create policies in space ${requestedSpaceId}`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const agentPolicy = await createAgentPolicyWithPackages({
|
||||
soClient,
|
||||
esClient,
|
||||
newPolicy,
|
||||
hasFleetServer,
|
||||
withSysMonitoring,
|
||||
monitoringEnabled,
|
||||
spaceId,
|
||||
user,
|
||||
authorizationHeader,
|
||||
force,
|
||||
});
|
||||
|
||||
const body: CreateAgentPolicyResponse = {
|
||||
item: await createAgentPolicyWithPackages({
|
||||
soClient,
|
||||
esClient,
|
||||
newPolicy,
|
||||
hasFleetServer,
|
||||
withSysMonitoring,
|
||||
monitoringEnabled,
|
||||
spaceId,
|
||||
user,
|
||||
authorizationHeader,
|
||||
force,
|
||||
}),
|
||||
item: agentPolicy,
|
||||
};
|
||||
|
||||
if (spaceIds && spaceIds.length > 1 && authorizedSpaces) {
|
||||
await updateAgentPolicySpaces({
|
||||
agentPolicyId: agentPolicy.id,
|
||||
currentSpaceId: spaceId,
|
||||
newSpaceIds: spaceIds,
|
||||
authorizedSpaces,
|
||||
});
|
||||
}
|
||||
|
||||
return response.ok({
|
||||
body,
|
||||
});
|
||||
|
@ -259,20 +308,36 @@ export const updateAgentPolicyHandler: FleetRequestHandler<
|
|||
> = async (context, request, response) => {
|
||||
const coreContext = await context.core;
|
||||
const fleetContext = await context.fleet;
|
||||
const soClient = coreContext.savedObjects.client;
|
||||
const esClient = coreContext.elasticsearch.client.asInternalUser;
|
||||
const user = appContextService.getSecurityCore().authc.getCurrentUser(request) || undefined;
|
||||
const { force, ...data } = request.body;
|
||||
const { force, space_ids: spaceIds, ...data } = request.body;
|
||||
|
||||
let spaceId = fleetContext.spaceId;
|
||||
|
||||
const spaceId = fleetContext.spaceId;
|
||||
try {
|
||||
if (spaceIds?.length) {
|
||||
const authorizedSpaces = await checkAgentPoliciesAllPrivilegesForSpaces(
|
||||
request,
|
||||
context,
|
||||
spaceIds
|
||||
);
|
||||
await updateAgentPolicySpaces({
|
||||
agentPolicyId: request.params.agentPolicyId,
|
||||
currentSpaceId: spaceId,
|
||||
newSpaceIds: spaceIds,
|
||||
authorizedSpaces,
|
||||
});
|
||||
|
||||
spaceId = spaceIds[0];
|
||||
}
|
||||
const agentPolicy = await agentPolicyService.update(
|
||||
soClient,
|
||||
appContextService.getInternalUserSOClientForSpaceId(spaceId),
|
||||
esClient,
|
||||
request.params.agentPolicyId,
|
||||
data,
|
||||
{ force, user, spaceId }
|
||||
);
|
||||
|
||||
const body: UpdateAgentPolicyResponse = { item: agentPolicy };
|
||||
return response.ok({
|
||||
body,
|
||||
|
|
|
@ -151,6 +151,35 @@ export const generateServiceTokenHandler: RequestHandler<
|
|||
}
|
||||
};
|
||||
|
||||
export const getAgentPoliciesSpacesHandler: FleetRequestHandler<
|
||||
null,
|
||||
null,
|
||||
TypeOf<typeof GenerateServiceTokenRequestSchema.body>
|
||||
> = async (context, request, response) => {
|
||||
try {
|
||||
const spaces = await (await context.fleet).getAllSpaces();
|
||||
const security = appContextService.getSecurity();
|
||||
const spaceIds = spaces.map(({ id }) => id);
|
||||
const res = await security.authz.checkPrivilegesWithRequest(request).atSpaces(spaceIds, {
|
||||
kibana: [security.authz.actions.api.get(`fleet-agent-policies-all`)],
|
||||
});
|
||||
|
||||
const authorizedSpaces = spaces.filter(
|
||||
(space) =>
|
||||
res.privileges.kibana.find((privilege) => privilege.resource === space.id)?.authorized ??
|
||||
false
|
||||
);
|
||||
|
||||
return response.ok({
|
||||
body: {
|
||||
items: authorizedSpaces,
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
return defaultFleetErrorHandler({ error, response });
|
||||
}
|
||||
};
|
||||
|
||||
const serviceTokenBodyValidation = (data: any, validationResult: RouteValidationResultFactory) => {
|
||||
const { ok } = validationResult;
|
||||
if (!data) {
|
||||
|
@ -192,6 +221,22 @@ export const registerRoutes = (router: FleetAuthzRouter, config: FleetConfigType
|
|||
getCheckPermissionsHandler
|
||||
);
|
||||
|
||||
router.versioned
|
||||
.get({
|
||||
path: APP_API_ROUTES.AGENT_POLICIES_SPACES,
|
||||
access: 'internal',
|
||||
fleetAuthz: {
|
||||
fleet: { allAgentPolicies: true },
|
||||
},
|
||||
})
|
||||
.addVersion(
|
||||
{
|
||||
version: API_VERSIONS.internal.v1,
|
||||
validate: {},
|
||||
},
|
||||
getAgentPoliciesSpacesHandler
|
||||
);
|
||||
|
||||
router.versioned
|
||||
.post({
|
||||
path: APP_API_ROUTES.GENERATE_SERVICE_TOKEN_PATTERN,
|
||||
|
|
|
@ -115,7 +115,7 @@ export const getFleetServerOrAgentPolicies = async (
|
|||
has_fleet_server: policy.has_fleet_server,
|
||||
fleet_server_host_id: policy.fleet_server_host_id,
|
||||
download_source_id: policy.download_source_id,
|
||||
space_id: policy.space_id,
|
||||
space_ids: policy.space_ids,
|
||||
});
|
||||
|
||||
// If an agent policy is specified, return only that policy
|
||||
|
|
|
@ -49,6 +49,7 @@ describe('FleetSetupHandler', () => {
|
|||
uninstallTokenService: {
|
||||
asCurrentUser: createUninstallTokenServiceMock(),
|
||||
},
|
||||
getAllSpaces: jest.fn(),
|
||||
agentClient: {
|
||||
asCurrentUser: agentServiceMock.createClient(),
|
||||
asInternalUser: agentServiceMock.createClient(),
|
||||
|
@ -136,6 +137,7 @@ describe('FleetStatusHandler', () => {
|
|||
uninstallTokenService: {
|
||||
asCurrentUser: createUninstallTokenServiceMock(),
|
||||
},
|
||||
getAllSpaces: jest.fn(),
|
||||
agentClient: {
|
||||
asCurrentUser: agentServiceMock.createClient(),
|
||||
asInternalUser: agentServiceMock.createClient(),
|
||||
|
|
|
@ -199,8 +199,8 @@ export async function getFullAgentPolicy(
|
|||
},
|
||||
};
|
||||
|
||||
if (agentPolicy.space_id) {
|
||||
fullAgentPolicy.namespaces = [agentPolicy.space_id];
|
||||
if (agentPolicy.space_ids) {
|
||||
fullAgentPolicy.namespaces = agentPolicy.space_ids;
|
||||
}
|
||||
|
||||
const packagePoliciesByOutputId = Object.keys(fullAgentPolicy.outputs).reduce(
|
||||
|
|
|
@ -40,7 +40,7 @@ export const mapAgentPolicySavedObjectToAgentPolicy = ({
|
|||
return {
|
||||
id,
|
||||
version,
|
||||
space_id: namespaces?.[0] ? namespaces?.[0] : undefined,
|
||||
space_ids: namespaces,
|
||||
description,
|
||||
is_default,
|
||||
is_default_fleet_server,
|
||||
|
|
|
@ -1502,12 +1502,17 @@ describe('Agent policy', () => {
|
|||
|
||||
it('should return empty array if no policies with inactivity timeouts', async () => {
|
||||
const mockSoClient = createMockSoClientThatReturns([]);
|
||||
expect(await agentPolicyService.getInactivityTimeouts(mockSoClient)).toEqual([]);
|
||||
mockedAppContextService.getInternalUserSOClientWithoutSpaceExtension.mockReturnValueOnce(
|
||||
mockSoClient
|
||||
);
|
||||
expect(await agentPolicyService.getInactivityTimeouts()).toEqual([]);
|
||||
});
|
||||
it('should return single inactivity timeout', async () => {
|
||||
const mockSoClient = createMockSoClientThatReturns([createPolicySO('policy1', 1000)]);
|
||||
|
||||
expect(await agentPolicyService.getInactivityTimeouts(mockSoClient)).toEqual([
|
||||
mockedAppContextService.getInternalUserSOClientWithoutSpaceExtension.mockReturnValueOnce(
|
||||
mockSoClient
|
||||
);
|
||||
expect(await agentPolicyService.getInactivityTimeouts()).toEqual([
|
||||
{ inactivityTimeout: 1000, policyIds: ['policy1'] },
|
||||
]);
|
||||
});
|
||||
|
@ -1516,8 +1521,11 @@ describe('Agent policy', () => {
|
|||
createPolicySO('policy1', 1000),
|
||||
createPolicySO('policy2', 1000),
|
||||
]);
|
||||
mockedAppContextService.getInternalUserSOClientWithoutSpaceExtension.mockReturnValueOnce(
|
||||
mockSoClient
|
||||
);
|
||||
|
||||
expect(await agentPolicyService.getInactivityTimeouts(mockSoClient)).toEqual([
|
||||
expect(await agentPolicyService.getInactivityTimeouts()).toEqual([
|
||||
{ inactivityTimeout: 1000, policyIds: ['policy1', 'policy2'] },
|
||||
]);
|
||||
});
|
||||
|
@ -1527,8 +1535,10 @@ describe('Agent policy', () => {
|
|||
createPolicySO('policy2', 1000),
|
||||
createPolicySO('policy3', 2000),
|
||||
]);
|
||||
|
||||
expect(await agentPolicyService.getInactivityTimeouts(mockSoClient)).toEqual([
|
||||
mockedAppContextService.getInternalUserSOClientWithoutSpaceExtension.mockReturnValueOnce(
|
||||
mockSoClient
|
||||
);
|
||||
expect(await agentPolicyService.getInactivityTimeouts()).toEqual([
|
||||
{ inactivityTimeout: 1000, policyIds: ['policy1', 'policy2'] },
|
||||
{ inactivityTimeout: 2000, policyIds: ['policy3'] },
|
||||
]);
|
||||
|
|
|
@ -478,7 +478,10 @@ class AgentPolicyService {
|
|||
return {
|
||||
...options,
|
||||
id: id.id,
|
||||
namespaces: id.spaceId ? [id.spaceId] : undefined,
|
||||
namespaces:
|
||||
savedObjectType === AGENT_POLICY_SAVED_OBJECT_TYPE && id.spaceId
|
||||
? [id.spaceId]
|
||||
: undefined,
|
||||
type: savedObjectType,
|
||||
};
|
||||
});
|
||||
|
@ -591,16 +594,12 @@ class AgentPolicyService {
|
|||
(await packagePolicyService.findAllForAgentPolicy(soClient, agentPolicySO.id)) || [];
|
||||
}
|
||||
if (options.withAgentCount) {
|
||||
await getAgentsByKuery(
|
||||
appContextService.getInternalUserESClient(),
|
||||
appContextService.getInternalUserSOClientForSpaceId(agentPolicy.space_id),
|
||||
{
|
||||
showInactive: true,
|
||||
perPage: 0,
|
||||
page: 1,
|
||||
kuery: `${AGENTS_PREFIX}.policy_id:${agentPolicy.id}`,
|
||||
}
|
||||
).then(({ total }) => (agentPolicy.agents = total));
|
||||
await getAgentsByKuery(appContextService.getInternalUserESClient(), soClient, {
|
||||
showInactive: true,
|
||||
perPage: 0,
|
||||
page: 1,
|
||||
kuery: `${AGENTS_PREFIX}.policy_id:${agentPolicy.id}`,
|
||||
}).then(({ total }) => (agentPolicy.agents = total));
|
||||
} else {
|
||||
agentPolicy.agents = 0;
|
||||
}
|
||||
|
@ -1519,16 +1518,19 @@ class AgentPolicyService {
|
|||
);
|
||||
}
|
||||
|
||||
public async getInactivityTimeouts(
|
||||
soClient: SavedObjectsClientContract
|
||||
): Promise<Array<{ policyIds: string[]; inactivityTimeout: number }>> {
|
||||
public async getInactivityTimeouts(): Promise<
|
||||
Array<{ policyIds: string[]; inactivityTimeout: number }>
|
||||
> {
|
||||
const savedObjectType = await getAgentPolicySavedObjectType();
|
||||
const findRes = await soClient.find<AgentPolicySOAttributes>({
|
||||
const internalSoClientWithoutSpaceExtension =
|
||||
appContextService.getInternalUserSOClientWithoutSpaceExtension();
|
||||
const findRes = await internalSoClientWithoutSpaceExtension.find<AgentPolicySOAttributes>({
|
||||
type: savedObjectType,
|
||||
page: 1,
|
||||
perPage: SO_SEARCH_LIMIT,
|
||||
filter: `${savedObjectType}.attributes.inactivity_timeout > 0`,
|
||||
fields: [`inactivity_timeout`],
|
||||
namespaces: ['*'],
|
||||
});
|
||||
|
||||
const groupedResults = groupBy(findRes.saved_objects, (so) => so.attributes.inactivity_timeout);
|
||||
|
|
|
@ -168,7 +168,7 @@ export function _buildStatusRuntimeField(opts: {
|
|||
// pathPrefix is used by the endpoint team currently to run
|
||||
// agent queries against the endpoint metadata index
|
||||
export async function buildAgentStatusRuntimeField(
|
||||
soClient: SavedObjectsClientContract,
|
||||
soClient: SavedObjectsClientContract, // Deprecated, it's now using an internal client
|
||||
pathPrefix?: string
|
||||
) {
|
||||
const config = appContextService.getConfig();
|
||||
|
@ -182,7 +182,7 @@ export async function buildAgentStatusRuntimeField(
|
|||
}
|
||||
const maxAgentPoliciesWithInactivityTimeout =
|
||||
config?.developer?.maxAgentPoliciesWithInactivityTimeout;
|
||||
const inactivityTimeouts = await agentPolicyService.getInactivityTimeouts(soClient);
|
||||
const inactivityTimeouts = await agentPolicyService.getInactivityTimeouts();
|
||||
|
||||
return _buildStatusRuntimeField({
|
||||
inactivityTimeouts,
|
||||
|
|
|
@ -57,7 +57,9 @@ describe('Agents CRUD test', () => {
|
|||
closePointInTime: jest.fn(),
|
||||
} as unknown as ElasticsearchClient;
|
||||
|
||||
mockContract = createAppContextStartContractMock();
|
||||
mockContract = createAppContextStartContractMock({}, false, {
|
||||
withoutSpaceExtensions: soClientMock,
|
||||
});
|
||||
appContextService.start(mockContract);
|
||||
});
|
||||
|
||||
|
|
|
@ -16,7 +16,12 @@ import { createClientMock } from './action.mock';
|
|||
|
||||
describe('reassignAgent', () => {
|
||||
beforeEach(async () => {
|
||||
appContextService.start(createAppContextStartContractMock());
|
||||
const { soClient } = createClientMock();
|
||||
appContextService.start(
|
||||
createAppContextStartContractMock({}, false, {
|
||||
withoutSpaceExtensions: soClient,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
|
|
|
@ -14,7 +14,12 @@ import { bulkRequestDiagnostics, requestDiagnostics } from './request_diagnostic
|
|||
|
||||
describe('requestDiagnostics', () => {
|
||||
beforeEach(async () => {
|
||||
appContextService.start(createAppContextStartContractMock());
|
||||
const { soClient } = createClientMock();
|
||||
appContextService.start(
|
||||
createAppContextStartContractMock({}, false, {
|
||||
withoutSpaceExtensions: soClient,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
|
|
|
@ -15,7 +15,23 @@ import { getAgentStatusForAgentPolicy } from './status';
|
|||
|
||||
describe('getAgentStatusForAgentPolicy', () => {
|
||||
beforeEach(async () => {
|
||||
appContextService.start(createAppContextStartContractMock());
|
||||
const soClient = {
|
||||
find: jest.fn().mockResolvedValue({
|
||||
saved_objects: [
|
||||
{
|
||||
id: 'agentPolicyId',
|
||||
attributes: {
|
||||
name: 'Policy 1',
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
};
|
||||
appContextService.start(
|
||||
createAppContextStartContractMock({}, false, {
|
||||
withoutSpaceExtensions: soClient as any,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
|
|
|
@ -27,7 +27,12 @@ const mockedInvalidateAPIKeys = invalidateAPIKeys as jest.MockedFunction<typeof
|
|||
|
||||
describe('unenroll', () => {
|
||||
beforeEach(async () => {
|
||||
appContextService.start(createAppContextStartContractMock());
|
||||
const { soClient } = createClientMock();
|
||||
appContextService.start(
|
||||
createAppContextStartContractMock({}, false, {
|
||||
withoutSpaceExtensions: soClient,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
|
|
|
@ -36,7 +36,12 @@ jest.mock('./action_status', () => {
|
|||
|
||||
describe('sendUpgradeAgentsActions (plural)', () => {
|
||||
beforeEach(async () => {
|
||||
appContextService.start(createAppContextStartContractMock());
|
||||
const { soClient } = createClientMock();
|
||||
appContextService.start(
|
||||
createAppContextStartContractMock({}, false, {
|
||||
withoutSpaceExtensions: soClient,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
|
|
|
@ -11,8 +11,8 @@ import { loggerMock } from '@kbn/logging-mocks';
|
|||
import type { Logger } from '@kbn/core/server';
|
||||
import { securityMock } from '@kbn/security-plugin/server/mocks';
|
||||
|
||||
import type { AgentPolicy } from '../../../common';
|
||||
import { ENROLLMENT_API_KEYS_INDEX } from '../../constants';
|
||||
|
||||
import { agentPolicyService } from '../agent_policy';
|
||||
import { auditLoggingService } from '../audit_logging';
|
||||
import { appContextService } from '../app_context';
|
||||
|
@ -99,8 +99,8 @@ describe('enrollment api keys', () => {
|
|||
|
||||
mockedAgentPolicyService.get.mockResolvedValue({
|
||||
id: 'test-agent-policy',
|
||||
space_id: 'test123',
|
||||
} as any);
|
||||
space_ids: ['test123'],
|
||||
} as AgentPolicy);
|
||||
|
||||
await generateEnrollmentAPIKey(soClient, esClient, {
|
||||
name: 'test-api-key',
|
||||
|
@ -117,6 +117,40 @@ describe('enrollment api keys', () => {
|
|||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should set namespaces if agent policy specify mulitple space IDs', async () => {
|
||||
const soClient = savedObjectsClientMock.create();
|
||||
const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser;
|
||||
|
||||
esClient.create.mockResolvedValue({
|
||||
_id: 'test-enrollment-api-key-id',
|
||||
} as any);
|
||||
|
||||
esClient.security.createApiKey.mockResolvedValue({
|
||||
api_key: 'test-api-key-value',
|
||||
id: 'test-api-key-id',
|
||||
} as any);
|
||||
|
||||
mockedAgentPolicyService.get.mockResolvedValue({
|
||||
id: 'test-agent-policy',
|
||||
space_ids: ['test123', 'test456'],
|
||||
} as AgentPolicy);
|
||||
|
||||
await generateEnrollmentAPIKey(soClient, esClient, {
|
||||
name: 'test-api-key',
|
||||
expiration: '7d',
|
||||
agentPolicyId: 'test-agent-policy',
|
||||
forceRecreate: true,
|
||||
});
|
||||
|
||||
expect(esClient.create).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
body: expect.objectContaining({
|
||||
namespaces: ['test123', 'test456'],
|
||||
}),
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('deleteEnrollmentApiKey', () => {
|
||||
|
|
|
@ -313,7 +313,7 @@ export async function generateEnrollmentAPIKey(
|
|||
api_key: apiKey,
|
||||
name,
|
||||
policy_id: agentPolicyId,
|
||||
namespaces: agentPolicy?.space_id ? [agentPolicy?.space_id] : undefined,
|
||||
namespaces: agentPolicy?.space_ids,
|
||||
created_at: new Date().toISOString(),
|
||||
};
|
||||
|
||||
|
|
|
@ -35,15 +35,15 @@ export const getFleetServerPolicies = async (
|
|||
});
|
||||
|
||||
// Extract associated fleet server agent policy IDs
|
||||
const fleetServerAgentPolicyIds = fleetServerPackagePolicies.items.flatMap((p) =>
|
||||
p.policy_ids?.map((id) => ({ id, spaceId: p.spaceId } ?? []))
|
||||
);
|
||||
const fleetServerAgentPolicyIds = fleetServerPackagePolicies.items.flatMap((p) => {
|
||||
return p.policy_ids?.map((id) => ({ id, spaceId: p.spaceIds?.[0] ?? DEFAULT_SPACE_ID } ?? []));
|
||||
});
|
||||
|
||||
// Retrieve associated agent policies
|
||||
const fleetServerAgentPolicies = fleetServerAgentPolicyIds.length
|
||||
? await agentPolicyService.getByIDs(
|
||||
soClient,
|
||||
uniqBy(fleetServerAgentPolicyIds, (p) => `${p?.spaceId ?? ''}:${p.id}`)
|
||||
uniqBy(fleetServerAgentPolicyIds, (p) => p.id)
|
||||
)
|
||||
: [];
|
||||
|
||||
|
@ -58,7 +58,7 @@ export const getFleetServerPolicies = async (
|
|||
export const hasFleetServersForPolicies = async (
|
||||
esClient: ElasticsearchClient,
|
||||
soClient: SavedObjectsClientContract,
|
||||
agentPolicies: Array<Pick<AgentPolicy, 'id' | 'space_id'>>,
|
||||
agentPolicies: Array<Pick<AgentPolicy, 'id' | 'space_ids'>>,
|
||||
activeOnly: boolean = false
|
||||
): Promise<boolean> => {
|
||||
if (agentPolicies.length > 0) {
|
||||
|
@ -67,10 +67,10 @@ export const hasFleetServersForPolicies = async (
|
|||
soClient,
|
||||
undefined,
|
||||
agentPolicies
|
||||
.map(({ id, space_id: spaceId }) => {
|
||||
.map(({ id, space_ids: spaceIds }) => {
|
||||
const space =
|
||||
spaceId && spaceId !== DEFAULT_SPACE_ID
|
||||
? `namespaces:"${spaceId}"`
|
||||
spaceIds?.[0] && spaceIds?.[0] !== DEFAULT_SPACE_ID
|
||||
? `namespaces:"${spaceIds?.[0]}"`
|
||||
: `not namespaces:* or namespaces:"${DEFAULT_SPACE_ID}"`;
|
||||
|
||||
return `(policy_id:${id} and (${space}))`;
|
||||
|
|
|
@ -804,7 +804,7 @@ class PackagePolicyClientImpl implements PackagePolicyClient {
|
|||
id: packagePolicySO.id,
|
||||
version: packagePolicySO.version,
|
||||
...packagePolicySO.attributes,
|
||||
spaceId: packagePolicySO.namespaces?.[0],
|
||||
spaceIds: packagePolicySO.namespaces,
|
||||
})),
|
||||
total: packagePolicies?.total,
|
||||
page,
|
||||
|
|
111
x-pack/plugins/fleet/server/services/spaces/agent_policy.ts
Normal file
111
x-pack/plugins/fleet/server/services/spaces/agent_policy.ts
Normal file
|
@ -0,0 +1,111 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import deepEqual from 'fast-deep-equal';
|
||||
|
||||
import { DEFAULT_SPACE_ID } from '@kbn/spaces-plugin/common';
|
||||
|
||||
import {
|
||||
AGENTS_INDEX,
|
||||
AGENT_POLICY_SAVED_OBJECT_TYPE,
|
||||
PACKAGE_POLICY_SAVED_OBJECT_TYPE,
|
||||
} from '../../../common/constants';
|
||||
|
||||
import { appContextService } from '../app_context';
|
||||
import { agentPolicyService } from '../agent_policy';
|
||||
import { ENROLLMENT_API_KEYS_INDEX } from '../../constants';
|
||||
import { packagePolicyService } from '../package_policy';
|
||||
import { FleetError } from '../../errors';
|
||||
|
||||
import { isSpaceAwarenessEnabled } from './helpers';
|
||||
|
||||
export async function updateAgentPolicySpaces({
|
||||
agentPolicyId,
|
||||
currentSpaceId,
|
||||
newSpaceIds,
|
||||
authorizedSpaces,
|
||||
}: {
|
||||
agentPolicyId: string;
|
||||
currentSpaceId: string;
|
||||
newSpaceIds: string[];
|
||||
authorizedSpaces: string[];
|
||||
}) {
|
||||
const useSpaceAwareness = await isSpaceAwarenessEnabled();
|
||||
if (!useSpaceAwareness || !newSpaceIds || newSpaceIds.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const esClient = appContextService.getInternalUserESClient();
|
||||
const soClient = appContextService.getInternalUserSOClientWithoutSpaceExtension();
|
||||
|
||||
const currentSpaceSoClient = appContextService.getInternalUserSOClientForSpaceId(currentSpaceId);
|
||||
const existingPolicy = await agentPolicyService.get(currentSpaceSoClient, agentPolicyId);
|
||||
|
||||
const existingPackagePolicies = await packagePolicyService.findAllForAgentPolicy(
|
||||
currentSpaceSoClient,
|
||||
agentPolicyId
|
||||
);
|
||||
|
||||
if (deepEqual(existingPolicy?.space_ids?.sort() ?? [DEFAULT_SPACE_ID], newSpaceIds.sort())) {
|
||||
return;
|
||||
}
|
||||
|
||||
const spacesToAdd = newSpaceIds.filter(
|
||||
(spaceId) => !existingPolicy?.space_ids?.includes(spaceId) ?? true
|
||||
);
|
||||
const spacesToRemove =
|
||||
existingPolicy?.space_ids?.filter((spaceId) => !newSpaceIds.includes(spaceId) ?? true) ?? [];
|
||||
|
||||
// Privileges check
|
||||
for (const spaceId of spacesToAdd) {
|
||||
if (!authorizedSpaces.includes(spaceId)) {
|
||||
throw new FleetError(`No enough permissions to create policies in space ${spaceId}`);
|
||||
}
|
||||
}
|
||||
|
||||
for (const spaceId of spacesToRemove) {
|
||||
if (!authorizedSpaces.includes(spaceId)) {
|
||||
throw new FleetError(`No enough permissions to remove policies from space ${spaceId}`);
|
||||
}
|
||||
}
|
||||
|
||||
const res = await soClient.updateObjectsSpaces(
|
||||
[
|
||||
{
|
||||
id: agentPolicyId,
|
||||
type: AGENT_POLICY_SAVED_OBJECT_TYPE,
|
||||
},
|
||||
...existingPackagePolicies.map(({ id }) => ({
|
||||
id,
|
||||
type: PACKAGE_POLICY_SAVED_OBJECT_TYPE,
|
||||
})),
|
||||
],
|
||||
spacesToAdd,
|
||||
spacesToRemove,
|
||||
{ refresh: 'wait_for', namespace: currentSpaceId }
|
||||
);
|
||||
|
||||
for (const soRes of res.objects) {
|
||||
if (soRes.error) {
|
||||
throw soRes.error;
|
||||
}
|
||||
}
|
||||
|
||||
// Update fleet server index agents, enrollment api keys
|
||||
await esClient.updateByQuery({
|
||||
index: ENROLLMENT_API_KEYS_INDEX,
|
||||
script: `ctx._source.namespaces = [${newSpaceIds.map((spaceId) => `"${spaceId}"`).join(',')}]`,
|
||||
ignore_unavailable: true,
|
||||
refresh: true,
|
||||
});
|
||||
await esClient.updateByQuery({
|
||||
index: AGENTS_INDEX,
|
||||
script: `ctx._source.namespaces = [${newSpaceIds.map((spaceId) => `"${spaceId}"`).join(',')}]`,
|
||||
ignore_unavailable: true,
|
||||
refresh: true,
|
||||
});
|
||||
}
|
|
@ -41,6 +41,11 @@ function isInteger(n: number) {
|
|||
|
||||
export const AgentPolicyBaseSchema = {
|
||||
id: schema.maybe(schema.string()),
|
||||
space_ids: schema.maybe(
|
||||
schema.arrayOf(schema.string(), {
|
||||
minSize: 1,
|
||||
})
|
||||
),
|
||||
name: schema.string({ minLength: 1, validate: validateNonEmptyString }),
|
||||
namespace: AgentPolicyNamespaceSchema,
|
||||
description: schema.maybe(schema.string()),
|
||||
|
|
|
@ -13,6 +13,7 @@ import type {
|
|||
SavedObjectsClientContract,
|
||||
IRouter,
|
||||
} from '@kbn/core/server';
|
||||
import type { GetSpaceResult } from '@kbn/spaces-plugin/common';
|
||||
|
||||
import type { FleetAuthz } from '../../common/authz';
|
||||
import type { AgentClient } from '../services';
|
||||
|
@ -43,6 +44,7 @@ export type FleetRequestHandlerContext = CustomRequestHandlerContext<{
|
|||
readonly internalSoClient: SavedObjectsClientContract;
|
||||
|
||||
spaceId: string;
|
||||
getAllSpaces(): Promise<GetSpaceResult[]>;
|
||||
/**
|
||||
* If data is to be limited to the list of integration package names. This will be set when
|
||||
* authz to the API was granted only based on Package Privileges.
|
||||
|
|
|
@ -17,6 +17,8 @@ import {
|
|||
httpServiceMock,
|
||||
savedObjectsClientMock,
|
||||
} from '@kbn/core/server/mocks';
|
||||
import { createAppContextStartContractMock as fleetCreateAppContextStartContractMock } from '@kbn/fleet-plugin/server/mocks';
|
||||
import { appContextService as fleetAppContextService } from '@kbn/fleet-plugin/server/services';
|
||||
import type { HostInfo, MetadataListResponse } from '../../../../common/endpoint/types';
|
||||
import { HostStatus } from '../../../../common/endpoint/types';
|
||||
import { registerEndpointRoutes } from '.';
|
||||
|
@ -135,6 +137,11 @@ describe('test endpoint routes', () => {
|
|||
page: 1,
|
||||
per_page: 10,
|
||||
});
|
||||
fleetAppContextService.start(
|
||||
fleetCreateAppContextStartContractMock({}, false, {
|
||||
withoutSpaceExtensions: mockSavedObjectClient,
|
||||
})
|
||||
);
|
||||
mockAgentClient.getAgentStatusById.mockResolvedValue('error');
|
||||
mockAgentClient.listAgents.mockResolvedValue(noUnenrolledAgent);
|
||||
mockAgentPolicyService.getByIds = jest.fn().mockResolvedValueOnce([]);
|
||||
|
|
|
@ -5,6 +5,9 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { createAppContextStartContractMock as fleetCreateAppContextStartContractMock } from '@kbn/fleet-plugin/server/mocks';
|
||||
import { appContextService as fleetAppContextService } from '@kbn/fleet-plugin/server/services';
|
||||
|
||||
import { getESQueryHostMetadataByID, buildUnitedIndexQuery } from './query_builders';
|
||||
import { metadataCurrentIndexPattern } from '../../../../common/endpoint/constants';
|
||||
import { get } from 'lodash';
|
||||
|
@ -50,6 +53,11 @@ describe('query builder', () => {
|
|||
per_page: 0,
|
||||
page: 0,
|
||||
});
|
||||
fleetAppContextService.start(
|
||||
fleetCreateAppContextStartContractMock({}, false, {
|
||||
withoutSpaceExtensions: soClient,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('correctly builds empty query', async () => {
|
||||
|
|
|
@ -21,6 +21,8 @@ import {
|
|||
import type { HostMetadata } from '../../../../common/endpoint/types';
|
||||
import type { Agent, PackagePolicy } from '@kbn/fleet-plugin/common';
|
||||
import type { AgentPolicyServiceInterface } from '@kbn/fleet-plugin/server/services';
|
||||
import { createAppContextStartContractMock as fleetCreateAppContextStartContractMock } from '@kbn/fleet-plugin/server/mocks';
|
||||
import { appContextService as fleetAppContextService } from '@kbn/fleet-plugin/server/services';
|
||||
import { EndpointError } from '../../../../common/endpoint/errors';
|
||||
import type { SavedObjectsClientContract } from '@kbn/core/server';
|
||||
|
||||
|
@ -38,6 +40,11 @@ describe('EndpointMetadataService', () => {
|
|||
esClient = elasticsearchServiceMock.createScopedClusterClient().asInternalUser;
|
||||
soClient = savedObjectsClientMock.create();
|
||||
soClient.find = jest.fn().mockResolvedValue({ saved_objects: [] });
|
||||
fleetAppContextService.start(
|
||||
fleetCreateAppContextStartContractMock({}, false, {
|
||||
withoutSpaceExtensions: soClient,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
describe('#findHostMetadataForFleetAgents()', () => {
|
||||
|
|
|
@ -531,6 +531,7 @@ export default function (providerContext: FtrProviderContext) {
|
|||
updated_by: 'elastic',
|
||||
package_policies: [],
|
||||
is_protected: false,
|
||||
space_ids: [],
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -962,6 +963,7 @@ export default function (providerContext: FtrProviderContext) {
|
|||
inactivity_timeout: 1209600,
|
||||
package_policies: [],
|
||||
is_protected: false,
|
||||
space_ids: [],
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -1125,6 +1127,7 @@ export default function (providerContext: FtrProviderContext) {
|
|||
package_policies: [],
|
||||
monitoring_enabled: ['logs', 'metrics'],
|
||||
inactivity_timeout: 1209600,
|
||||
space_ids: [],
|
||||
});
|
||||
|
||||
const listResponseAfterUpdate = await fetchPackageList();
|
||||
|
@ -1183,6 +1186,7 @@ export default function (providerContext: FtrProviderContext) {
|
|||
inactivity_timeout: 1209600,
|
||||
package_policies: [],
|
||||
is_protected: false,
|
||||
space_ids: [],
|
||||
overrides: {
|
||||
agent: {
|
||||
logging: {
|
||||
|
@ -1473,6 +1477,7 @@ export default function (providerContext: FtrProviderContext) {
|
|||
const {
|
||||
package_policies: packagePolicies,
|
||||
id,
|
||||
space_ids: spaceIds,
|
||||
updated_at: updatedAt,
|
||||
version: policyVersion,
|
||||
...rest
|
||||
|
|
|
@ -67,12 +67,14 @@ export default function (providerContext: FtrProviderContext) {
|
|||
is_default_fleet_server: true,
|
||||
is_managed: false,
|
||||
name: 'Fleet Server Policy',
|
||||
space_ids: [],
|
||||
},
|
||||
{
|
||||
id: 'fleet-server-policy-2',
|
||||
is_default_fleet_server: false,
|
||||
is_managed: false,
|
||||
name: 'Fleet Server Policy 2',
|
||||
space_ids: [],
|
||||
},
|
||||
],
|
||||
has_active: true,
|
||||
|
@ -117,6 +119,7 @@ export default function (providerContext: FtrProviderContext) {
|
|||
is_default_fleet_server: false,
|
||||
is_managed: false,
|
||||
name: 'Fleet Server Policy 2',
|
||||
space_ids: [],
|
||||
},
|
||||
],
|
||||
has_active: true,
|
||||
|
|
|
@ -10,7 +10,7 @@ import { CreateAgentPolicyResponse } from '@kbn/fleet-plugin/common';
|
|||
import { FtrProviderContext } from '../../../api_integration/ftr_provider_context';
|
||||
import { skipIfNoDockerRegistry } from '../../helpers';
|
||||
import { SpaceTestApiClient } from './api_helper';
|
||||
import { cleanFleetIndices } from './helpers';
|
||||
import { cleanFleetIndices, expectToRejectWithNotFound } from './helpers';
|
||||
import { setupTestSpaces, TEST_SPACE_1 } from './space_helpers';
|
||||
|
||||
export default function (providerContext: FtrProviderContext) {
|
||||
|
@ -81,27 +81,13 @@ export default function (providerContext: FtrProviderContext) {
|
|||
await apiClient.getAgentPolicy(spaceTest1Policy1.item.id, TEST_SPACE_1);
|
||||
});
|
||||
it('should not allow to get a policy from a different space from the default space', async () => {
|
||||
let err: Error | undefined;
|
||||
try {
|
||||
await apiClient.getAgentPolicy(spaceTest1Policy1.item.id);
|
||||
} catch (_err) {
|
||||
err = _err;
|
||||
}
|
||||
|
||||
expect(err).to.be.an(Error);
|
||||
expect(err?.message).to.match(/404 "Not Found"/);
|
||||
await expectToRejectWithNotFound(() => apiClient.getAgentPolicy(spaceTest1Policy1.item.id));
|
||||
});
|
||||
|
||||
it('should not allow to get an default space policy from a different space', async () => {
|
||||
let err: Error | undefined;
|
||||
try {
|
||||
await apiClient.getAgentPolicy(defaultSpacePolicy1.item.id, TEST_SPACE_1);
|
||||
} catch (_err) {
|
||||
err = _err;
|
||||
}
|
||||
|
||||
expect(err).to.be.an(Error);
|
||||
expect(err?.message).to.match(/404 "Not Found"/);
|
||||
await expectToRejectWithNotFound(() =>
|
||||
apiClient.getAgentPolicy(defaultSpacePolicy1.item.id, TEST_SPACE_1)
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -15,6 +15,7 @@ import {
|
|||
GetAgentsResponse,
|
||||
GetOneAgentPolicyResponse,
|
||||
GetOneAgentResponse,
|
||||
GetOnePackagePolicyResponse,
|
||||
GetPackagePoliciesResponse,
|
||||
} from '@kbn/fleet-plugin/common';
|
||||
import {
|
||||
|
@ -28,21 +29,28 @@ import {
|
|||
PutSpaceSettingsRequest,
|
||||
GetActionStatusResponse,
|
||||
PostNewAgentActionResponse,
|
||||
UpdateAgentPolicyResponse,
|
||||
UpdateAgentPolicyRequest,
|
||||
} from '@kbn/fleet-plugin/common/types';
|
||||
import {
|
||||
GetUninstallTokenResponse,
|
||||
GetUninstallTokensMetadataResponse,
|
||||
} from '@kbn/fleet-plugin/common/types/rest_spec/uninstall_token';
|
||||
import { SimplifiedPackagePolicy } from '@kbn/fleet-plugin/common/services/simplified_package_policy_helper';
|
||||
import { testUsers } from '../test_users';
|
||||
|
||||
export class SpaceTestApiClient {
|
||||
constructor(private readonly supertest: Agent) {}
|
||||
constructor(
|
||||
private readonly supertest: Agent,
|
||||
private readonly auth = testUsers.fleet_all_int_all
|
||||
) {}
|
||||
private getBaseUrl(spaceId?: string) {
|
||||
return spaceId ? `/s/${spaceId}` : '';
|
||||
}
|
||||
async setup(spaceId?: string): Promise<CreateAgentPolicyResponse> {
|
||||
const { body: res } = await this.supertest
|
||||
.post(`${this.getBaseUrl(spaceId)}/api/fleet/setup`)
|
||||
.auth(this.auth.username, this.auth.password)
|
||||
.set('kbn-xsrf', 'xxxx')
|
||||
.send({})
|
||||
.expect(200);
|
||||
|
@ -57,6 +65,7 @@ export class SpaceTestApiClient {
|
|||
): Promise<CreateAgentPolicyResponse> {
|
||||
const { body: res } = await this.supertest
|
||||
.post(`${this.getBaseUrl(spaceId)}/api/fleet/agent_policies`)
|
||||
.auth(this.auth.username, this.auth.password)
|
||||
.set('kbn-xsrf', 'xxxx')
|
||||
.send({
|
||||
name: `test ${uuidV4()}`,
|
||||
|
@ -82,6 +91,17 @@ export class SpaceTestApiClient {
|
|||
return res;
|
||||
}
|
||||
|
||||
async getPackagePolicy(
|
||||
packagePolicyId: string,
|
||||
spaceId?: string
|
||||
): Promise<GetOnePackagePolicyResponse> {
|
||||
const { body: res } = await this.supertest
|
||||
.get(`${this.getBaseUrl(spaceId)}/api/fleet/package_policies/${packagePolicyId}`)
|
||||
.expect(200);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
async getPackagePolicies(spaceId?: string): Promise<GetPackagePoliciesResponse> {
|
||||
const { body: res } = await this.supertest
|
||||
.get(`${this.getBaseUrl(spaceId)}/api/fleet/package_policies`)
|
||||
|
@ -89,6 +109,7 @@ export class SpaceTestApiClient {
|
|||
|
||||
return res;
|
||||
}
|
||||
|
||||
async createFleetServerPolicy(spaceId?: string): Promise<CreateAgentPolicyResponse> {
|
||||
const { body: res } = await this.supertest
|
||||
.post(`${this.getBaseUrl(spaceId)}/api/fleet/agent_policies`)
|
||||
|
@ -121,6 +142,29 @@ export class SpaceTestApiClient {
|
|||
|
||||
return res;
|
||||
}
|
||||
async putAgentPolicy(
|
||||
policyId: string,
|
||||
data: Partial<UpdateAgentPolicyRequest['body']>,
|
||||
spaceId?: string
|
||||
): Promise<UpdateAgentPolicyResponse> {
|
||||
const { body: res, statusCode } = await this.supertest
|
||||
.put(`${this.getBaseUrl(spaceId)}/api/fleet/agent_policies/${policyId}`)
|
||||
.auth(this.auth.username, this.auth.password)
|
||||
.send({
|
||||
...data,
|
||||
})
|
||||
.set('kbn-xsrf', 'xxxx');
|
||||
|
||||
if (statusCode === 200) {
|
||||
return res;
|
||||
}
|
||||
|
||||
if (statusCode === 404) {
|
||||
throw new Error('404 "Not Found"');
|
||||
} else {
|
||||
throw new Error(`${statusCode} ${res?.error} ${res.message}`);
|
||||
}
|
||||
}
|
||||
async getAgentPolicies(spaceId?: string): Promise<GetAgentPoliciesResponse> {
|
||||
const { body: res } = await this.supertest
|
||||
.get(`${this.getBaseUrl(spaceId)}/api/fleet/agent_policies`)
|
||||
|
|
|
@ -0,0 +1,197 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import expect from '@kbn/expect';
|
||||
import { CreateAgentPolicyResponse, GetOnePackagePolicyResponse } from '@kbn/fleet-plugin/common';
|
||||
import { FtrProviderContext } from '../../../api_integration/ftr_provider_context';
|
||||
import { skipIfNoDockerRegistry } from '../../helpers';
|
||||
import { SpaceTestApiClient } from './api_helper';
|
||||
import {
|
||||
cleanFleetIndices,
|
||||
createFleetAgent,
|
||||
expectToRejectWithError,
|
||||
expectToRejectWithNotFound,
|
||||
} from './helpers';
|
||||
import { setupTestSpaces, TEST_SPACE_1 } from './space_helpers';
|
||||
import { testUsers, setupTestUsers } from '../test_users';
|
||||
|
||||
export default function (providerContext: FtrProviderContext) {
|
||||
const { getService } = providerContext;
|
||||
const supertest = getService('supertest');
|
||||
const supertestWithoutAuth = getService('supertestWithoutAuth');
|
||||
const esClient = getService('es');
|
||||
const kibanaServer = getService('kibanaServer');
|
||||
|
||||
describe('change space agent policies', async function () {
|
||||
skipIfNoDockerRegistry(providerContext);
|
||||
const apiClient = new SpaceTestApiClient(supertest);
|
||||
|
||||
before(async () => {
|
||||
await setupTestUsers(getService('security'), true);
|
||||
await kibanaServer.savedObjects.cleanStandardList();
|
||||
await kibanaServer.savedObjects.cleanStandardList({
|
||||
space: TEST_SPACE_1,
|
||||
});
|
||||
await cleanFleetIndices(esClient);
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await kibanaServer.savedObjects.cleanStandardList();
|
||||
await kibanaServer.savedObjects.cleanStandardList({
|
||||
space: TEST_SPACE_1,
|
||||
});
|
||||
await cleanFleetIndices(esClient);
|
||||
});
|
||||
|
||||
setupTestSpaces(providerContext);
|
||||
let defaultSpacePolicy1: CreateAgentPolicyResponse;
|
||||
let defaultPackagePolicy1: GetOnePackagePolicyResponse;
|
||||
before(async () => {
|
||||
await apiClient.postEnableSpaceAwareness();
|
||||
const _policyRes = await apiClient.createAgentPolicy();
|
||||
defaultSpacePolicy1 = _policyRes;
|
||||
await apiClient.installPackage({
|
||||
pkgName: 'nginx',
|
||||
pkgVersion: '1.20.0',
|
||||
force: true, // To avoid package verification
|
||||
});
|
||||
await createFleetAgent(esClient, defaultSpacePolicy1.item.id);
|
||||
const packagePolicyRes = await apiClient.createPackagePolicy(undefined, {
|
||||
policy_ids: [defaultSpacePolicy1.item.id],
|
||||
name: `test-nginx-${Date.now()}`,
|
||||
description: 'test',
|
||||
package: {
|
||||
name: 'nginx',
|
||||
version: '1.20.0',
|
||||
},
|
||||
inputs: {},
|
||||
});
|
||||
defaultPackagePolicy1 = packagePolicyRes;
|
||||
});
|
||||
|
||||
describe('PUT /agent_policies/{id}', () => {
|
||||
beforeEach(async () => {
|
||||
// Reset policy in default space
|
||||
await apiClient
|
||||
.putAgentPolicy(
|
||||
defaultSpacePolicy1.item.id,
|
||||
{
|
||||
name: 'tata',
|
||||
namespace: 'default',
|
||||
description: 'tata',
|
||||
space_ids: ['default'],
|
||||
},
|
||||
TEST_SPACE_1
|
||||
)
|
||||
.catch(() => {});
|
||||
await apiClient
|
||||
.putAgentPolicy(defaultSpacePolicy1.item.id, {
|
||||
name: 'tata',
|
||||
namespace: 'default',
|
||||
description: 'tata',
|
||||
space_ids: ['default'],
|
||||
})
|
||||
.catch(() => {});
|
||||
});
|
||||
async function assertPolicyAvailableInSpace(spaceId?: string) {
|
||||
await apiClient.getAgentPolicy(defaultSpacePolicy1.item.id, spaceId);
|
||||
await apiClient.getPackagePolicy(defaultPackagePolicy1.item.id, spaceId);
|
||||
const enrollmentApiKeys = await apiClient.getEnrollmentApiKeys(spaceId);
|
||||
expect(
|
||||
enrollmentApiKeys.items.find((item) => item.policy_id === defaultSpacePolicy1.item.id)
|
||||
).not.to.be(undefined);
|
||||
|
||||
const agents = await apiClient.getAgents(spaceId);
|
||||
expect(agents.total).to.be(1);
|
||||
}
|
||||
|
||||
async function assertPolicyNotAvailableInSpace(spaceId?: string) {
|
||||
await expectToRejectWithNotFound(() =>
|
||||
apiClient.getPackagePolicy(defaultPackagePolicy1.item.id, spaceId)
|
||||
);
|
||||
await expectToRejectWithNotFound(() =>
|
||||
apiClient.getAgentPolicy(defaultSpacePolicy1.item.id, spaceId)
|
||||
);
|
||||
|
||||
const enrollmentApiKeys = await apiClient.getEnrollmentApiKeys(spaceId);
|
||||
expect(
|
||||
enrollmentApiKeys.items.find((item) => item.policy_id === defaultSpacePolicy1.item.id)
|
||||
).to.be(undefined);
|
||||
|
||||
const agents = await apiClient.getAgents(spaceId);
|
||||
expect(agents.total).to.be(0);
|
||||
}
|
||||
|
||||
it('should allow set policy in multiple space', async () => {
|
||||
await apiClient.putAgentPolicy(defaultSpacePolicy1.item.id, {
|
||||
name: 'tata',
|
||||
namespace: 'default',
|
||||
description: 'tata',
|
||||
space_ids: ['default', TEST_SPACE_1],
|
||||
});
|
||||
|
||||
await assertPolicyAvailableInSpace();
|
||||
await assertPolicyAvailableInSpace(TEST_SPACE_1);
|
||||
});
|
||||
|
||||
it('should allow set policy in test space only', async () => {
|
||||
await apiClient.putAgentPolicy(defaultSpacePolicy1.item.id, {
|
||||
name: 'tata',
|
||||
namespace: 'default',
|
||||
description: 'tata',
|
||||
space_ids: [TEST_SPACE_1],
|
||||
});
|
||||
|
||||
await assertPolicyNotAvailableInSpace();
|
||||
await assertPolicyAvailableInSpace(TEST_SPACE_1);
|
||||
});
|
||||
|
||||
it('should not allow add policy to a space where user do not have access', async () => {
|
||||
const testApiClient = new SpaceTestApiClient(
|
||||
supertestWithoutAuth,
|
||||
testUsers.fleet_all_int_all_default_space_only
|
||||
);
|
||||
|
||||
await expectToRejectWithError(
|
||||
() =>
|
||||
testApiClient.putAgentPolicy(defaultSpacePolicy1.item.id, {
|
||||
name: 'tata',
|
||||
namespace: 'default',
|
||||
description: 'tata',
|
||||
space_ids: ['default', TEST_SPACE_1],
|
||||
}),
|
||||
/400 Bad Request No enough permissions to create policies in space test1/
|
||||
);
|
||||
});
|
||||
|
||||
it('should not allow to remove policy from a space where user do not have access', async () => {
|
||||
await apiClient.putAgentPolicy(defaultSpacePolicy1.item.id, {
|
||||
name: 'tata',
|
||||
namespace: 'default',
|
||||
description: 'tata',
|
||||
space_ids: ['default', TEST_SPACE_1],
|
||||
});
|
||||
|
||||
const testApiClient = new SpaceTestApiClient(
|
||||
supertestWithoutAuth,
|
||||
testUsers.fleet_all_int_all_default_space_only
|
||||
);
|
||||
|
||||
await expectToRejectWithError(
|
||||
() =>
|
||||
testApiClient.putAgentPolicy(defaultSpacePolicy1.item.id, {
|
||||
name: 'tata',
|
||||
namespace: 'default',
|
||||
description: 'tata',
|
||||
space_ids: ['default'],
|
||||
}),
|
||||
/400 Bad Request No enough permissions to remove policies from space test1/
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
|
@ -6,6 +6,7 @@
|
|||
*/
|
||||
|
||||
import { Client } from '@elastic/elasticsearch';
|
||||
import expect from '@kbn/expect';
|
||||
|
||||
import {
|
||||
AGENT_ACTIONS_INDEX,
|
||||
|
@ -17,6 +18,21 @@ import { ENROLLMENT_API_KEYS_INDEX } from '@kbn/fleet-plugin/common/constants';
|
|||
|
||||
const ES_INDEX_OPTIONS = { headers: { 'X-elastic-product-origin': 'fleet' } };
|
||||
|
||||
export async function expectToRejectWithNotFound(fn: any) {
|
||||
await expectToRejectWithError(fn, /404 "Not Found"/);
|
||||
}
|
||||
|
||||
export async function expectToRejectWithError(fn: any, errRegexp: RegExp) {
|
||||
let err: Error | undefined;
|
||||
try {
|
||||
await fn();
|
||||
} catch (_err) {
|
||||
err = _err;
|
||||
}
|
||||
expect(err).to.be.an(Error);
|
||||
expect(err?.message).to.match(errRegexp);
|
||||
}
|
||||
|
||||
export async function cleanFleetIndices(esClient: Client) {
|
||||
await Promise.all([
|
||||
esClient.deleteByQuery({
|
||||
|
|
|
@ -15,6 +15,7 @@ export default function loadTests({ loadTestFile }) {
|
|||
loadTestFile(require.resolve('./package_install'));
|
||||
loadTestFile(require.resolve('./space_settings'));
|
||||
loadTestFile(require.resolve('./actions'));
|
||||
loadTestFile(require.resolve('./change_space_agent_policies'));
|
||||
loadTestFile(require.resolve('./space_awareness_migration'));
|
||||
});
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ import { UninstallTokenMetadata } from '@kbn/fleet-plugin/common/types/models/un
|
|||
import { FtrProviderContext } from '../../../api_integration/ftr_provider_context';
|
||||
import { skipIfNoDockerRegistry } from '../../helpers';
|
||||
import { SpaceTestApiClient } from './api_helper';
|
||||
import { cleanFleetIndices } from './helpers';
|
||||
import { cleanFleetIndices, expectToRejectWithNotFound } from './helpers';
|
||||
import { setupTestSpaces, TEST_SPACE_1 } from './space_helpers';
|
||||
|
||||
export default function (providerContext: FtrProviderContext) {
|
||||
|
@ -89,27 +89,13 @@ export default function (providerContext: FtrProviderContext) {
|
|||
await apiClient.getUninstallToken(spaceTest1Token.id, TEST_SPACE_1);
|
||||
});
|
||||
it('should not allow to get an uninstall token from a different space from the default space', async () => {
|
||||
let err: Error | undefined;
|
||||
try {
|
||||
await apiClient.getUninstallToken(spaceTest1Token.id);
|
||||
} catch (_err) {
|
||||
err = _err;
|
||||
}
|
||||
|
||||
expect(err).to.be.an(Error);
|
||||
expect(err?.message).to.match(/404 "Not Found"/);
|
||||
await expectToRejectWithNotFound(() => apiClient.getUninstallToken(spaceTest1Token.id));
|
||||
});
|
||||
|
||||
it('should not allow to get an default space uninstall token from a different space', async () => {
|
||||
let err: Error | undefined;
|
||||
try {
|
||||
await apiClient.getUninstallToken(defaultSpaceToken.id, TEST_SPACE_1);
|
||||
} catch (_err) {
|
||||
err = _err;
|
||||
}
|
||||
|
||||
expect(err).to.be.an(Error);
|
||||
expect(err?.message).to.match(/404 "Not Found"/);
|
||||
await expectToRejectWithNotFound(() =>
|
||||
apiClient.getUninstallToken(defaultSpaceToken.id, TEST_SPACE_1)
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -21,6 +21,17 @@ export const testUsers: {
|
|||
username: 'fleet_all_int_all',
|
||||
password: 'changeme',
|
||||
},
|
||||
fleet_all_int_all_default_space_only: {
|
||||
permissions: {
|
||||
feature: {
|
||||
fleetv2: ['all'],
|
||||
fleet: ['all'],
|
||||
},
|
||||
spaces: ['default'],
|
||||
},
|
||||
username: 'fleet_all_int_all_default_space_only',
|
||||
password: 'changeme',
|
||||
},
|
||||
fleet_read_only: {
|
||||
permissions: {
|
||||
feature: {
|
||||
|
@ -231,8 +242,11 @@ export const testUsers: {
|
|||
},
|
||||
};
|
||||
|
||||
export const setupTestUsers = async (security: SecurityService) => {
|
||||
export const setupTestUsers = async (security: SecurityService, spaceAwarenessEnabled = false) => {
|
||||
for (const roleName in testUsers) {
|
||||
if (!spaceAwarenessEnabled && roleName === 'fleet_all_int_all_default_space_only') {
|
||||
continue;
|
||||
}
|
||||
if (Object.hasOwn(testUsers, roleName)) {
|
||||
const user = testUsers[roleName];
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue