Skip to content

Commit

Permalink
[Fleet] Disallow some egress-specific inputs for agentless integratio…
Browse files Browse the repository at this point in the history
…ns (elastic#206074)

Closes elastic#202091

## Summary

Disallow some egress-specific inputs for agentless integrations. 
- In the policy editor, when Setup technology dropdown is set to
Agentless, hide the rendering of configuration for inputs that have type
matching the blocklist and ensure that these inputs are set to `enabled:
false`
- `tcp, udp, winlog, http_endpoint, filestream` should be disabled when
`supports_agentless: true`
- At the API level, throw an error if attempting to enable a disallowed
input type


### Testing
Simulate agentless env with following setup in `kibana.dev.yml`:
```
xpack.cloud.id: 'anything-to-pass-cloud-validation-checks'
xpack.fleet.agentless.enabled: true
xpack.fleet.agentless.api.url: 'https://localhost:8443'
xpack.fleet.agentless.api.tls.certificate: './config/certs/ess-client.crt'
xpack.fleet.agentless.api.tls.key: './config/certs/ess-client.key'
xpack.fleet.agentless.api.tls.ca: './config/certs/ca.crt'
```
-Apply [this
patch](https://gist.github.com/jen-huang/dfc3e02ceb63976ad54bd1f50c524cb4)
to prevent attempt to create agentless pod (the agentless policy
creation fails without the patch)
- Install the following test integration, that has a bunch of different
inputs to simulate this specific case and is enabled for agentless (it
shows the setup technology as well)

[agentless_package_links-0.0.2.zip](https://github.com/user-attachments/files/18425895/agentless_package_links-0.0.2.zip)

```
curl -XPOST -H 'content-type: application/zip' -H 'kbn-xsrf: true' http://localhost:5601/YOUR_PATH/api/fleet/epm/packages -u elastic:changeme --data-binary @agentless_package_links-0.0.2.zip
```
- Navigate to the integrations page, find the above integration and test
that switching between agent-based/agentless the enabled inputs change
as follows:

<img width="1288" alt="Screenshot 2025-01-15 at 15 30 28"
src="https://github.com/user-attachments/assets/6abd45d7-1bd8-465a-af29-4c34940b32e3"
/>
<img width="1072" alt="Screenshot 2025-01-15 at 15 31 18"
src="https://github.com/user-attachments/assets/6957562f-08a6-403a-8725-1a654e443537"
/>


- Verify that the preview flyout has the correct inputs based on the
selected deployment mode

<img width="863" alt="Screenshot 2025-01-15 at 15 32 19"
src="https://github.com/user-attachments/assets/ceca1f5d-249c-4ee1-9295-6f01ae21fdb4"
/>
<img width="862" alt="Screenshot 2025-01-15 at 15 33 33"
src="https://github.com/user-attachments/assets/f43562d7-633e-4f0a-bfc1-19e89aef7659"
/>


- Verify that the api throws an error when attempting to enable any of
the disallowed types
<img width="1774" alt="Screenshot 2025-01-15 at 15 36 03"
src="https://github.com/user-attachments/assets/2b4d24a3-5adc-4ab2-bbad-83b44d348763"
/>



### Checklist
- [ ] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios
- [ ] The PR description includes the appropriate Release Notes section,
and the correct `release_note:*` label is applied per the
[guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)

---------

Co-authored-by: Elastic Machine <[email protected]>
  • Loading branch information
2 people authored and viduni94 committed Jan 23, 2025
1 parent d81e91f commit fa121ad
Show file tree
Hide file tree
Showing 13 changed files with 293 additions and 68 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,6 @@ export const AGENTLESS_AGENT_POLICY_MONITORING: MonitoringType = ['logs', 'metri
export const AGENTLESS_GLOBAL_TAG_NAME_ORGANIZATION = 'organization';
export const AGENTLESS_GLOBAL_TAG_NAME_DIVISION = 'division';
export const AGENTLESS_GLOBAL_TAG_NAME_TEAM = 'team';

// Input types to disable for agentless integrations
export const AGENTLESS_DISABLED_INPUTS = ['tcp', 'udp', 'filestream', 'http_endpoint', 'winlog'];
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@
* 2.0.
*/

import type { PackageInfo, RegistryPolicyTemplate } from '../types';
import { AGENTLESS_DISABLED_INPUTS } from '../constants';
import { PackagePolicyValidationError } from '../errors';
import type { NewPackagePolicyInput, PackageInfo, RegistryPolicyTemplate } from '../types';

import type { SimplifiedInputs } from './simplified_package_policy_helper';

export const isAgentlessIntegration = (
packageInfo: Pick<PackageInfo, 'policy_templates'> | undefined
Expand Down Expand Up @@ -49,3 +53,45 @@ export const isOnlyAgentlessPolicyTemplate = (policyTemplate: RegistryPolicyTemp
policyTemplate.deployment_modes.default.enabled === false)
);
};

/*
* Check if the package policy inputs is not allowed in agentless
*/
export function inputNotAllowedInAgentless(inputType: string, supportsAgentless?: boolean | null) {
return supportsAgentless === true && AGENTLESS_DISABLED_INPUTS.includes(inputType);
}

/*
* Throw error if trying to enabling an input that is not allowed in agentless
*/
export function validateAgentlessInputs(
packagePolicyInputs: NewPackagePolicyInput[] | SimplifiedInputs,
supportsAgentless?: boolean | null
) {
if (Array.isArray(packagePolicyInputs)) {
return packagePolicyInputs.forEach((input) => {
throwIfInputNotAllowed(input.type, input.enabled, supportsAgentless);
});
} else {
Object.keys(packagePolicyInputs).forEach((inputName) => {
const input = packagePolicyInputs[inputName];
const match = inputName.match(/\-(\w*)$/);
const inputType = match && match.length > 0 ? match[1] : '';
throwIfInputNotAllowed(inputType, input?.enabled ?? false, supportsAgentless);
});
}
}

function throwIfInputNotAllowed(
inputType: string,
inputEnabled: boolean,
supportsAgentless?: boolean | null
) {
if (inputNotAllowedInAgentless(inputType, supportsAgentless) && inputEnabled === true) {
throw new PackagePolicyValidationError(
`Input ${inputType} is not allowed: types '${AGENTLESS_DISABLED_INPUTS.map(
(name) => name
).join(', ')}' cannot be enabled for an Agentless integration`
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,11 @@ import type {
PackageInfo,
ExperimentalDataStreamFeature,
} from '../types';
import { DATASET_VAR_NAME } from '../constants';
import { AGENTLESS_DISABLED_INPUTS, DATASET_VAR_NAME } from '../constants';
import { PackagePolicyValidationError } from '../errors';

import { packageToPackagePolicy } from '.';
import { inputNotAllowedInAgentless } from './agentless_policy_helper';

export type SimplifiedVars = Record<string, string | string[] | boolean | number | number[] | null>;

Expand Down Expand Up @@ -75,14 +76,18 @@ export function generateInputId(input: NewPackagePolicyInput) {
return `${input.policy_template ? `${input.policy_template}-` : ''}${input.type}`;
}

export function formatInputs(inputs: NewPackagePolicy['inputs']) {
export function formatInputs(inputs: NewPackagePolicy['inputs'], supportsAgentless?: boolean) {
return inputs.reduce((acc, input) => {
const inputId = generateInputId(input);
if (!acc) {
acc = {};
}
const enabled =
supportsAgentless === true && AGENTLESS_DISABLED_INPUTS.includes(input.type)
? false
: input.enabled;
acc[inputId] = {
enabled: input.enabled,
enabled,
vars: formatVars(input.vars),
streams: formatStreams(input.streams),
};
Expand Down Expand Up @@ -196,7 +201,10 @@ export function simplifiedPackagePolicytoNewPackagePolicy(
throw new PackagePolicyValidationError(`Input not found: ${inputId}`);
}

if (enabled === false) {
if (
inputNotAllowedInAgentless(packagePolicyInput.type, packagePolicy?.supports_agentless) ||
enabled === false
) {
packagePolicyInput.enabled = false;
} else {
packagePolicyInput.enabled = true;
Expand All @@ -213,7 +221,10 @@ export function simplifiedPackagePolicytoNewPackagePolicy(
throw new PackagePolicyValidationError(`Stream not found ${inputId}: ${streamId}`);
}

if (streamEnabled === false) {
if (
streamEnabled === false ||
inputNotAllowedInAgentless(packagePolicyInput.type, packagePolicy?.supports_agentless)
) {
packagePolicyStream.enabled = false;
} else {
packagePolicyStream.enabled = true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ import { doesPackageHaveIntegrations } from '../../../../../services';

import type { PackagePolicyValidationResults } from '../../services';

import { AGENTLESS_DISABLED_INPUTS } from '../../../../../../../../common/constants';

import { PackagePolicyInputPanel } from './components';

export const StepConfigurePackagePolicy: React.FunctionComponent<{
Expand All @@ -38,6 +40,7 @@ export const StepConfigurePackagePolicy: React.FunctionComponent<{
submitAttempted: boolean;
noTopRule?: boolean;
isEditPage?: boolean;
isAgentlessSelected?: boolean;
}> = ({
packageInfo,
showOnlyIntegration,
Expand All @@ -47,6 +50,7 @@ export const StepConfigurePackagePolicy: React.FunctionComponent<{
submitAttempted,
noTopRule = false,
isEditPage = false,
isAgentlessSelected = false,
}) => {
const hasIntegrations = useMemo(() => doesPackageHaveIntegrations(packageInfo), [packageInfo]);
const packagePolicyTemplates = useMemo(
Expand All @@ -66,8 +70,9 @@ export const StepConfigurePackagePolicy: React.FunctionComponent<{
<EuiFlexGroup direction="column" gutterSize="none">
{packagePolicyTemplates.map((policyTemplate) => {
const inputs = getNormalizedInputs(policyTemplate);
const packagePolicyInputs = packagePolicy.inputs;
return inputs.map((packageInput) => {
const packagePolicyInput = packagePolicy.inputs.find(
const packagePolicyInput = packagePolicyInputs.find(
(input) =>
input.type === packageInput.type &&
(hasIntegrations ? input.policy_template === policyTemplate.name : true)
Expand All @@ -79,28 +84,35 @@ export const StepConfigurePackagePolicy: React.FunctionComponent<{
? policyTemplate.data_streams
: []
);
return packagePolicyInput ? (

const updatePackagePolicyInput = (updatedInput: Partial<NewPackagePolicyInput>) => {
const indexOfUpdatedInput = packagePolicyInputs.findIndex(
(input) =>
input.type === packageInput.type &&
(hasIntegrations ? input.policy_template === policyTemplate.name : true)
);
const newInputs = [...packagePolicyInputs];
newInputs[indexOfUpdatedInput] = {
...newInputs[indexOfUpdatedInput],
...updatedInput,
};
updatePackagePolicy({
inputs: newInputs,
});
};

return packagePolicyInput &&
!(
(isAgentlessSelected || packagePolicy.supports_agentless === true) &&
AGENTLESS_DISABLED_INPUTS.includes(packagePolicyInput.type)
) ? (
<EuiFlexItem key={packageInput.type}>
<PackagePolicyInputPanel
packageInput={packageInput}
packageInfo={packageInfo}
packageInputStreams={packageInputStreams}
packagePolicyInput={packagePolicyInput}
updatePackagePolicyInput={(updatedInput: Partial<NewPackagePolicyInput>) => {
const indexOfUpdatedInput = packagePolicy.inputs.findIndex(
(input) =>
input.type === packageInput.type &&
(hasIntegrations ? input.policy_template === policyTemplate.name : true)
);
const newInputs = [...packagePolicy.inputs];
newInputs[indexOfUpdatedInput] = {
...newInputs[indexOfUpdatedInput],
...updatedInput,
};
updatePackagePolicy({
inputs: newInputs,
});
}}
updatePackagePolicyInput={updatePackagePolicyInput}
inputValidationResults={
validationResults?.inputs?.[
hasIntegrations
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@ describe('useOnSubmit', () => {
newAgentPolicy: { name: 'test', namespace: '' },
queryParamsPolicyId: undefined,
hasFleetAddAgentsPrivileges: true,
setNewAgentPolicy: jest.fn(),
setSelectedPolicyTab: jest.fn(),
})
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,21 @@
* 2.0.
*/

import { useCallback, useEffect, useRef, useState } from 'react';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { i18n } from '@kbn/i18n';
import { load } from 'js-yaml';

import { isEqual } from 'lodash';

import { useSpaceSettingsContext } from '../../../../../../../hooks/use_space_settings_context';
import type {
AgentPolicy,
NewPackagePolicy,
NewAgentPolicy,
CreatePackagePolicyRequest,
PackagePolicy,
PackageInfo,
import {
type AgentPolicy,
type NewPackagePolicy,
type NewAgentPolicy,
type CreatePackagePolicyRequest,
type PackagePolicy,
type PackageInfo,
SetupTechnology,
} from '../../../../../types';
import {
useStartServices,
Expand Down Expand Up @@ -49,7 +50,9 @@ import {
getCloudShellUrlFromPackagePolicy,
} from '../../../../../../../components/cloud_security_posture/services';

import { useAgentless } from './setup_technology';
import { AGENTLESS_DISABLED_INPUTS } from '../../../../../../../../common/constants';

import { useAgentless, useSetupTechnology } from './setup_technology';

export async function createAgentPolicy({
packagePolicy,
Expand Down Expand Up @@ -142,6 +145,8 @@ export function useOnSubmit({
packageInfo,
integrationToEnable,
hasFleetAddAgentsPrivileges,
setNewAgentPolicy,
setSelectedPolicyTab,
}: {
packageInfo?: PackageInfo;
newAgentPolicy: NewAgentPolicy;
Expand All @@ -151,6 +156,8 @@ export function useOnSubmit({
queryParamsPolicyId: string | undefined;
integrationToEnable?: string;
hasFleetAddAgentsPrivileges: boolean;
setNewAgentPolicy: (policy: NewAgentPolicy) => void;
setSelectedPolicyTab: (tab: SelectedPolicyTab) => void;
}) {
const { notifications } = useStartServices();
const confirmForceInstall = useConfirmForceInstall();
Expand All @@ -167,6 +174,11 @@ export function useOnSubmit({
// Used to initialize the package policy once
const isInitializedRef = useRef(false);

// only used to save the initial value of the package policy
const [initialPackagePolicy, setInitialPackagePolicy] = useState<NewPackagePolicy>({
...DEFAULT_PACKAGE_POLICY,
});

const [agentPolicies, setAgentPolicies] = useState<AgentPolicy[]>([]);
// New package policy state
const [packagePolicy, setPackagePolicy] = useState<NewPackagePolicy>({
Expand Down Expand Up @@ -255,20 +267,27 @@ export function useOnSubmit({
const incrementedName = getMaxPackageName(packageInfo.name, packagePolicyData?.items);

isInitializedRef.current = true;
updatePackagePolicy(
packageToPackagePolicy(
packageInfo,
agentPolicies.map((policy) => policy.id),
'',
DEFAULT_PACKAGE_POLICY.name || incrementedName,
DEFAULT_PACKAGE_POLICY.description,
integrationToEnable
)
const basePackagePolicy = packageToPackagePolicy(
packageInfo,
agentPolicies.map((policy) => policy.id),
'',
DEFAULT_PACKAGE_POLICY.name || incrementedName,
DEFAULT_PACKAGE_POLICY.description,
integrationToEnable
);
setInitialPackagePolicy(basePackagePolicy);
updatePackagePolicy(basePackagePolicy);
setIsInitialized(true);
}
init();
}, [packageInfo, agentPolicies, updatePackagePolicy, integrationToEnable, isInitialized]);
}, [
packageInfo,
agentPolicies,
updatePackagePolicy,
integrationToEnable,
isInitialized,
initialPackagePolicy,
]);

useEffect(() => {
if (
Expand All @@ -284,6 +303,42 @@ export function useOnSubmit({
}
}, [packagePolicy, agentPolicies, updatePackagePolicy, canUseMultipleAgentPolicies]);

const { handleSetupTechnologyChange, selectedSetupTechnology, defaultSetupTechnology } =
useSetupTechnology({
newAgentPolicy,
setNewAgentPolicy,
updateAgentPolicies,
updatePackagePolicy,
setSelectedPolicyTab,
packageInfo,
packagePolicy,
});
const setupTechnologyRef = useRef<SetupTechnology | undefined>(selectedSetupTechnology);
// sync the inputs with the agentless selector change
useEffect(() => {
setupTechnologyRef.current = selectedSetupTechnology;
});
const prevSetupTechnology = setupTechnologyRef.current;
const isAgentlessSelected =
isAgentlessIntegration(packageInfo) && selectedSetupTechnology === SetupTechnology.AGENTLESS;

const newInputs = useMemo(() => {
return packagePolicy.inputs.map((input, i) => {
if (isAgentlessSelected && AGENTLESS_DISABLED_INPUTS.includes(input.type)) {
return { ...input, enabled: false };
}
return initialPackagePolicy.inputs[i];
});
}, [initialPackagePolicy?.inputs, isAgentlessSelected, packagePolicy.inputs]);

useEffect(() => {
if (prevSetupTechnology !== selectedSetupTechnology) {
updatePackagePolicy({
inputs: newInputs,
});
}
}, [newInputs, prevSetupTechnology, selectedSetupTechnology, updatePackagePolicy, packagePolicy]);

const onSaveNavigate = useOnSaveNavigate({
packagePolicy,
queryParamsPolicyId,
Expand Down Expand Up @@ -495,5 +550,9 @@ export function useOnSubmit({
// TODO check
navigateAddAgent,
navigateAddAgentHelp,
handleSetupTechnologyChange,
selectedSetupTechnology,
defaultSetupTechnology,
isAgentlessSelected,
};
}
Loading

0 comments on commit fa121ad

Please sign in to comment.