diff --git a/src/Components/CreateImageWizard/steps/Oscap/components/OscapProfileInformation.tsx b/src/Components/CreateImageWizard/steps/Oscap/components/OscapProfileInformation.tsx index 2b3152e8..d8aa0102 100644 --- a/src/Components/CreateImageWizard/steps/Oscap/components/OscapProfileInformation.tsx +++ b/src/Components/CreateImageWizard/steps/Oscap/components/OscapProfileInformation.tsx @@ -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 = ({ + + FIPS mode + + + + + {fips.enabled ? 'Enabled' : 'Disabled'} + + + )} diff --git a/src/Components/CreateImageWizard/steps/Oscap/components/PolicySelector.tsx b/src/Components/CreateImageWizard/steps/Oscap/components/PolicySelector.tsx index aea45c1c..5e2cf9c8 100644 --- a/src/Components/CreateImageWizard/steps/Oscap/components/PolicySelector.tsx +++ b/src/Components/CreateImageWizard/steps/Oscap/components/PolicySelector.tsx @@ -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)); }); } }; diff --git a/src/Components/CreateImageWizard/steps/Oscap/components/ProfileSelector.tsx b/src/Components/CreateImageWizard/steps/Oscap/components/ProfileSelector.tsx index c737986d..a35b4caa 100644 --- a/src/Components/CreateImageWizard/steps/Oscap/components/ProfileSelector.tsx +++ b/src/Components/CreateImageWizard/steps/Oscap/components/ProfileSelector.tsx @@ -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)); }); } }; diff --git a/src/Components/CreateImageWizard/steps/Oscap/index.tsx b/src/Components/CreateImageWizard/steps/Oscap/index.tsx index ec0d6de6..52b626fd 100644 --- a/src/Components/CreateImageWizard/steps/Oscap/index.tsx +++ b/src/Components/CreateImageWizard/steps/Oscap/index.tsx @@ -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. + + handleFipsToggle(checked)} + description='Enable FIPS 140-2 compliant cryptographic algorithms. This setting will be applied at build time and will persist on boot.' + /> + {complianceEnabled && ( ({ + 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); + }); + }); +}); diff --git a/src/test/fixtures/oscap.ts b/src/test/fixtures/oscap.ts index bcd645eb..6f807460 100644 --- a/src/test/fixtures/oscap.ts +++ b/src/test/fixtures/oscap.ts @@ -113,6 +113,9 @@ export const oscapCustomizations = ( masked: ['nfs-server', 'rpcbind', 'autofs', 'nftables'], enabled: ['crond', 'firewalld', 'systemd-journald', 'rsyslog', 'auditd'], }, + fips: { + enabled: true, + }, }; };