Skip to content

Commit

Permalink
show hipaa check status on the compliance dashboard (#4227)
Browse files Browse the repository at this point in the history
  • Loading branch information
ianedwards authored Feb 2, 2024
1 parent 05acfee commit 8d57c45
Show file tree
Hide file tree
Showing 14 changed files with 200 additions and 85 deletions.
18 changes: 17 additions & 1 deletion api/server/handlers/cluster/compliance_checks.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ func NewListComplianceChecksHandler(

// ListComplianceChecksRequest is the expected format for a request to /compliance/checks
type ListComplianceChecksRequest struct {
Vendor compliance.Vendor `schema:"vendor"`
Vendor compliance.Vendor `schema:"vendor"`
Profile compliance.Profile `schema:"profile"`
}

// ListComplianceChecksResponse is the expected format for a response from /compliance/checks
Expand Down Expand Up @@ -69,10 +70,25 @@ func (c *ListComplianceChecksHandler) ServeHTTP(w http.ResponseWriter, r *http.R
}
}

var profile porterv1.EnumComplianceProfile
if request.Profile != "" {
switch request.Profile {
case compliance.Profile_SOC2:
profile = porterv1.EnumComplianceProfile_ENUM_COMPLIANCE_PROFILE_SOC2
case compliance.Profile_HIPAA:
profile = porterv1.EnumComplianceProfile_ENUM_COMPLIANCE_PROFILE_HIPAA
default:
err := telemetry.Error(ctx, span, nil, "invalid profile")
c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusBadRequest))
return
}
}

req := connect.NewRequest(&porterv1.ContractComplianceChecksRequest{
ProjectId: int64(project.ID),
ClusterId: int64(cluster.ID),
Vendor: vendor,
Profile: profile,
})

ccpResp, err := c.Config().ClusterControlPlaneClient.ContractComplianceChecks(ctx, req)
Expand Down
14 changes: 7 additions & 7 deletions dashboard/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion dashboard/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@
"@babel/preset-typescript": "^7.15.0",
"@ianvs/prettier-plugin-sort-imports": "^4.1.1",
"@pmmmwh/react-refresh-webpack-plugin": "^0.4.3",
"@porter-dev/api-contracts": "^0.2.84",
"@porter-dev/api-contracts": "^0.2.97",
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.3.2",
"@testing-library/user-event": "^7.1.2",
Expand Down
40 changes: 26 additions & 14 deletions dashboard/src/main/home/compliance-dashboard/ActionBanner.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React, { useMemo, type Dispatch, type SetStateAction } from "react";
import { useHistory } from "react-router";
import { match } from "ts-pattern";

import Banner from "components/porter/Banner";
import Image from "components/porter/Image";
Expand All @@ -20,10 +21,11 @@ export const ActionBanner: React.FC<ActionBannerProps> = ({
}) => {
const history = useHistory();
const {
profile,
updateInProgress,
latestContractDB,
latestContractProto,
updateContractWithSOC2,
updateContractWithProfile,
} = useCompliance();

const provisioningStatus = useMemo(() => {
Expand Down Expand Up @@ -60,21 +62,30 @@ export const ActionBanner: React.FC<ActionBannerProps> = ({
return provisioningStatus.state === "pending" || updateInProgress;
}, [provisioningStatus.state, updateInProgress]);

const complianceEnabled = match(profile)
.with("soc2", () => latestContractProto?.complianceProfiles?.soc2)
.with("hipaa", () => latestContractProto?.complianceProfiles?.hipaa)
.exhaustive();

// check if compliance has not been enable or if not all checks have passed
const actionRequredWithoutProvisioningError = useMemo(() => {
return (
provisioningStatus.state === "compliance_error" ||
!latestContractProto?.cluster?.isSoc2Compliant
provisioningStatus.state === "compliance_error" || !complianceEnabled
);
}, [provisioningStatus.state, latestContractProto?.toJsonString()]);
}, [
provisioningStatus.state,
latestContractProto?.toJsonString(),
complianceEnabled,
]);

// check if provisioning error is due to compliance update
const provisioningErrorWithComplianceEnabled = useMemo(() => {
return (
provisioningStatus.state === "failed" &&
latestContractProto?.cluster?.isSoc2Compliant
);
}, [provisioningStatus.state, latestContractProto?.toJsonString()]);
return provisioningStatus.state === "failed" && complianceEnabled;
}, [
provisioningStatus.state,
latestContractProto?.toJsonString(),
complianceEnabled,
]);

if (isInfraPending) {
return (
Expand All @@ -83,8 +94,8 @@ export const ActionBanner: React.FC<ActionBannerProps> = ({
<Image src={loading_img} style={{ height: "16px", width: "16px" }} />
}
>
SOC 2 infrastructure controls are being enabled. Note: This may take up
to 30 minutes.
Infrastructure controls are being enabled. Note: This may take up to 30
minutes.
</Banner>
);
}
Expand All @@ -101,7 +112,7 @@ export const ActionBanner: React.FC<ActionBannerProps> = ({
cursor: "pointer",
}}
onClick={() => {
void updateContractWithSOC2();
void updateContractWithProfile();
}}
>
Re-run infrastructure controls
Expand All @@ -116,7 +127,8 @@ export const ActionBanner: React.FC<ActionBannerProps> = ({
setShowCostConsentModal(true);
}}
>
Enable SOC 2 infrastructure controls
Enable {profile === "soc2" ? "SOC2" : "HIPAA"} infrastructure
controls
</Text>
)}
</Banner>
Expand Down Expand Up @@ -146,7 +158,7 @@ export const ActionBanner: React.FC<ActionBannerProps> = ({
cursor: "pointer",
}}
onClick={() => {
void updateContractWithSOC2();
void updateContractWithProfile();
}}
>
<Image src={refresh} size={12} style={{ marginBottom: "-2px" }} />
Expand Down
39 changes: 33 additions & 6 deletions dashboard/src/main/home/compliance-dashboard/ComplianceContext.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
import React, { createContext, useContext, useMemo, useState } from "react";
import { Contract, EKS, EKSLogging } from "@porter-dev/api-contracts";
import React, {
createContext,
useContext,
useMemo,
useState,
type Dispatch,
type SetStateAction,
} from "react";
import {
ComplianceProfile,
Contract,
EKS,
EKSLogging,
} from "@porter-dev/api-contracts";
import { useQuery, useQueryClient } from "@tanstack/react-query";
import { match } from "ts-pattern";
import { z } from "zod";
Expand All @@ -15,6 +27,8 @@ import {
type VendorCheck,
} from "./types";

type ComplianceProfileType = "soc2" | "hipaa";

type ProjectComplianceContextType = {
projectId: number;
clusterId: number;
Expand All @@ -25,7 +39,9 @@ type ProjectComplianceContextType = {
checksLoading: boolean;
contractLoading: boolean;
updateInProgress: boolean;
updateContractWithSOC2: () => Promise<void>;
profile: ComplianceProfileType;
setProfile: Dispatch<SetStateAction<ComplianceProfileType>>;
updateContractWithProfile: () => Promise<void>;
};

const ProjectComplianceContext =
Expand All @@ -52,6 +68,7 @@ export const ProjectComplianceProvider: React.FC<
> = ({ projectId, clusterId, children }) => {
const queryClient = useQueryClient();
const [updateInProgress, setUpdateInProgress] = useState(false);
const [profile, setProfile] = useState<ComplianceProfileType>("soc2");

const { data: baseContract, isLoading: contractLoading } = useQuery(
[projectId, clusterId, "getContracts"],
Expand Down Expand Up @@ -80,13 +97,14 @@ export const ProjectComplianceProvider: React.FC<
projectId,
clusterId,
condition: baseContract?.condition ?? "",
profile,
name: "getComplianceChecks",
},
],
async () => {
const res = await api.getComplianceChecks(
"<token>",
{ vendor: "vanta" },
{ vendor: "vanta", profile },
{ projectId, clusterId }
);

Expand Down Expand Up @@ -114,7 +132,7 @@ export const ProjectComplianceProvider: React.FC<
});
}, [baseContract?.base64_contract]);

const updateContractWithSOC2 = async (): Promise<void> => {
const updateContractWithProfile = async (): Promise<void> => {
try {
setUpdateInProgress(true);

Expand All @@ -141,13 +159,20 @@ export const ProjectComplianceProvider: React.FC<
}))
.otherwise((kind) => kind);

const complianceProfiles = new ComplianceProfile({
...latestContract.complianceProfiles,
...(profile === "soc2" && { soc2: true }),
...(profile === "hipaa" && { hipaa: true }),
});

const updatedContract = new Contract({
...latestContract,
cluster: {
...latestContract.cluster,
kindValues: updatedKindValues,
isSoc2Compliant: true,
},
complianceProfiles,
});

await api.createContract("<token>", updatedContract, {
Expand Down Expand Up @@ -176,7 +201,9 @@ export const ProjectComplianceProvider: React.FC<
checksLoading,
contractLoading,
updateInProgress,
updateContractWithSOC2,
profile,
setProfile,
updateContractWithProfile,
}}
>
{children}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,16 @@ import React, { useContext, useState } from "react";
import styled from "styled-components";

import ClusterProvisioningPlaceholder from "components/ClusterProvisioningPlaceholder";
import Container from "components/porter/Container";
import Image from "components/porter/Image";
import Spacer from "components/porter/Spacer";
import Text from "components/porter/Text";
import DashboardHeader from "main/home/cluster-dashboard/DashboardHeader";

import { Context } from "shared/Context";
import compliance from "assets/compliance.svg";
import linkExternal from "assets/link-external.svg";
import vanta from "assets/vanta.svg";

import { ActionBanner } from "./ActionBanner";
import { ProjectComplianceProvider } from "./ComplianceContext";
import { ConfigSelectors } from "./ConfigSelectors";
import { ProfileHeader } from "./ProfileHeader";
import { SOC2CostConsent } from "./SOC2CostConsent";
import { VendorChecksList } from "./VendorChecksList";

Expand Down Expand Up @@ -46,29 +42,8 @@ const ComplianceDashboard: React.FC = () => {
<>
<ConfigSelectors />
<Spacer y={1} />
<Container row>
<Image src={vanta} size={25} />
<Spacer inline x={1} />
<Text
size={21}
additionalStyles=":hover { text-decoration: underline } cursor: pointer;"
onClick={() => {
window.open(
"https://app.vanta.com/tests?framework=soc2&service=aws&taskType=TEST",
"_blank"
);
}}
>
AWS SOC 2 Controls (Vanta)
<Spacer inline x={0.5} />
<Image
src={linkExternal}
size={16}
additionalStyles="margin-bottom: -2px"
/>
</Text>
</Container>

<ProfileHeader />
<Spacer y={1} />

<ActionBanner setShowCostConsentModal={setShowCostConsentModal} />
Expand Down
20 changes: 14 additions & 6 deletions dashboard/src/main/home/compliance-dashboard/ConfigSelectors.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,30 @@ import provider from "assets/provider.svg";
import typeSvg from "assets/type.svg";
import vanta from "assets/vanta.svg";

import { useCompliance } from "./ComplianceContext";

export const ConfigSelectors: React.FC = () => {
// to be made selectable with state living in context
const { profile, setProfile } = useCompliance();
return (
<Container row>
<Select
options={[
{ value: "soc-2", label: "SOC 2" },
{ value: "soc2", label: "SOC 2" },
{
value: "hipaa",
label: "HIPAA (request access)",
disabled: true,
label: "HIPAA",
},
]}
width="200px"
value={"soc-2"}
setValue={() => {}}
value={profile}
setValue={(value) => {
if (value === "soc2") {
setProfile("soc2");
return;
}

setProfile("hipaa");
}}
prefix={
<Container row>
<Image src={framework} size={15} opacity={0.6} />
Expand Down
Loading

0 comments on commit 8d57c45

Please sign in to comment.