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,
+ },
};
};