Wizard: Add FIPS mode support for OpenSCAP and compliance profiles (HMS-8919)
Automatically enable FIPS mode when: User selects OpenSCAP profile with FIPS enabled (e.g., DISA STIG) User selects compliance profile with FIPS enabled and not customized off - Add FIPS checkbox in openscap step - Display FIPS status in review step - Add unit tests to FIPS checkbox feature This ensures security compliance for profiles that require FIPS mode without manual user intervention.
This commit is contained in:
parent
3461c908fb
commit
d66f54a847
6 changed files with 195 additions and 0 deletions
|
|
@ -17,6 +17,7 @@ import {
|
|||
selectCompliancePolicyID,
|
||||
selectComplianceProfileID,
|
||||
selectDistribution,
|
||||
selectFips,
|
||||
} from '../../../../../store/wizardSlice';
|
||||
|
||||
type OscapProfileInformationOptionPropType = {
|
||||
|
|
@ -30,6 +31,7 @@ export const OscapProfileInformation = ({
|
|||
const release = useAppSelector(selectDistribution);
|
||||
const compliancePolicyID = useAppSelector(selectCompliancePolicyID);
|
||||
const complianceProfileID = useAppSelector(selectComplianceProfileID);
|
||||
const fips = useAppSelector(selectFips);
|
||||
|
||||
const {
|
||||
data: oscapProfileInfo,
|
||||
|
|
@ -159,6 +161,19 @@ export const OscapProfileInformation = ({
|
|||
</CodeBlockCode>
|
||||
</CodeBlock>
|
||||
</Content>
|
||||
<Content
|
||||
component={ContentVariants.dt}
|
||||
className='pf-v5-u-min-width'
|
||||
>
|
||||
FIPS mode
|
||||
</Content>
|
||||
<Content component={ContentVariants.dd}>
|
||||
<CodeBlock>
|
||||
<CodeBlockCode>
|
||||
{fips.enabled ? 'Enabled' : 'Disabled'}
|
||||
</CodeBlockCode>
|
||||
</CodeBlock>
|
||||
</Content>
|
||||
</Content>
|
||||
</>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import {
|
|||
import {
|
||||
changeCompliance,
|
||||
changeFileSystemConfigurationType,
|
||||
changeFips,
|
||||
clearKernelAppend,
|
||||
selectCompliancePolicyID,
|
||||
selectCompliancePolicyTitle,
|
||||
|
|
@ -143,6 +144,7 @@ const PolicySelector = () => {
|
|||
dispatch(changeFileSystemConfigurationType('automatic'));
|
||||
handleServices(undefined);
|
||||
dispatch(clearKernelAppend());
|
||||
dispatch(changeFips(false));
|
||||
};
|
||||
|
||||
const applyChanges = (selection: ComplianceSelectOptionValueType) => {
|
||||
|
|
@ -177,6 +179,7 @@ const PolicySelector = () => {
|
|||
policyTitle: selection.title,
|
||||
}),
|
||||
);
|
||||
dispatch(changeFips(response?.fips?.enabled || false));
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ import {
|
|||
import {
|
||||
changeCompliance,
|
||||
changeFileSystemConfigurationType,
|
||||
changeFips,
|
||||
clearKernelAppend,
|
||||
selectComplianceProfileID,
|
||||
selectComplianceType,
|
||||
|
|
@ -181,6 +182,7 @@ const ProfileSelector = () => {
|
|||
dispatch(changeFileSystemConfigurationType('automatic'));
|
||||
handleServices(undefined);
|
||||
dispatch(clearKernelAppend());
|
||||
dispatch(changeFips(false));
|
||||
setInputValue('');
|
||||
setFilterValue('');
|
||||
};
|
||||
|
|
@ -261,6 +263,7 @@ const ProfileSelector = () => {
|
|||
policyTitle: undefined,
|
||||
}),
|
||||
);
|
||||
dispatch(changeFips(response?.fips?.enabled || false));
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -3,8 +3,10 @@ import React, { useEffect } from 'react';
|
|||
import {
|
||||
Alert,
|
||||
AlertActionLink,
|
||||
Checkbox,
|
||||
Content,
|
||||
Form,
|
||||
FormGroup,
|
||||
Title,
|
||||
ToggleGroup,
|
||||
ToggleGroupItem,
|
||||
|
|
@ -29,6 +31,7 @@ import {
|
|||
changeDisabledServices,
|
||||
changeEnabledServices,
|
||||
changeFileSystemConfigurationType,
|
||||
changeFips,
|
||||
changeMaskedServices,
|
||||
clearKernelAppend,
|
||||
ComplianceType,
|
||||
|
|
@ -36,6 +39,7 @@ import {
|
|||
selectComplianceProfileID,
|
||||
selectComplianceType,
|
||||
selectDistribution,
|
||||
selectFips,
|
||||
} from '../../../../store/wizardSlice';
|
||||
import { useFlag } from '../../../../Utilities/useGetEnvironment';
|
||||
import { useOnPremOpenSCAPAvailable } from '../../../../Utilities/useOnPremOpenSCAP';
|
||||
|
|
@ -46,6 +50,7 @@ const OscapContent = () => {
|
|||
const complianceEnabled = useFlag('image-builder.compliance.enabled');
|
||||
const complianceType = useAppSelector(selectComplianceType);
|
||||
const profileID = useAppSelector(selectComplianceProfileID);
|
||||
const fips = useAppSelector(selectFips);
|
||||
const prefetchOscapProfile = useBackendPrefetch('getOscapProfiles', {});
|
||||
const release = removeBetaFromRelease(useAppSelector(selectDistribution));
|
||||
const majorVersion = release.split('-')[1];
|
||||
|
|
@ -66,6 +71,10 @@ const OscapContent = () => {
|
|||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
const handleFipsToggle = (checked: boolean) => {
|
||||
dispatch(changeFips(checked));
|
||||
};
|
||||
|
||||
const handleTypeChange = (complianceType: string) => {
|
||||
dispatch(changeComplianceType(complianceType as ComplianceType));
|
||||
|
||||
|
|
@ -87,6 +96,7 @@ const OscapContent = () => {
|
|||
dispatch(changeMaskedServices([]));
|
||||
dispatch(changeDisabledServices([]));
|
||||
dispatch(clearKernelAppend());
|
||||
dispatch(changeFips(false));
|
||||
};
|
||||
|
||||
if (!process.env.IS_ON_PREMISE) {
|
||||
|
|
@ -117,6 +127,15 @@ const OscapContent = () => {
|
|||
versions. This will automatically help monitor the adherence of your
|
||||
registered RHEL systems to a selected policy or profile.
|
||||
</Content>
|
||||
<FormGroup>
|
||||
<Checkbox
|
||||
id='fips-enabled-checkbox'
|
||||
label='Enable FIPS mode'
|
||||
isChecked={fips.enabled}
|
||||
onChange={(_event, checked) => handleFipsToggle(checked)}
|
||||
description='Enable FIPS 140-2 compliant cryptographic algorithms. This setting will be applied at build time and will persist on boot.'
|
||||
/>
|
||||
</FormGroup>
|
||||
{complianceEnabled && (
|
||||
<ToggleGroup aria-label='Default with single selectable'>
|
||||
<ToggleGroupItem
|
||||
|
|
|
|||
|
|
@ -0,0 +1,152 @@
|
|||
import { screen, waitFor } from '@testing-library/react';
|
||||
import { userEvent } from '@testing-library/user-event';
|
||||
|
||||
import { CREATE_BLUEPRINT } from '../../../../../constants';
|
||||
import { CreateBlueprintRequest } from '../../../../../store/imageBuilderApi';
|
||||
import { baseCreateBlueprintRequest } from '../../../../fixtures/editMode';
|
||||
import {
|
||||
clickNext,
|
||||
clickRegisterLater,
|
||||
enterBlueprintName,
|
||||
interceptBlueprintRequest,
|
||||
openAndDismissSaveAndBuildModal,
|
||||
renderCreateMode,
|
||||
selectRhel9,
|
||||
} from '../../wizardTestUtils';
|
||||
|
||||
vi.mock('@unleash/proxy-client-react', () => ({
|
||||
useUnleashContext: () => vi.fn(),
|
||||
useFlag: vi.fn((flag) => {
|
||||
switch (flag) {
|
||||
case 'image-builder.compliance.enabled':
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}),
|
||||
}));
|
||||
|
||||
const goToComplianceStep = async () => {
|
||||
const user = userEvent.setup();
|
||||
await selectRhel9();
|
||||
const guestImageCheckBox = await screen.findByRole('checkbox', {
|
||||
name: /virtualization guest image checkbox/i,
|
||||
});
|
||||
await waitFor(() => user.click(guestImageCheckBox));
|
||||
await clickNext(); // Registration
|
||||
await clickRegisterLater();
|
||||
await clickNext(); // Compliance
|
||||
await screen.findByRole('heading', { name: /Compliance/ });
|
||||
const button = await screen.findByRole('button', {
|
||||
name: /Compliance policies/,
|
||||
});
|
||||
await waitFor(() => user.click(button));
|
||||
await screen.findByText('None');
|
||||
};
|
||||
|
||||
const selectStigPolicy = async () => {
|
||||
const user = userEvent.setup();
|
||||
|
||||
const policyMenu = await screen.findByText('None');
|
||||
await waitFor(() => user.click(policyMenu));
|
||||
|
||||
const stigPolicy = await screen.findByRole('option', {
|
||||
name: /stig gui/i,
|
||||
});
|
||||
await waitFor(() => user.click(stigPolicy));
|
||||
|
||||
const profile_id = await screen.findByTestId('oscap-profile-info-ref-id');
|
||||
expect(profile_id).toHaveTextContent('content_profile_stig_gui');
|
||||
};
|
||||
|
||||
const getFipsCheckbox = async () => {
|
||||
return await screen.findByRole('checkbox', {
|
||||
name: /enable fips mode/i,
|
||||
});
|
||||
};
|
||||
|
||||
const toggleFipsCheckbox = async () => {
|
||||
const user = userEvent.setup();
|
||||
const fipsCheckbox = await getFipsCheckbox();
|
||||
await waitFor(() => user.click(fipsCheckbox));
|
||||
return fipsCheckbox;
|
||||
};
|
||||
|
||||
const goToReviewStep = async () => {
|
||||
await clickNext(); // File system configuration
|
||||
await clickNext(); // Snapshot repositories
|
||||
await clickNext(); // Custom repositories
|
||||
await clickNext(); // Additional packages
|
||||
await clickNext(); // Users
|
||||
await clickNext(); // Timezone
|
||||
await clickNext(); // Locale
|
||||
await clickNext(); // Hostname
|
||||
await clickNext(); // Kernel
|
||||
await clickNext(); // Firewall
|
||||
await clickNext(); // Services
|
||||
await clickNext(); // FirstBoot
|
||||
await clickNext(); // Details
|
||||
await enterBlueprintName('FIPS test');
|
||||
await clickNext(); // Review
|
||||
};
|
||||
|
||||
describe('FIPS Mode Tests', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
test('FIPS checkbox is present and functional', async () => {
|
||||
await renderCreateMode();
|
||||
await goToComplianceStep();
|
||||
|
||||
const fipsCheckbox = await getFipsCheckbox();
|
||||
|
||||
expect(fipsCheckbox).toBeInTheDocument();
|
||||
expect(fipsCheckbox).not.toBeChecked();
|
||||
|
||||
await toggleFipsCheckbox();
|
||||
expect(fipsCheckbox).toBeChecked();
|
||||
await screen.findByText(
|
||||
/enable fips 140-2 compliant cryptographic algorithms/i,
|
||||
);
|
||||
});
|
||||
|
||||
test('FIPS checkbox is automatically enabled when selecting STIG GUI profile', async () => {
|
||||
await renderCreateMode();
|
||||
await goToComplianceStep();
|
||||
|
||||
const fipsCheckbox = await getFipsCheckbox();
|
||||
expect(fipsCheckbox).not.toBeChecked();
|
||||
|
||||
await selectStigPolicy();
|
||||
await waitFor(() => {
|
||||
expect(fipsCheckbox).toBeChecked();
|
||||
});
|
||||
});
|
||||
|
||||
test('FIPS setting included in blueprint when manually enabled', async () => {
|
||||
await renderCreateMode();
|
||||
await goToComplianceStep();
|
||||
|
||||
await toggleFipsCheckbox();
|
||||
await goToReviewStep();
|
||||
await openAndDismissSaveAndBuildModal();
|
||||
|
||||
const receivedRequest = await interceptBlueprintRequest(CREATE_BLUEPRINT);
|
||||
|
||||
const expectedRequest: CreateBlueprintRequest = {
|
||||
...baseCreateBlueprintRequest,
|
||||
name: 'FIPS test',
|
||||
customizations: {
|
||||
...baseCreateBlueprintRequest.customizations,
|
||||
fips: {
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
await waitFor(() => {
|
||||
expect(receivedRequest).toEqual(expectedRequest);
|
||||
});
|
||||
});
|
||||
});
|
||||
3
src/test/fixtures/oscap.ts
vendored
3
src/test/fixtures/oscap.ts
vendored
|
|
@ -113,6 +113,9 @@ export const oscapCustomizations = (
|
|||
masked: ['nfs-server', 'rpcbind', 'autofs', 'nftables'],
|
||||
enabled: ['crond', 'firewalld', 'systemd-journald', 'rsyslog', 'auditd'],
|
||||
},
|
||||
fips: {
|
||||
enabled: true,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue