Wizard: add compliance step
The Insights compliance support reuses most of the existing OpenSCAP step. Depending on the state of the feature flag, it will show radio buttons allowing users to switch between regular openscap and Insights compliance.
This commit is contained in:
parent
571fe18861
commit
e43357bf55
3 changed files with 326 additions and 94 deletions
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useState } from 'react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
|
||||
import {
|
||||
Alert,
|
||||
|
|
@ -18,6 +18,7 @@ import { v4 as uuidv4 } from 'uuid';
|
|||
|
||||
import OscapProfileInformation from './OscapProfileInformation';
|
||||
|
||||
import { usePoliciesQuery, PolicyRead } from '../../../../store/complianceApi';
|
||||
import { useAppDispatch, useAppSelector } from '../../../../store/hooks';
|
||||
import {
|
||||
DistributionProfileItem,
|
||||
|
|
@ -29,9 +30,11 @@ import {
|
|||
Services,
|
||||
} from '../../../../store/imageBuilderApi';
|
||||
import {
|
||||
changeOscapProfile,
|
||||
changeCompliance,
|
||||
selectDistribution,
|
||||
selectProfile,
|
||||
selectComplianceProfileID,
|
||||
selectCompliancePolicyID,
|
||||
selectCompliancePolicyTitle,
|
||||
addPackage,
|
||||
addPartition,
|
||||
changeFileSystemConfigurationType,
|
||||
|
|
@ -42,47 +45,125 @@ import {
|
|||
changeMaskedServices,
|
||||
changeDisabledServices,
|
||||
changeKernelAppend,
|
||||
selectComplianceType,
|
||||
} from '../../../../store/wizardSlice';
|
||||
import { useHasSpecificTargetOnly } from '../../utilities/hasSpecificTargetOnly';
|
||||
import { parseSizeUnit } from '../../utilities/parseSizeUnit';
|
||||
import { Partition, Units } from '../FileSystem/FileSystemConfiguration';
|
||||
|
||||
const OpenSCAPFGLabel = () => {
|
||||
return (
|
||||
<>
|
||||
OpenSCAP profile
|
||||
<Popover
|
||||
maxWidth="30rem"
|
||||
bodyContent={
|
||||
<TextContent>
|
||||
<Text>
|
||||
To run a manual compliance scan in OpenSCAP, download this image.
|
||||
</Text>
|
||||
</TextContent>
|
||||
}
|
||||
>
|
||||
<Button
|
||||
variant="plain"
|
||||
aria-label="About OpenSCAP"
|
||||
isInline
|
||||
className="pf-u-pl-sm pf-u-pt-0 pf-u-pb-0 pf-u-pr-0"
|
||||
>
|
||||
<HelpIcon />
|
||||
</Button>
|
||||
</Popover>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const ProfileSelector = () => {
|
||||
const oscapProfile = useAppSelector(selectProfile);
|
||||
const policyID = useAppSelector(selectCompliancePolicyID);
|
||||
const policyTitle = useAppSelector(selectCompliancePolicyTitle);
|
||||
const profileID = useAppSelector(selectComplianceProfileID);
|
||||
const release = useAppSelector(selectDistribution);
|
||||
const majorVersion = release.split('-')[1];
|
||||
const hasWslTargetOnly = useHasSpecificTargetOnly('wsl');
|
||||
const dispatch = useAppDispatch();
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const complianceType = useAppSelector(selectComplianceType);
|
||||
|
||||
const {
|
||||
data: profiles,
|
||||
isFetching,
|
||||
isSuccess,
|
||||
isError,
|
||||
refetch,
|
||||
} = useGetOscapProfilesQuery({
|
||||
distribution: release,
|
||||
});
|
||||
} = useGetOscapProfilesQuery(
|
||||
{
|
||||
distribution: release,
|
||||
},
|
||||
{
|
||||
skip: complianceType === 'compliance',
|
||||
}
|
||||
);
|
||||
|
||||
const {
|
||||
data: policies,
|
||||
isFetching: isFetchingPolicies,
|
||||
isSuccess: isSuccessPolicies,
|
||||
} = usePoliciesQuery(
|
||||
{
|
||||
filter: `os_major_version=${majorVersion}`,
|
||||
},
|
||||
{
|
||||
skip: complianceType === 'openscap',
|
||||
}
|
||||
);
|
||||
|
||||
const { data: currentProfileData } = useGetOscapCustomizationsQuery(
|
||||
{
|
||||
distribution: release,
|
||||
// @ts-ignore if openScapProfile is undefined the query is going to get skipped
|
||||
profile: oscapProfile,
|
||||
profile: profileID,
|
||||
},
|
||||
{ skip: !oscapProfile }
|
||||
{ skip: !profileID }
|
||||
);
|
||||
|
||||
const [trigger] = useLazyGetOscapCustomizationsQuery();
|
||||
|
||||
useEffect(() => {
|
||||
if (!policies || policies.data === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (policyID && !policyTitle) {
|
||||
for (const p of policies.data) {
|
||||
const pol = p as PolicyRead;
|
||||
if (pol.id === policyID) {
|
||||
dispatch(
|
||||
changeCompliance({
|
||||
policyID: pol.id,
|
||||
profileID: pol.ref_id,
|
||||
policyTitle: pol.title,
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [isSuccessPolicies]);
|
||||
|
||||
const handleToggle = () => {
|
||||
if (!isOpen) {
|
||||
if (!isOpen && complianceType === 'openscap') {
|
||||
refetch();
|
||||
}
|
||||
setIsOpen(!isOpen);
|
||||
};
|
||||
|
||||
const handleClear = () => {
|
||||
dispatch(changeOscapProfile(undefined));
|
||||
dispatch(
|
||||
changeCompliance({
|
||||
profileID: undefined,
|
||||
policyID: undefined,
|
||||
policyTitle: undefined,
|
||||
})
|
||||
);
|
||||
clearOscapPackages(currentProfileData?.packages || []);
|
||||
dispatch(changeFileSystemConfigurationType('automatic'));
|
||||
handleServices(undefined);
|
||||
|
|
@ -142,9 +223,9 @@ const ProfileSelector = () => {
|
|||
|
||||
const handleSelect = (
|
||||
_event: React.MouseEvent<Element, MouseEvent>,
|
||||
selection: OScapSelectOptionValueType
|
||||
selection: OScapSelectOptionValueType | ComplianceSelectOptionValueType
|
||||
) => {
|
||||
if (selection.id === undefined) {
|
||||
if (selection.profileID === undefined) {
|
||||
// handle user has selected 'None' case
|
||||
handleClear();
|
||||
} else {
|
||||
|
|
@ -152,7 +233,7 @@ const ProfileSelector = () => {
|
|||
trigger(
|
||||
{
|
||||
distribution: release,
|
||||
profile: selection.id,
|
||||
profile: selection.profileID as DistributionProfileItem,
|
||||
},
|
||||
true // preferCacheValue
|
||||
)
|
||||
|
|
@ -164,7 +245,24 @@ const ProfileSelector = () => {
|
|||
handlePackages(oldOscapPackages, newOscapPackages);
|
||||
handleServices(response.services);
|
||||
dispatch(changeKernelAppend(response.kernel?.append || ''));
|
||||
dispatch(changeOscapProfile(selection.id));
|
||||
if (complianceType === 'openscap') {
|
||||
dispatch(
|
||||
changeCompliance({
|
||||
profileID: selection.profileID,
|
||||
policyID: undefined,
|
||||
policyTitle: undefined,
|
||||
})
|
||||
);
|
||||
} else {
|
||||
const compl = selection as ComplianceSelectOptionValueType;
|
||||
dispatch(
|
||||
changeCompliance({
|
||||
profileID: compl.profileID,
|
||||
policyID: compl.policyID,
|
||||
policyTitle: compl.toString(),
|
||||
})
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
setIsOpen(false);
|
||||
|
|
@ -180,67 +278,77 @@ const ProfileSelector = () => {
|
|||
}
|
||||
};
|
||||
|
||||
const complianceOptions = () => {
|
||||
if (!policies || policies.data === undefined) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const res = [<ComplianceNoneOption key="compliance-non-option" />];
|
||||
for (const p of policies.data) {
|
||||
if (p === undefined) {
|
||||
continue;
|
||||
}
|
||||
const pol = p as PolicyRead;
|
||||
res.push(<ComplianceSelectOption key={pol.id} policy={pol} />);
|
||||
}
|
||||
return res;
|
||||
};
|
||||
|
||||
return (
|
||||
<FormGroup
|
||||
isRequired={true}
|
||||
data-testid="profiles-form-group"
|
||||
label={
|
||||
<>
|
||||
OpenSCAP profile
|
||||
<Popover
|
||||
maxWidth="30rem"
|
||||
bodyContent={
|
||||
<TextContent>
|
||||
<Text>
|
||||
To run a manual compliance scan in OpenSCAP, download this
|
||||
image.
|
||||
</Text>
|
||||
</TextContent>
|
||||
}
|
||||
>
|
||||
<Button
|
||||
variant="plain"
|
||||
aria-label="About OpenSCAP"
|
||||
isInline
|
||||
className="pf-u-pl-sm pf-u-pt-0 pf-u-pb-0 pf-u-pr-0"
|
||||
>
|
||||
<HelpIcon />
|
||||
</Button>
|
||||
</Popover>
|
||||
</>
|
||||
}
|
||||
label={complianceType === 'openscap' ? <OpenSCAPFGLabel /> : <>Policy</>}
|
||||
>
|
||||
<Select
|
||||
loadingVariant={isFetching ? 'spinner' : undefined}
|
||||
ouiaId="profileSelect"
|
||||
variant={SelectVariant.typeahead}
|
||||
onToggle={handleToggle}
|
||||
onSelect={handleSelect}
|
||||
onClear={handleClear}
|
||||
maxHeight="300px"
|
||||
selections={oscapProfile}
|
||||
isOpen={isOpen}
|
||||
placeholderText="Select a profile"
|
||||
typeAheadAriaLabel="Select a profile"
|
||||
isDisabled={!isSuccess || hasWslTargetOnly}
|
||||
onFilter={(_event, value) => {
|
||||
if (profiles) {
|
||||
return [<OScapNoneOption key="oscap-none-option" />].concat(
|
||||
profiles.map((profile_id, index) => {
|
||||
return (
|
||||
<OScapSelectOption
|
||||
key={index}
|
||||
profile_id={profile_id}
|
||||
filter={value}
|
||||
/>
|
||||
);
|
||||
})
|
||||
);
|
||||
{complianceType === 'openscap' && (
|
||||
<Select
|
||||
loadingVariant={isFetching ? 'spinner' : undefined}
|
||||
ouiaId="profileSelect"
|
||||
variant={SelectVariant.typeahead}
|
||||
onToggle={handleToggle}
|
||||
onSelect={handleSelect}
|
||||
onClear={handleClear}
|
||||
maxHeight="300px"
|
||||
selections={profileID}
|
||||
isOpen={isOpen}
|
||||
placeholderText="Select a profile"
|
||||
typeAheadAriaLabel="Select a profile"
|
||||
isDisabled={!isSuccess || hasWslTargetOnly}
|
||||
onFilter={(_event, value) => {
|
||||
if (profiles) {
|
||||
return [<OScapNoneOption key="oscap-none-option" />].concat(
|
||||
profiles.map((profile_id, index) => {
|
||||
return (
|
||||
<OScapSelectOption
|
||||
key={index}
|
||||
profile_id={profile_id}
|
||||
filter={value}
|
||||
/>
|
||||
);
|
||||
})
|
||||
);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{options()}
|
||||
</Select>
|
||||
)}
|
||||
{complianceType === 'compliance' && (
|
||||
<Select
|
||||
isDisabled={isFetchingPolicies}
|
||||
isOpen={isOpen}
|
||||
onSelect={handleSelect}
|
||||
onToggle={handleToggle}
|
||||
selections={
|
||||
isFetchingPolicies
|
||||
? 'Loading policies'
|
||||
: policyTitle || policyID || 'None'
|
||||
}
|
||||
}}
|
||||
>
|
||||
{options()}
|
||||
</Select>
|
||||
ouiaId="compliancePolicySelect"
|
||||
>
|
||||
{complianceOptions()}
|
||||
</Select>
|
||||
)}
|
||||
{isError && (
|
||||
<Alert
|
||||
title="Error fetching the profiles"
|
||||
|
|
@ -267,7 +375,7 @@ type OScapSelectOptionPropType = {
|
|||
};
|
||||
|
||||
type OScapSelectOptionValueType = {
|
||||
id: DistributionProfileItem;
|
||||
profileID: DistributionProfileItem;
|
||||
toString: () => string;
|
||||
};
|
||||
|
||||
|
|
@ -291,7 +399,7 @@ const OScapSelectOption = ({
|
|||
id: DistributionProfileItem,
|
||||
name?: string
|
||||
): OScapSelectOptionValueType => ({
|
||||
id,
|
||||
profileID: id,
|
||||
toString: () => name || '',
|
||||
});
|
||||
|
||||
|
|
@ -303,9 +411,52 @@ const OScapSelectOption = ({
|
|||
/>
|
||||
);
|
||||
};
|
||||
type ComplianceSelectOptionPropType = {
|
||||
policy: PolicyRead;
|
||||
};
|
||||
|
||||
type ComplianceSelectOptionValueType = {
|
||||
policyID: string;
|
||||
profileID: string;
|
||||
toString: () => string;
|
||||
};
|
||||
|
||||
const ComplianceNoneOption = () => {
|
||||
return (
|
||||
<SelectOption value={{ toString: () => 'None', compareTo: () => false }} />
|
||||
);
|
||||
};
|
||||
|
||||
const ComplianceSelectOption = ({ policy }: ComplianceSelectOptionPropType) => {
|
||||
const selectObj = (
|
||||
policyID: string,
|
||||
profileID: string,
|
||||
title: string
|
||||
): ComplianceSelectOptionValueType => ({
|
||||
policyID,
|
||||
profileID,
|
||||
toString: () => title,
|
||||
});
|
||||
|
||||
const descr = (
|
||||
<>
|
||||
Threshold: {policy.compliance_threshold}
|
||||
<br />
|
||||
Active systems: {policy.total_system_count}
|
||||
</>
|
||||
);
|
||||
|
||||
return (
|
||||
<SelectOption
|
||||
key={policy.id}
|
||||
value={selectObj(policy.id!, policy.ref_id!, policy.title!)}
|
||||
description={descr}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const Oscap = () => {
|
||||
const oscapProfile = useAppSelector(selectProfile);
|
||||
const oscapProfile = useAppSelector(selectComplianceProfileID);
|
||||
const environments = useAppSelector(selectImageTypes);
|
||||
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -1,21 +1,44 @@
|
|||
import React, { useEffect } from 'react';
|
||||
|
||||
import { Button, Form, Text, Title } from '@patternfly/react-core';
|
||||
import {
|
||||
Button,
|
||||
Form,
|
||||
FormGroup,
|
||||
Radio,
|
||||
Text,
|
||||
Title,
|
||||
} from '@patternfly/react-core';
|
||||
import { ExternalLinkAltIcon } from '@patternfly/react-icons';
|
||||
import { useFlag } from '@unleash/proxy-client-react';
|
||||
|
||||
import { Oscap } from './Oscap';
|
||||
|
||||
import { COMPLIANCE_AND_VULN_SCANNING_URL } from '../../../../constants';
|
||||
import {
|
||||
COMPLIANCE_AND_VULN_SCANNING_URL,
|
||||
COMPLIANCE_PROD_URL,
|
||||
COMPLIANCE_STAGE_URL,
|
||||
} from '../../../../constants';
|
||||
import { imageBuilderApi } from '../../../../store/enhancedImageBuilderApi';
|
||||
import { useAppSelector } from '../../../../store/hooks';
|
||||
import { selectDistribution } from '../../../../store/wizardSlice';
|
||||
import { useAppDispatch, useAppSelector } from '../../../../store/hooks';
|
||||
import {
|
||||
ComplianceType,
|
||||
changeComplianceType,
|
||||
selectDistribution,
|
||||
selectComplianceType,
|
||||
} from '../../../../store/wizardSlice';
|
||||
import { useGetEnvironment } from '../../../../Utilities/useGetEnvironment';
|
||||
|
||||
const OscapStep = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
const complianceEnabled = useFlag('image-builder.compliance.enabled');
|
||||
const complianceType = useAppSelector(selectComplianceType);
|
||||
const prefetchOscapProfile = imageBuilderApi.usePrefetch(
|
||||
'getOscapProfiles',
|
||||
{}
|
||||
);
|
||||
const { isProd } = useGetEnvironment();
|
||||
const release = useAppSelector(selectDistribution);
|
||||
|
||||
useEffect(() => {
|
||||
prefetchOscapProfile({ distribution: release });
|
||||
// This useEffect hook should run *only* on mount and therefore has an empty
|
||||
|
|
@ -26,24 +49,78 @@ const OscapStep = () => {
|
|||
return (
|
||||
<Form>
|
||||
<Title headingLevel="h1" size="xl">
|
||||
OpenSCAP profile
|
||||
{complianceEnabled ? 'Compliance' : 'OpenSCAP profile'}
|
||||
</Title>
|
||||
<Text>
|
||||
OpenSCAP enables you to automatically monitor the adherence of your
|
||||
registered RHEL systems to a selected regulatory compliance profile.
|
||||
<br />
|
||||
<Button
|
||||
component="a"
|
||||
target="_blank"
|
||||
variant="link"
|
||||
icon={<ExternalLinkAltIcon />}
|
||||
iconPosition="right"
|
||||
isInline
|
||||
href={COMPLIANCE_AND_VULN_SCANNING_URL}
|
||||
>
|
||||
Documentation
|
||||
</Button>
|
||||
</Text>
|
||||
{complianceEnabled && (
|
||||
<FormGroup>
|
||||
<Radio
|
||||
id="openscap radio openscap type"
|
||||
label="OpenSCAP"
|
||||
name="oscap-radio-openscap"
|
||||
isChecked={complianceType === 'openscap'}
|
||||
onChange={() =>
|
||||
dispatch(changeComplianceType('openscap' as ComplianceType))
|
||||
}
|
||||
/>
|
||||
<Radio
|
||||
id="openscap radio compliance type"
|
||||
label="Insights compliance"
|
||||
name="oscap-radio-compliance"
|
||||
isChecked={complianceType === 'compliance'}
|
||||
onChange={() =>
|
||||
dispatch(changeComplianceType('compliance' as ComplianceType))
|
||||
}
|
||||
/>
|
||||
</FormGroup>
|
||||
)}
|
||||
{(!complianceEnabled || complianceType === 'openscap') && (
|
||||
<Text>
|
||||
OpenSCAP enables you to automatically monitor the adherence of your
|
||||
registered RHEL systems to a selected regulatory compliance profile.
|
||||
<br />
|
||||
<Button
|
||||
component="a"
|
||||
target="_blank"
|
||||
variant="link"
|
||||
icon={<ExternalLinkAltIcon />}
|
||||
iconPosition="right"
|
||||
isInline
|
||||
href={COMPLIANCE_AND_VULN_SCANNING_URL}
|
||||
>
|
||||
Documentation
|
||||
</Button>
|
||||
</Text>
|
||||
)}
|
||||
{complianceType === 'compliance' && (
|
||||
<Text>
|
||||
Insights compliance enables you to monitor the adherence of your
|
||||
registered RHEL systems to a selected compliance policy.
|
||||
<br />
|
||||
<Button
|
||||
component="a"
|
||||
target="_blank"
|
||||
variant="link"
|
||||
icon={<ExternalLinkAltIcon />}
|
||||
iconPosition="right"
|
||||
isInline
|
||||
href={isProd() ? COMPLIANCE_PROD_URL : COMPLIANCE_STAGE_URL}
|
||||
>
|
||||
Define new policies in Insights Compliance
|
||||
</Button>
|
||||
<br />
|
||||
<Button
|
||||
component="a"
|
||||
target="_blank"
|
||||
variant="link"
|
||||
icon={<ExternalLinkAltIcon />}
|
||||
iconPosition="right"
|
||||
isInline
|
||||
href={COMPLIANCE_AND_VULN_SCANNING_URL}
|
||||
>
|
||||
Documentation
|
||||
</Button>
|
||||
</Text>
|
||||
)}
|
||||
<Oscap />
|
||||
</Form>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -24,6 +24,10 @@ export const RELEASE_LIFECYCLE_URL =
|
|||
'https://access.redhat.com/support/policy/updates/errata';
|
||||
export const AZURE_AUTH_URL =
|
||||
'https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-auth-code-flow';
|
||||
export const COMPLIANCE_PROD_URL =
|
||||
'https://console.redhat.com/insights/compliance/scappolicies';
|
||||
export const COMPLIANCE_STAGE_URL =
|
||||
'https://console.stage.redhat.com/insights/compliance/scappolicies';
|
||||
export const ACTIVATION_KEYS_PROD_URL =
|
||||
'https://console.redhat.com/insights/connector/activation-keys';
|
||||
export const ACTIVATION_KEYS_STAGE_URL =
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue