mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
[8.x] [UII] Restrict agentless integrations to deployments with agentless enabled (#194885) (#196459)
# Backport This will backport the following commits from `main` to `8.x`: - [[UII] Restrict agentless integrations to deployments with agentless enabled (#194885)](https://github.com/elastic/kibana/pull/194885) <!--- Backport version: 9.4.3 --> ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sqren/backport) <!--BACKPORT [{"author":{"name":"Jen Huang","email":"its.jenetic@gmail.com"},"sourceCommit":{"committedDate":"2024-10-15T23:57:32Z","message":"[UII] Restrict agentless integrations to deployments with agentless enabled (#194885)\n\n## Summary\r\n\r\nResolves #192486. This PR makes it so that on deployments without\r\nagentless enabled:\r\n1. Agentless-only integrations are hidden from the browse integration UI\r\n2. Agentless-only integrations cannot be installed via API (unless force\r\nflag is used)\r\n\r\n⚠️ https://github.com/elastic/package-registry/issues/1238 needs to be\r\ncompleted for the below testing steps to work. Currently EPR does not\r\nreturn `deployment_modes` property which is necessary for Fleet to know\r\nwhich packages are agentless.\r\n\r\n## How to test\r\n\r\n1. Simulate agentless being available by adding the following to\r\nkibana.yml:\r\n```\r\nxpack.fleet.agentless.enabled: true\r\n\r\n# Simulate cloud\r\nxpack.cloud.id: \"foo\"\r\nxpack.cloud.base_url: \"https://cloud.elastic.co\"\r\nxpack.cloud.organization_url: \"/account/\"\r\nxpack.cloud.billing_url: \"/billing/\"\r\nxpack.cloud.profile_url: \"/user/settings/\"\r\n```\r\n2. Go to `Integrations > Browse` and enable showing Beta integrations,\r\nsearch for `connector` and you should see the agentless integrations:\r\nElastic Connectors, GitHub & GitHub Enterprise Server Connector, Google\r\nDrive Connector\r\n3. Install any one of them (they all come from the same package), it\r\nshould be successful\r\n4. Uninstall them\r\n5. Remove config changes to go back to a non-agentless deployment\r\n6. Refresh Integrations list, the three integrations should no longer\r\nappear\r\n7. Try installing via API, an error should appear\r\n```\r\nPOST kbn:/api/fleet/epm/packages/elastic_connectors/0.0.2\r\n```\r\n8. Try installing via API again with force flag, it should be\r\nsuccessful:\r\n```\r\nPOST kbn:/api/fleet/epm/packages/elastic_connectors/0.0.2\r\n{\r\n \"force\": true\r\n}\r\n```\r\n\r\n### Checklist\r\n\r\n- [x] [Unit or functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere updated or added to match the most common scenarios","sha":"8cadf88c66a257c073279fa11572b089c32eb643","branchLabelMapping":{"^v9.0.0$":"main","^v8.16.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:skip","Team:Fleet","v9.0.0","backport:prev-minor"],"title":"[UII] Restrict agentless integrations to deployments with agentless enabled","number":194885,"url":"https://github.com/elastic/kibana/pull/194885","mergeCommit":{"message":"[UII] Restrict agentless integrations to deployments with agentless enabled (#194885)\n\n## Summary\r\n\r\nResolves #192486. This PR makes it so that on deployments without\r\nagentless enabled:\r\n1. Agentless-only integrations are hidden from the browse integration UI\r\n2. Agentless-only integrations cannot be installed via API (unless force\r\nflag is used)\r\n\r\n⚠️ https://github.com/elastic/package-registry/issues/1238 needs to be\r\ncompleted for the below testing steps to work. Currently EPR does not\r\nreturn `deployment_modes` property which is necessary for Fleet to know\r\nwhich packages are agentless.\r\n\r\n## How to test\r\n\r\n1. Simulate agentless being available by adding the following to\r\nkibana.yml:\r\n```\r\nxpack.fleet.agentless.enabled: true\r\n\r\n# Simulate cloud\r\nxpack.cloud.id: \"foo\"\r\nxpack.cloud.base_url: \"https://cloud.elastic.co\"\r\nxpack.cloud.organization_url: \"/account/\"\r\nxpack.cloud.billing_url: \"/billing/\"\r\nxpack.cloud.profile_url: \"/user/settings/\"\r\n```\r\n2. Go to `Integrations > Browse` and enable showing Beta integrations,\r\nsearch for `connector` and you should see the agentless integrations:\r\nElastic Connectors, GitHub & GitHub Enterprise Server Connector, Google\r\nDrive Connector\r\n3. Install any one of them (they all come from the same package), it\r\nshould be successful\r\n4. Uninstall them\r\n5. Remove config changes to go back to a non-agentless deployment\r\n6. Refresh Integrations list, the three integrations should no longer\r\nappear\r\n7. Try installing via API, an error should appear\r\n```\r\nPOST kbn:/api/fleet/epm/packages/elastic_connectors/0.0.2\r\n```\r\n8. Try installing via API again with force flag, it should be\r\nsuccessful:\r\n```\r\nPOST kbn:/api/fleet/epm/packages/elastic_connectors/0.0.2\r\n{\r\n \"force\": true\r\n}\r\n```\r\n\r\n### Checklist\r\n\r\n- [x] [Unit or functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere updated or added to match the most common scenarios","sha":"8cadf88c66a257c073279fa11572b089c32eb643"}},"sourceBranch":"main","suggestedTargetBranches":[],"targetPullRequestStates":[{"branch":"main","label":"v9.0.0","branchLabelMappingKey":"^v9.0.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/194885","number":194885,"mergeCommit":{"message":"[UII] Restrict agentless integrations to deployments with agentless enabled (#194885)\n\n## Summary\r\n\r\nResolves #192486. This PR makes it so that on deployments without\r\nagentless enabled:\r\n1. Agentless-only integrations are hidden from the browse integration UI\r\n2. Agentless-only integrations cannot be installed via API (unless force\r\nflag is used)\r\n\r\n⚠️ https://github.com/elastic/package-registry/issues/1238 needs to be\r\ncompleted for the below testing steps to work. Currently EPR does not\r\nreturn `deployment_modes` property which is necessary for Fleet to know\r\nwhich packages are agentless.\r\n\r\n## How to test\r\n\r\n1. Simulate agentless being available by adding the following to\r\nkibana.yml:\r\n```\r\nxpack.fleet.agentless.enabled: true\r\n\r\n# Simulate cloud\r\nxpack.cloud.id: \"foo\"\r\nxpack.cloud.base_url: \"https://cloud.elastic.co\"\r\nxpack.cloud.organization_url: \"/account/\"\r\nxpack.cloud.billing_url: \"/billing/\"\r\nxpack.cloud.profile_url: \"/user/settings/\"\r\n```\r\n2. Go to `Integrations > Browse` and enable showing Beta integrations,\r\nsearch for `connector` and you should see the agentless integrations:\r\nElastic Connectors, GitHub & GitHub Enterprise Server Connector, Google\r\nDrive Connector\r\n3. Install any one of them (they all come from the same package), it\r\nshould be successful\r\n4. Uninstall them\r\n5. Remove config changes to go back to a non-agentless deployment\r\n6. Refresh Integrations list, the three integrations should no longer\r\nappear\r\n7. Try installing via API, an error should appear\r\n```\r\nPOST kbn:/api/fleet/epm/packages/elastic_connectors/0.0.2\r\n```\r\n8. Try installing via API again with force flag, it should be\r\nsuccessful:\r\n```\r\nPOST kbn:/api/fleet/epm/packages/elastic_connectors/0.0.2\r\n{\r\n \"force\": true\r\n}\r\n```\r\n\r\n### Checklist\r\n\r\n- [x] [Unit or functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere updated or added to match the most common scenarios","sha":"8cadf88c66a257c073279fa11572b089c32eb643"}}]}] BACKPORT--> Co-authored-by: Jen Huang <its.jenetic@gmail.com>
This commit is contained in:
parent
b9820cc067
commit
34b0772076
14 changed files with 488 additions and 27 deletions
|
@ -0,0 +1,287 @@
|
|||
/*
|
||||
* 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 { RegistryPolicyTemplate } from '../types';
|
||||
|
||||
import {
|
||||
isAgentlessIntegration,
|
||||
getAgentlessAgentPolicyNameFromPackagePolicyName,
|
||||
isOnlyAgentlessIntegration,
|
||||
isOnlyAgentlessPolicyTemplate,
|
||||
} from './agentless_policy_helper';
|
||||
|
||||
describe('agentless_policy_helper', () => {
|
||||
describe('isAgentlessIntegration', () => {
|
||||
it('should return true if packageInfo is defined and has at least one agentless integration', () => {
|
||||
const packageInfo = {
|
||||
policy_templates: [
|
||||
{
|
||||
name: 'template1',
|
||||
title: 'Template 1',
|
||||
description: '',
|
||||
deployment_modes: {
|
||||
default: {
|
||||
enabled: true,
|
||||
},
|
||||
agentless: {
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'template2',
|
||||
title: 'Template 2',
|
||||
description: '',
|
||||
deployment_modes: {
|
||||
default: {
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
] as RegistryPolicyTemplate[],
|
||||
};
|
||||
|
||||
const result = isAgentlessIntegration(packageInfo);
|
||||
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false if packageInfo is defined but does not have agentless integrations', () => {
|
||||
const packageInfo = {
|
||||
policy_templates: [
|
||||
{
|
||||
name: 'template1',
|
||||
title: 'Template 1',
|
||||
description: '',
|
||||
deployment_modes: {
|
||||
default: {
|
||||
enabled: true,
|
||||
},
|
||||
agentless: {
|
||||
enabled: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'template2',
|
||||
title: 'Template 2',
|
||||
description: '',
|
||||
deployment_modes: {
|
||||
default: {
|
||||
enabled: false,
|
||||
},
|
||||
agentless: {
|
||||
enabled: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
] as RegistryPolicyTemplate[],
|
||||
};
|
||||
|
||||
const result = isAgentlessIntegration(packageInfo);
|
||||
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false if packageInfo has no policy templates', () => {
|
||||
const packageInfo = {
|
||||
policy_templates: [],
|
||||
};
|
||||
|
||||
const result = isAgentlessIntegration(packageInfo);
|
||||
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false if packageInfo is undefined', () => {
|
||||
const packageInfo = undefined;
|
||||
|
||||
const result = isAgentlessIntegration(packageInfo);
|
||||
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getAgentlessAgentPolicyNameFromPackagePolicyName', () => {
|
||||
it('should return the agentless agent policy name based on the package policy name', () => {
|
||||
const packagePolicyName = 'example-package-policy';
|
||||
|
||||
const result = getAgentlessAgentPolicyNameFromPackagePolicyName(packagePolicyName);
|
||||
|
||||
expect(result).toBe('Agentless policy for example-package-policy');
|
||||
});
|
||||
});
|
||||
|
||||
describe('isOnlyAgentlessIntegration', () => {
|
||||
it('should return true if packageInfo is defined and has only agentless integration', () => {
|
||||
const packageInfo = {
|
||||
policy_templates: [
|
||||
{
|
||||
name: 'template1',
|
||||
title: 'Template 1',
|
||||
description: '',
|
||||
deployment_modes: {
|
||||
default: {
|
||||
enabled: false,
|
||||
},
|
||||
agentless: {
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'template2',
|
||||
title: 'Template 2',
|
||||
description: '',
|
||||
deployment_modes: {
|
||||
agentless: {
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
] as RegistryPolicyTemplate[],
|
||||
};
|
||||
|
||||
const result = isOnlyAgentlessIntegration(packageInfo);
|
||||
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false if packageInfo is defined but has other deployment types', () => {
|
||||
const packageInfo = {
|
||||
policy_templates: [
|
||||
{
|
||||
name: 'template1',
|
||||
title: 'Template 1',
|
||||
description: '',
|
||||
deployment_modes: {
|
||||
default: {
|
||||
enabled: true,
|
||||
},
|
||||
agentless: {
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'template2',
|
||||
title: 'Template 2',
|
||||
description: '',
|
||||
deployment_modes: {
|
||||
default: {
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
] as RegistryPolicyTemplate[],
|
||||
};
|
||||
|
||||
const result = isOnlyAgentlessIntegration(packageInfo);
|
||||
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false if packageInfo has no policy templates', () => {
|
||||
const packageInfo = {
|
||||
policy_templates: [],
|
||||
};
|
||||
|
||||
const result = isOnlyAgentlessIntegration(packageInfo);
|
||||
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false if packageInfo is undefined', () => {
|
||||
const packageInfo = undefined;
|
||||
|
||||
const result = isOnlyAgentlessIntegration(packageInfo);
|
||||
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isOnlyAgentlessPolicyTemplate', () => {
|
||||
it('should return true if the policy template is only agentless', () => {
|
||||
const policyTemplate = {
|
||||
name: 'template1',
|
||||
title: 'Template 1',
|
||||
description: '',
|
||||
deployment_modes: {
|
||||
default: {
|
||||
enabled: false,
|
||||
},
|
||||
agentless: {
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
const policyTemplate2 = {
|
||||
name: 'template2',
|
||||
title: 'Template 2',
|
||||
description: '',
|
||||
deployment_modes: {
|
||||
agentless: {
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const result = isOnlyAgentlessPolicyTemplate(policyTemplate);
|
||||
const result2 = isOnlyAgentlessPolicyTemplate(policyTemplate2);
|
||||
|
||||
expect(result).toBe(true);
|
||||
expect(result2).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false if the policy template has other deployment types', () => {
|
||||
const policyTemplate = {
|
||||
name: 'template1',
|
||||
title: 'Template 1',
|
||||
description: '',
|
||||
deployment_modes: {
|
||||
default: {
|
||||
enabled: true,
|
||||
},
|
||||
agentless: {
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
const policyTemplate2 = {
|
||||
name: 'template2',
|
||||
title: 'Template 2',
|
||||
description: '',
|
||||
deployment_modes: {
|
||||
default: {
|
||||
enabled: true,
|
||||
},
|
||||
agentless: {
|
||||
enabled: false,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const result = isOnlyAgentlessPolicyTemplate(policyTemplate);
|
||||
const result2 = isOnlyAgentlessPolicyTemplate(policyTemplate2);
|
||||
|
||||
expect(result).toBe(false);
|
||||
expect(result2).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false if the policy template has no deployment modes', () => {
|
||||
const policyTemplate = {
|
||||
name: 'template1',
|
||||
title: 'Template 1',
|
||||
description: '',
|
||||
};
|
||||
|
||||
const result = isOnlyAgentlessPolicyTemplate(policyTemplate);
|
||||
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -5,6 +5,47 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { PackageInfo, RegistryPolicyTemplate } from '../types';
|
||||
|
||||
export const isAgentlessIntegration = (
|
||||
packageInfo: Pick<PackageInfo, 'policy_templates'> | undefined
|
||||
) => {
|
||||
if (
|
||||
packageInfo?.policy_templates &&
|
||||
packageInfo?.policy_templates.length > 0 &&
|
||||
!!packageInfo?.policy_templates.find(
|
||||
(policyTemplate) => policyTemplate?.deployment_modes?.agentless.enabled === true
|
||||
)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
export const getAgentlessAgentPolicyNameFromPackagePolicyName = (packagePolicyName: string) => {
|
||||
return `Agentless policy for ${packagePolicyName}`;
|
||||
};
|
||||
|
||||
export const isOnlyAgentlessIntegration = (
|
||||
packageInfo: Pick<PackageInfo, 'policy_templates'> | undefined
|
||||
) => {
|
||||
if (
|
||||
packageInfo?.policy_templates &&
|
||||
packageInfo?.policy_templates.length > 0 &&
|
||||
packageInfo?.policy_templates.every((policyTemplate) =>
|
||||
isOnlyAgentlessPolicyTemplate(policyTemplate)
|
||||
)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
export const isOnlyAgentlessPolicyTemplate = (policyTemplate: RegistryPolicyTemplate) => {
|
||||
return Boolean(
|
||||
policyTemplate.deployment_modes &&
|
||||
policyTemplate.deployment_modes.agentless.enabled === true &&
|
||||
(!policyTemplate.deployment_modes.default ||
|
||||
policyTemplate.deployment_modes.default.enabled === false)
|
||||
);
|
||||
};
|
||||
|
|
|
@ -20,7 +20,10 @@ import { SetupTechnology } from '../../../../../types';
|
|||
import { sendGetOneAgentPolicy, useStartServices } from '../../../../../hooks';
|
||||
import { SelectedPolicyTab } from '../../components';
|
||||
import { AGENTLESS_POLICY_ID } from '../../../../../../../../common/constants';
|
||||
import { getAgentlessAgentPolicyNameFromPackagePolicyName } from '../../../../../../../../common/services/agentless_policy_helper';
|
||||
import {
|
||||
isAgentlessIntegration as isAgentlessIntegrationFn,
|
||||
getAgentlessAgentPolicyNameFromPackagePolicyName,
|
||||
} from '../../../../../../../../common/services/agentless_policy_helper';
|
||||
|
||||
export const useAgentless = () => {
|
||||
const config = useConfig();
|
||||
|
@ -45,14 +48,7 @@ export const useAgentless = () => {
|
|||
|
||||
// When an integration has at least a policy template enabled for agentless
|
||||
const isAgentlessIntegration = (packageInfo: PackageInfo | undefined) => {
|
||||
if (
|
||||
isAgentlessEnabled &&
|
||||
packageInfo?.policy_templates &&
|
||||
packageInfo?.policy_templates.length > 0 &&
|
||||
!!packageInfo?.policy_templates.find(
|
||||
(policyTemplate) => policyTemplate?.deployment_modes?.agentless.enabled === true
|
||||
)
|
||||
) {
|
||||
if (isAgentlessEnabled && isAgentlessIntegrationFn(packageInfo)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
|
|
@ -135,7 +135,6 @@ export function usePackagePolicySteps({
|
|||
setNewAgentPolicy,
|
||||
updateAgentPolicies,
|
||||
setSelectedPolicyTab,
|
||||
packageInfo,
|
||||
packagePolicy,
|
||||
isEditPage: true,
|
||||
agentPolicies,
|
||||
|
|
|
@ -11,8 +11,10 @@ import { uniq } from 'lodash';
|
|||
import type { CustomIntegration } from '@kbn/custom-integrations-plugin/common';
|
||||
|
||||
import type { IntegrationPreferenceType } from '../../../components/integration_preference';
|
||||
import { useGetPackagesQuery, useGetCategoriesQuery } from '../../../../../hooks';
|
||||
import { useAgentless } from '../../../../../../fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/setup_technology';
|
||||
import {
|
||||
useGetPackagesQuery,
|
||||
useGetCategoriesQuery,
|
||||
useGetAppendCustomIntegrationsQuery,
|
||||
useGetReplacementCustomIntegrationsQuery,
|
||||
} from '../../../../../hooks';
|
||||
|
@ -28,6 +30,11 @@ import {
|
|||
isIntegrationPolicyTemplate,
|
||||
} from '../../../../../../../../common/services';
|
||||
|
||||
import {
|
||||
isOnlyAgentlessPolicyTemplate,
|
||||
isOnlyAgentlessIntegration,
|
||||
} from '../../../../../../../../common/services/agentless_policy_helper';
|
||||
|
||||
import type { IntegrationCardItem } from '..';
|
||||
|
||||
import { ALL_CATEGORY } from '../category_facets';
|
||||
|
@ -103,6 +110,23 @@ const packageListToIntegrationsList = (packages: PackageList): PackageList => {
|
|||
}, []);
|
||||
};
|
||||
|
||||
// Return filtered packages based on deployment mode,
|
||||
// Currently filters out agentless only packages and policy templates if agentless is not available
|
||||
const filterPackageListDeploymentModes = (packages: PackageList, isAgentlessEnabled: boolean) => {
|
||||
return isAgentlessEnabled
|
||||
? packages
|
||||
: packages
|
||||
.filter((pkg) => {
|
||||
return !isOnlyAgentlessIntegration(pkg);
|
||||
})
|
||||
.map((pkg) => {
|
||||
pkg.policy_templates = (pkg.policy_templates || []).filter((policyTemplate) => {
|
||||
return !isOnlyAgentlessPolicyTemplate(policyTemplate);
|
||||
});
|
||||
return pkg;
|
||||
});
|
||||
};
|
||||
|
||||
export type AvailablePackagesHookType = typeof useAvailablePackages;
|
||||
|
||||
export const useAvailablePackages = ({
|
||||
|
@ -113,6 +137,7 @@ export const useAvailablePackages = ({
|
|||
const [preference, setPreference] = useState<IntegrationPreferenceType>('recommended');
|
||||
|
||||
const { showIntegrationsSubcategories } = ExperimentalFeaturesService.get();
|
||||
const { isAgentlessEnabled } = useAgentless();
|
||||
|
||||
const {
|
||||
initialSelectedCategory,
|
||||
|
@ -146,10 +171,13 @@ export const useAvailablePackages = ({
|
|||
});
|
||||
}
|
||||
|
||||
const eprIntegrationList = useMemo(
|
||||
() => packageListToIntegrationsList(eprPackages?.items || []),
|
||||
[eprPackages]
|
||||
);
|
||||
const eprIntegrationList = useMemo(() => {
|
||||
const filteredPackageList =
|
||||
filterPackageListDeploymentModes(eprPackages?.items || [], isAgentlessEnabled) || [];
|
||||
const integrations = packageListToIntegrationsList(filteredPackageList);
|
||||
return integrations;
|
||||
}, [eprPackages?.items, isAgentlessEnabled]);
|
||||
|
||||
const {
|
||||
data: replacementCustomIntegrations,
|
||||
isInitialLoading: isLoadingReplacmentCustomIntegrations,
|
||||
|
|
|
@ -9,12 +9,27 @@ import React, { useContext } from 'react';
|
|||
|
||||
import type { FleetConfigType } from '../plugin';
|
||||
|
||||
import { useStartServices } from '.';
|
||||
|
||||
export const ConfigContext = React.createContext<FleetConfigType | null>(null);
|
||||
|
||||
export function useConfig() {
|
||||
const config = useContext(ConfigContext);
|
||||
if (config === null) {
|
||||
throw new Error('ConfigContext not initialized');
|
||||
export function useConfig(): FleetConfigType {
|
||||
const { fleet } = useStartServices();
|
||||
const baseConfig = useContext(ConfigContext);
|
||||
|
||||
// Downstream plugins may set `fleet` as part of the Kibana context
|
||||
// which means that the Fleet config is exposed in that way
|
||||
const pluginConfig = fleet?.config;
|
||||
const config = baseConfig || pluginConfig || null;
|
||||
|
||||
if (baseConfig === null && pluginConfig) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn('Fleet ConfigContext not initialized, using from plugin context');
|
||||
}
|
||||
|
||||
if (!config) {
|
||||
throw new Error('Fleet ConfigContext not initialized');
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
|
|
@ -7,10 +7,11 @@
|
|||
|
||||
import { useKibana } from '@kbn/kibana-react-plugin/public';
|
||||
|
||||
import type { FleetStartServices } from '../plugin';
|
||||
import type { FleetStart, FleetStartServices } from '../plugin';
|
||||
|
||||
export function useStartServices(): FleetStartServices {
|
||||
const { services } = useKibana<FleetStartServices>();
|
||||
// Downstream plugins may set `fleet` as part of the Kibana context
|
||||
export function useStartServices(): FleetStartServices & { fleet?: FleetStart } {
|
||||
const { services } = useKibana<FleetStartServices & { fleet?: FleetStart }>();
|
||||
if (services === null) {
|
||||
throw new Error('KibanaContextProvider not initialized');
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ import type { UIExtensionsStorage } from '../types';
|
|||
import { createExtensionRegistrationCallback } from '../services/ui_extensions';
|
||||
|
||||
import type { MockedFleetStart } from './types';
|
||||
import { createConfigurationMock } from './plugin_configuration';
|
||||
|
||||
export const createStartMock = (extensionsStorage: UIExtensionsStorage = {}): MockedFleetStart => {
|
||||
return {
|
||||
|
@ -41,6 +42,7 @@ export const createStartMock = (extensionsStorage: UIExtensionsStorage = {}): Mo
|
|||
writeIntegrationPolicies: true,
|
||||
},
|
||||
},
|
||||
config: createConfigurationMock(),
|
||||
hooks: { epm: { getBulkAssets: jest.fn() } },
|
||||
};
|
||||
};
|
||||
|
|
|
@ -102,6 +102,7 @@ export interface FleetSetup {}
|
|||
export interface FleetStart {
|
||||
/** Authorization for the current user */
|
||||
authz: FleetAuthz;
|
||||
config: FleetConfigType;
|
||||
registerExtension: UIExtensionRegistrationCallback;
|
||||
isInitialized: () => Promise<true>;
|
||||
hooks: {
|
||||
|
@ -356,7 +357,7 @@ export class FleetPlugin implements Plugin<FleetSetup, FleetStart, FleetSetupDep
|
|||
// capabilities.fleetv2 returns fleet privileges and capabilities.fleet returns integrations privileges
|
||||
return {
|
||||
authz,
|
||||
|
||||
config: this.config,
|
||||
isInitialized: once(async () => {
|
||||
const permissionsResponse = await getPermissions();
|
||||
|
||||
|
|
|
@ -45,6 +45,7 @@ import {
|
|||
PackageSavedObjectConflictError,
|
||||
FleetTooManyRequestsError,
|
||||
AgentlessPolicyExistsRequestError,
|
||||
PackageInvalidDeploymentMode,
|
||||
PackagePolicyContentPackageError,
|
||||
} from '.';
|
||||
|
||||
|
@ -61,6 +62,9 @@ interface IngestErrorHandlerParams {
|
|||
// this type is based on BadRequest values observed while debugging https://github.com/elastic/kibana/issues/75862
|
||||
const getHTTPResponseCode = (error: FleetError): number => {
|
||||
// Bad Request
|
||||
if (error instanceof PackageInvalidDeploymentMode) {
|
||||
return 400;
|
||||
}
|
||||
if (error instanceof PackageFailedVerificationError) {
|
||||
return 400;
|
||||
}
|
||||
|
|
|
@ -29,6 +29,7 @@ export class RegistryResponseError extends RegistryError {
|
|||
|
||||
// Package errors
|
||||
|
||||
export class PackageInvalidDeploymentMode extends FleetError {}
|
||||
export class PackageOutdatedError extends FleetError {}
|
||||
export class PackageFailedVerificationError extends FleetError {
|
||||
constructor(pkgName: string, pkgVersion: string) {
|
||||
|
|
|
@ -17,6 +17,7 @@ import { licenseService } from '../../license';
|
|||
import { auditLoggingService } from '../../audit_logging';
|
||||
import { appContextService } from '../../app_context';
|
||||
import { ConcurrentInstallOperationError, FleetError, PackageNotFoundError } from '../../../errors';
|
||||
import { isAgentlessEnabled, isOnlyAgentlessIntegration } from '../../utils/agentless';
|
||||
|
||||
import * as Registry from '../registry';
|
||||
import { dataStreamService } from '../../data_streams';
|
||||
|
@ -102,6 +103,13 @@ jest.mock('../archive', () => {
|
|||
});
|
||||
jest.mock('../../audit_logging');
|
||||
|
||||
jest.mock('../../utils/agentless', () => {
|
||||
return {
|
||||
isAgentlessEnabled: jest.fn(),
|
||||
isOnlyAgentlessIntegration: jest.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
const mockGetBundledPackageByPkgKey = jest.mocked(getBundledPackageByPkgKey);
|
||||
const mockedAuditLoggingService = jest.mocked(auditLoggingService);
|
||||
|
||||
|
@ -357,13 +365,72 @@ describe('install', () => {
|
|||
expect(response.status).toEqual('already_installed');
|
||||
});
|
||||
|
||||
// failing
|
||||
describe('agentless', () => {
|
||||
beforeEach(() => {
|
||||
jest.mocked(appContextService.getConfig).mockClear();
|
||||
jest.spyOn(licenseService, 'hasAtLeast').mockClear();
|
||||
jest.mocked(isAgentlessEnabled).mockClear();
|
||||
jest.mocked(isOnlyAgentlessIntegration).mockClear();
|
||||
});
|
||||
|
||||
it('should not allow to install agentless only integration if agentless is not enabled', async () => {
|
||||
jest.spyOn(licenseService, 'hasAtLeast').mockReturnValue(true);
|
||||
jest.mocked(isAgentlessEnabled).mockReturnValueOnce(false);
|
||||
jest.mocked(isOnlyAgentlessIntegration).mockReturnValueOnce(true);
|
||||
|
||||
const response = await installPackage({
|
||||
spaceId: DEFAULT_SPACE_ID,
|
||||
installSource: 'registry',
|
||||
pkgkey: 'test_package',
|
||||
savedObjectsClient: savedObjectsClientMock.create(),
|
||||
esClient: {} as ElasticsearchClient,
|
||||
});
|
||||
expect(response.error).toBeDefined();
|
||||
expect(response.error!.message).toEqual(
|
||||
'test_package contains agentless policy templates, agentless is not available on this deployment'
|
||||
);
|
||||
});
|
||||
|
||||
it('should allow to install agentless only integration if agentless is not enabled but using force flag', async () => {
|
||||
jest.spyOn(licenseService, 'hasAtLeast').mockReturnValue(true);
|
||||
jest.mocked(isAgentlessEnabled).mockReturnValueOnce(false);
|
||||
jest.mocked(isOnlyAgentlessIntegration).mockReturnValueOnce(true);
|
||||
|
||||
const response = await installPackage({
|
||||
spaceId: DEFAULT_SPACE_ID,
|
||||
installSource: 'registry',
|
||||
pkgkey: 'test_package',
|
||||
savedObjectsClient: savedObjectsClientMock.create(),
|
||||
esClient: {} as ElasticsearchClient,
|
||||
force: true,
|
||||
});
|
||||
expect(response.error).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should allow to install agentless only integration if agentless is enabled', async () => {
|
||||
jest.spyOn(licenseService, 'hasAtLeast').mockReturnValue(true);
|
||||
jest.mocked(isAgentlessEnabled).mockReturnValueOnce(true);
|
||||
jest.mocked(isOnlyAgentlessIntegration).mockReturnValueOnce(true);
|
||||
|
||||
const response = await installPackage({
|
||||
spaceId: DEFAULT_SPACE_ID,
|
||||
installSource: 'registry',
|
||||
pkgkey: 'test_package',
|
||||
savedObjectsClient: savedObjectsClientMock.create(),
|
||||
esClient: {} as ElasticsearchClient,
|
||||
});
|
||||
expect(response.error).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
it('should allow to install fleet_server if internal.fleetServerStandalone is configured', async () => {
|
||||
jest.mocked(appContextService.getConfig).mockReturnValueOnce({
|
||||
internal: {
|
||||
fleetServerStandalone: true,
|
||||
},
|
||||
} as any);
|
||||
jest.spyOn(licenseService, 'hasAtLeast').mockReturnValueOnce(true);
|
||||
jest.mocked(isOnlyAgentlessIntegration).mockReturnValueOnce(false);
|
||||
|
||||
const response = await installPackage({
|
||||
spaceId: DEFAULT_SPACE_ID,
|
||||
|
|
|
@ -60,6 +60,7 @@ import {
|
|||
FleetUnauthorizedError,
|
||||
PackageNotFoundError,
|
||||
FleetTooManyRequestsError,
|
||||
PackageInvalidDeploymentMode,
|
||||
} from '../../../errors';
|
||||
import {
|
||||
PACKAGES_SAVED_OBJECT_TYPE,
|
||||
|
@ -82,6 +83,8 @@ import { sendTelemetryEvents, UpdateEventType } from '../../upgrade_sender';
|
|||
import { auditLoggingService } from '../../audit_logging';
|
||||
import { getFilteredInstallPackages } from '../filtered_packages';
|
||||
|
||||
import { isAgentlessEnabled, isOnlyAgentlessIntegration } from '../../utils/agentless';
|
||||
|
||||
import { _stateMachineInstallPackage } from './install_state_machine/_state_machine_package_install';
|
||||
|
||||
import { formatVerificationResultForSO } from './package_verification';
|
||||
|
@ -507,6 +510,21 @@ async function installPackageFromRegistry({
|
|||
}`
|
||||
);
|
||||
}
|
||||
|
||||
// only allow install of agentless packages if agentless is enabled, or if using force flag
|
||||
const agentlessEnabled = isAgentlessEnabled();
|
||||
const agentlessOnlyIntegration = isOnlyAgentlessIntegration(packageInfo);
|
||||
if (!agentlessEnabled && agentlessOnlyIntegration) {
|
||||
if (!force) {
|
||||
throw new PackageInvalidDeploymentMode(
|
||||
`${pkgkey} contains agentless policy templates, agentless is not available on this deployment`
|
||||
);
|
||||
}
|
||||
logger.debug(
|
||||
`${pkgkey} contains agentless policy templates, agentless is not available on this deployment but installing anyway due to force flag`
|
||||
);
|
||||
}
|
||||
|
||||
return await installPackageWithStateMachine({
|
||||
pkgName,
|
||||
pkgVersion,
|
||||
|
|
|
@ -7,14 +7,15 @@
|
|||
|
||||
import { appContextService } from '..';
|
||||
import type { FleetConfigType } from '../../config';
|
||||
export { isOnlyAgentlessIntegration } from '../../../common/services/agentless_policy_helper';
|
||||
|
||||
export const isAgentlessApiEnabled = () => {
|
||||
const cloudSetup = appContextService.getCloud();
|
||||
const cloudSetup = appContextService.getCloud && appContextService.getCloud();
|
||||
const isHosted = cloudSetup?.isCloudEnabled || cloudSetup?.isServerlessEnabled;
|
||||
return Boolean(isHosted && appContextService.getConfig()?.agentless?.enabled);
|
||||
};
|
||||
export const isDefaultAgentlessPolicyEnabled = () => {
|
||||
const cloudSetup = appContextService.getCloud();
|
||||
const cloudSetup = appContextService.getCloud && appContextService.getCloud();
|
||||
return Boolean(
|
||||
cloudSetup?.isServerlessEnabled && appContextService.getExperimentalFeatures().agentless
|
||||
);
|
||||
|
@ -44,7 +45,7 @@ export const prependAgentlessApiBasePathToEndpoint = (
|
|||
agentlessConfig: FleetConfigType['agentless'],
|
||||
endpoint: AgentlessApiEndpoints
|
||||
) => {
|
||||
const cloudSetup = appContextService.getCloud();
|
||||
const cloudSetup = appContextService.getCloud && appContextService.getCloud();
|
||||
const endpointPrefix = cloudSetup?.isServerlessEnabled
|
||||
? AGENTLESS_SERVERLESS_API_BASE_PATH
|
||||
: AGENTLESS_ESS_API_BASE_PATH;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue