Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[8.x] [EDR Workflows] Fix Endpoint list RBAC problems (#199803) #203534

Merged
merged 1 commit into from
Dec 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@
* 2.0.
*/

import { calculateEndpointAuthz, getEndpointAuthzInitialState } from './authz';
import {
calculateEndpointAuthz,
canFetchPackageAndAgentPolicies,
getEndpointAuthzInitialState,
} from './authz';
import type { FleetAuthz } from '@kbn/fleet-plugin/common';
import { createFleetAuthzMock } from '@kbn/fleet-plugin/common/mocks';
import { createLicenseServiceMock } from '../../../license/mocks';
Expand All @@ -15,6 +19,7 @@ import {
RESPONSE_CONSOLE_ACTION_COMMANDS_TO_RBAC_FEATURE_CONTROL,
type ResponseConsoleRbacControls,
} from '../response_actions/constants';
import type { Capabilities } from '@kbn/core-capabilities-common';

describe('Endpoint Authz service', () => {
let licenseService: ReturnType<typeof createLicenseServiceMock>;
Expand Down Expand Up @@ -91,48 +96,53 @@ describe('Endpoint Authz service', () => {
);
});

it('should not give canAccessFleet if `fleet.all` is false', () => {
fleetAuthz.fleet.all = false;
expect(calculateEndpointAuthz(licenseService, fleetAuthz, userRoles).canAccessFleet).toBe(
false
);
});
describe('Fleet', () => {
[true, false].forEach((value) => {
it(`should set canAccessFleet to ${value} if \`fleet.all\` is ${value}`, () => {
fleetAuthz.fleet.all = value;
expect(calculateEndpointAuthz(licenseService, fleetAuthz, userRoles).canAccessFleet).toBe(
value
);
});

it('should not give canReadFleetAgents if `fleet.readAgents` is false', () => {
fleetAuthz.fleet.readAgents = false;
expect(calculateEndpointAuthz(licenseService, fleetAuthz, userRoles).canReadFleetAgents).toBe(
false
);
});
it(`should set canReadFleetAgents to ${value} if \`fleet.readAgents\` is ${value}`, () => {
fleetAuthz.fleet.readAgents = value;
expect(
calculateEndpointAuthz(licenseService, fleetAuthz, userRoles).canReadFleetAgents
).toBe(value);
});

it('should not give canWriteFleetAgents if `fleet.allAgents` is false', () => {
fleetAuthz.fleet.allAgents = false;
expect(
calculateEndpointAuthz(licenseService, fleetAuthz, userRoles).canWriteFleetAgents
).toBe(false);
});
it(`should set canWriteFleetAgents to ${value} if \`fleet.allAgents\` is ${value}`, () => {
fleetAuthz.fleet.allAgents = value;
expect(
calculateEndpointAuthz(licenseService, fleetAuthz, userRoles).canWriteFleetAgents
).toBe(value);
});

it('should not give canReadFleetAgentPolicies if `fleet.readAgentPolicies` is false', () => {
fleetAuthz.fleet.readAgentPolicies = false;
expect(
calculateEndpointAuthz(licenseService, fleetAuthz, userRoles).canReadFleetAgentPolicies
).toBe(false);
it(`should set canReadFleetAgentPolicies to ${value} if \`fleet.readAgentPolicies\` is ${value}`, () => {
fleetAuthz.fleet.readAgentPolicies = value;
expect(
calculateEndpointAuthz(licenseService, fleetAuthz, userRoles).canReadFleetAgentPolicies
).toBe(value);
});

it(`should set canWriteIntegrationPolicies to ${value} if \`integrations.writeIntegrationPolicies\` is ${value}`, () => {
fleetAuthz.integrations.writeIntegrationPolicies = value;
expect(
calculateEndpointAuthz(licenseService, fleetAuthz, userRoles)
.canWriteIntegrationPolicies
).toBe(value);
});
});
});

it('should not give canAccessEndpointManagement if not superuser', () => {
it('should set canAccessEndpointManagement if not superuser', () => {
userRoles = [];
expect(
calculateEndpointAuthz(licenseService, fleetAuthz, userRoles).canAccessEndpointManagement
).toBe(false);
});

it('should give canAccessFleet if `fleet.all` is true', () => {
fleetAuthz.fleet.all = true;
expect(calculateEndpointAuthz(licenseService, fleetAuthz, userRoles).canAccessFleet).toBe(
true
);
});

it('should give canAccessEndpointManagement if superuser', () => {
userRoles = ['superuser'];
expect(
Expand Down Expand Up @@ -303,6 +313,7 @@ describe('Endpoint Authz service', () => {
canReadFleetAgentPolicies: false,
canReadFleetAgents: false,
canWriteFleetAgents: false,
canWriteIntegrationPolicies: false,
canAccessEndpointActionsLogManagement: false,
canAccessEndpointManagement: false,
canCreateArtifactsByPolicy: false,
Expand Down Expand Up @@ -336,4 +347,64 @@ describe('Endpoint Authz service', () => {
});
});
});

describe('canFetchPackageAndAgentPolicies()', () => {
describe('without granular Fleet permissions', () => {
it.each`
readFleet | readIntegrations | readPolicyManagement | result
${false} | ${false} | ${false} | ${false}
${true} | ${false} | ${false} | ${false}
${false} | ${true} | ${false} | ${false}
${true} | ${true} | ${false} | ${true}
${false} | ${false} | ${true} | ${true}
${true} | ${false} | ${true} | ${true}
${false} | ${true} | ${true} | ${true}
${true} | ${true} | ${true} | ${true}
`(
'should return $result when readFleet is $readFleet, readIntegrations is $readIntegrations and readPolicyManagement is $readPolicyManagement',
({ readFleet, readIntegrations, readPolicyManagement, result }) => {
const capabilities: Partial<Capabilities> = {
siem: { readPolicyManagement },
fleetv2: { read: readFleet },
fleet: { read: readIntegrations },
};

expect(canFetchPackageAndAgentPolicies(capabilities as Capabilities)).toBe(result);
}
);
});

describe('with granular Fleet permissions', () => {
it.each`
readFleet | readAgentPolicies | readIntegrations | readPolicyManagement | result
${false} | ${false} | ${false} | ${false} | ${false}
${false} | ${false} | ${true} | ${false} | ${false}
${false} | ${false} | ${false} | ${true} | ${true}
${false} | ${false} | ${true} | ${true} | ${true}
${false} | ${true} | ${false} | ${false} | ${false}
${false} | ${true} | ${true} | ${false} | ${false}
${false} | ${true} | ${false} | ${true} | ${true}
${false} | ${true} | ${true} | ${true} | ${true}
${true} | ${false} | ${false} | ${false} | ${false}
${true} | ${false} | ${true} | ${false} | ${false}
${true} | ${false} | ${false} | ${true} | ${true}
${true} | ${false} | ${true} | ${true} | ${true}
${true} | ${true} | ${false} | ${false} | ${false}
${true} | ${true} | ${true} | ${false} | ${true}
${true} | ${true} | ${false} | ${true} | ${true}
${true} | ${true} | ${true} | ${true} | ${true}
`(
'should return $result when readAgentPolicies is $readAgentPolicies, readFleet is $readFleet, readIntegrations is $readIntegrations and readPolicyManagement is $readPolicyManagement',
({ readAgentPolicies, readFleet, readIntegrations, readPolicyManagement, result }) => {
const capabilities: Partial<Capabilities> = {
siem: { readPolicyManagement },
fleetv2: { read: readFleet, agent_policies_read: readAgentPolicies },
fleet: { read: readIntegrations },
};

expect(canFetchPackageAndAgentPolicies(capabilities as Capabilities)).toBe(result);
}
);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import type { ENDPOINT_PRIVILEGES, FleetAuthz } from '@kbn/fleet-plugin/common';

import { omit } from 'lodash';
import type { Capabilities } from '@kbn/core-capabilities-common';
import type { ProductFeaturesService } from '../../../../server/lib/product_features_service';
import { RESPONSE_CONSOLE_ACTION_COMMANDS_TO_REQUIRED_AUTHZ } from '../response_actions/constants';
import type { LicenseService } from '../../../license';
Expand Down Expand Up @@ -99,10 +100,19 @@ export const calculateEndpointAuthz = (
const authz: EndpointAuthz = {
canWriteSecuritySolution,
canReadSecuritySolution,

// ---------------------------------------------------------
// Coming from Fleet authz
// ---------------------------------------------------------
canAccessFleet: fleetAuthz?.fleet.all ?? false,
canReadFleetAgentPolicies: fleetAuthz?.fleet.readAgentPolicies ?? false,
canWriteFleetAgents: fleetAuthz?.fleet.allAgents ?? false,
canReadFleetAgents: fleetAuthz?.fleet.readAgents ?? false,
canWriteIntegrationPolicies: fleetAuthz?.integrations.writeIntegrationPolicies ?? false,

// ---------------------------------------------------------
// Endpoint & policy management
// ---------------------------------------------------------
canAccessEndpointManagement: hasEndpointManagementAccess, // TODO: is this one deprecated? it is the only place we need to check for superuser.
canCreateArtifactsByPolicy: isPlatinumPlusLicense,
canWriteEndpointList,
Expand Down Expand Up @@ -166,6 +176,7 @@ export const getEndpointAuthzInitialState = (): EndpointAuthz => {
canReadFleetAgentPolicies: false,
canReadFleetAgents: false,
canWriteFleetAgents: false,
canWriteIntegrationPolicies: false,
canAccessEndpointActionsLogManagement: false,
canAccessEndpointManagement: false,
canCreateArtifactsByPolicy: false,
Expand Down Expand Up @@ -198,3 +209,24 @@ export const getEndpointAuthzInitialState = (): EndpointAuthz => {
canWriteEndpointExceptions: false,
};
};

/**
* Duplicate logic to calculate if user has privilege to fetch Agent Policies,
* working only with Capabilities, in order to be able to use it e.g. in middleware.
*
* The logic works with Fleet granular privileges (`subfeaturePrivileges`) both enabled and disabled.
*
* @param capabilities Capabilities from coreStart.application
*/
export const canFetchPackageAndAgentPolicies = (capabilities: Capabilities): boolean => {
const canReadPolicyManagement = Boolean(capabilities.siem?.readPolicyManagement);

const fleetv2 = capabilities.fleetv2;
const canReadFleetAgentPolicies = Boolean(
fleetv2?.read && (fleetv2?.agent_policies_read ?? true)
);

const canReadIntegrations = Boolean(capabilities.fleet?.read);

return canReadPolicyManagement || (canReadFleetAgentPolicies && canReadIntegrations);
};
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ export interface EndpointAuthz {
canReadFleetAgents: boolean;
/** If the user has permissions to write Fleet Agents */
canWriteFleetAgents: boolean;
/** If the user has permissions to write Integration policies in the Fleet app */
canWriteIntegrationPolicies: boolean;
/** If the user has permissions to access Endpoint management (includes check to ensure they also have access to fleet) */
canAccessEndpointManagement: boolean;
/** If the user has permissions to access Actions Log management and also has a platinum license (used for endpoint details flyout) */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,11 @@ const PolicyEmptyState = React.memo<{
policyEntryPoint?: boolean;
}>(({ loading, onActionClick, actionDisabled, policyEntryPoint = false }) => {
const docLinks = useKibana().services.docLinks;
const { canAccessFleet, loading: authzLoading } = useUserPrivileges().endpointPrivileges;
const {
canAccessFleet,
canWriteIntegrationPolicies,
loading: authzLoading,
} = useUserPrivileges().endpointPrivileges;

return (
<div data-test-subj="emptyPolicyTable">
Expand Down Expand Up @@ -134,7 +138,7 @@ const PolicyEmptyState = React.memo<{

{authzLoading && <EuiSkeletonText lines={1} />}

{!authzLoading && canAccessFleet && (
{!authzLoading && canAccessFleet && canWriteIntegrationPolicies && (
<>
<EuiSpacer size="s" />
<EuiFlexGroup>
Expand All @@ -156,7 +160,9 @@ const PolicyEmptyState = React.memo<{
</>
)}

{!authzLoading && !canAccessFleet && <MissingFleetAccessInfo />}
{!authzLoading && !(canAccessFleet && canWriteIntegrationPolicies) && (
<MissingFleetAccessInfo />
)}
</EuiFlexItem>

<EuiFlexItem grow={2}>
Expand Down
Loading
Loading