Explorar o código

Add SOC-2 Compliance tab to infrastructure UI (#3995)

Co-authored-by: jusrhee <justin@porter.run>
jose-fully-ported %!s(int64=2) %!d(string=hai) anos
pai
achega
bd298d3c0f

+ 53 - 25
api/types/project.go

@@ -24,77 +24,97 @@ type ProjectList struct {
 	ValidateApplyV2        bool   `json:"validate_apply_v2"`
 }
 
+// Project type for entries in api responses for everything other than `GET /projects`
 type Project struct {
 	ID                     uint    `json:"id"`
 	Name                   string  `json:"name"`
 	Roles                  []*Role `json:"roles"`
-	PreviewEnvsEnabled     bool    `json:"preview_envs_enabled"`
-	RDSDatabasesEnabled    bool    `json:"enable_rds_databases"`
-	ManagedInfraEnabled    bool    `json:"managed_infra_enabled"`
 	APITokensEnabled       bool    `json:"api_tokens_enabled"`
-	StacksEnabled          bool    `json:"stacks_enabled"`
+	AWSACKAuthEnabled      bool    `json:"aws_ack_auth_enabled"`
+	AzureEnabled           bool    `json:"azure_enabled"`
+	BetaFeaturesEnabled    bool    `json:"beta_features_enabled"`
 	CapiProvisionerEnabled bool    `json:"capi_provisioner_enabled"`
 	DBEnabled              bool    `json:"db_enabled"`
 	EFSEnabled             bool    `json:"efs_enabled"`
+	EnableReprovision      bool    `json:"enable_reprovision"`
+	FullAddOns             bool    `json:"full_add_ons"`
 	GPUEnabled             bool    `json:"gpu_enabled"`
-	SimplifiedViewEnabled  bool    `json:"simplified_view_enabled"`
-	AzureEnabled           bool    `json:"azure_enabled"`
 	HelmValuesEnabled      bool    `json:"helm_values_enabled"`
+	ManagedInfraEnabled    bool    `json:"managed_infra_enabled"`
 	MultiCluster           bool    `json:"multi_cluster"`
-	FullAddOns             bool    `json:"full_add_ons"`
-	EnableReprovision      bool    `json:"enable_reprovision"`
-	ValidateApplyV2        bool    `json:"validate_apply_v2"`
+	PreviewEnvsEnabled     bool    `json:"preview_envs_enabled"`
 	QuotaIncrease          bool    `json:"quota_increase"`
-	BetaFeaturesEnabled    bool    `json:"beta_features_enabled"`
-	AWSACKAuthEnabled      bool    `json:"aws_ack_auth_enabled"`
+	RDSDatabasesEnabled    bool    `json:"enable_rds_databases"`
+	SimplifiedViewEnabled  bool    `json:"simplified_view_enabled"`
+	SOC2ControlsEnabled    bool    `json:"soc2_controls_enabled"`
+	StacksEnabled          bool    `json:"stacks_enabled"`
+	ValidateApplyV2        bool    `json:"validate_apply_v2"`
 }
 
+// FeatureFlags is a struct that contains old feature flag representations
+//
+// Deprecated: Add the feature flag to the `Project` struct instead and
+// retrieve feature flags from the `GET /projects/{project_id}` response instead
 type FeatureFlags struct {
-	PreviewEnvironmentsEnabled string `json:"preview_environments_enabled,omitempty"`
-	ManagedInfraEnabled        string `json:"managed_infra_enabled,omitempty"`
-	StacksEnabled              string `json:"stacks_enabled,omitempty"`
-	ManagedDatabasesEnabled    string `json:"managed_databases_enabled,omitempty"`
-	CapiProvisionerEnabled     string `json:"capi_provisioner_enabled,omitempty"`
-	SimplifiedViewEnabled      string `json:"simplified_view_enabled,omitempty"`
 	AzureEnabled               bool   `json:"azure_enabled,omitempty"`
+	CapiProvisionerEnabled     string `json:"capi_provisioner_enabled,omitempty"`
+	EnableReprovision          bool   `json:"enable_reprovision,omitempty"`
+	FullAddOns                 bool   `json:"full_add_ons,omitempty"`
 	HelmValuesEnabled          bool   `json:"helm_values_enabled,omitempty"`
+	ManagedDatabasesEnabled    string `json:"managed_databases_enabled,omitempty"`
+	ManagedInfraEnabled        string `json:"managed_infra_enabled,omitempty"`
 	MultiCluster               bool   `json:"multi_cluster,omitempty"`
-	FullAddOns                 bool   `json:"full_add_ons,omitempty"`
-	EnableReprovision          bool   `json:"enable_reprovision,omitempty"`
+	PreviewEnvironmentsEnabled string `json:"preview_environments_enabled,omitempty"`
+	SimplifiedViewEnabled      string `json:"simplified_view_enabled,omitempty"`
+	StacksEnabled              string `json:"stacks_enabled,omitempty"`
 	ValidateApplyV2            bool   `json:"validate_apply_v2"`
 }
 
+// CreateProjectRequest is a struct that contains the information
+// necessary to seed a project
 type CreateProjectRequest struct {
 	Name string `json:"name" form:"required"`
 }
 
+// CreateProjectResponse is a struct that contains the response from a create project call
 type CreateProjectResponse Project
 
+// CreateProjectRoleRequest is a struct that contains the information needed to set a role on a user
 type CreateProjectRoleRequest struct {
 	Kind   string `json:"kind" form:"required"`
 	UserID uint   `json:"user_id" form:"required"`
 }
 
+// ProjectInviteAdminRequest is a struct that contains the information needed to invite an admin to a project
 type ProjectInviteAdminRequest struct{}
 
+// ReadProjectResponse is a struct that contains the response from a `GET /projects/{project_id}` request
 type ReadProjectResponse Project
 
+// ListProjectsRequest is a struct that contains the information needed to make a `GET /projects` request
 type ListProjectsRequest struct{}
 
+// ListProjectsResponse is a struct that contains the response from a `GET /projects` request
 type ListProjectsResponse []Project
 
+// DeleteProjectRequest is a struct that contains the information needed to make a `DELETE /projects` request
 type DeleteProjectRequest struct {
 	Name string `json:"name" form:"required"`
 }
 
+// DeleteProjectResponse is a struct that contains the response from a `DELETE /projects` request
 type DeleteProjectResponse Project
 
+// ListProjectInfraResponse is a struct that contains the response from a `GET projects/{project_id}/infra` request
 type ListProjectInfraResponse []*Infra
 
+// GetProjectPolicyResponse is a struct that contains the response from a `GET projects/{project_id}/policy` request
 type GetProjectPolicyResponse []*PolicyDocument
 
+// ListProjectRolesResponse is a struct that contains the response from a `GET projects/{project_id}/roles` request
 type ListProjectRolesResponse []RoleKind
 
+// Collaborator is a struct defining a collaborator on a project
 type Collaborator struct {
 	ID        uint   `json:"id"`
 	Kind      string `json:"kind"`
@@ -103,48 +123,54 @@ type Collaborator struct {
 	ProjectID uint   `json:"project_id"`
 }
 
+// ListCollaboratorsResponse is a struct that contains the response from a `GET projects/{project_id}/collaborators` request
 type ListCollaboratorsResponse []*Collaborator
 
+// UpdateRoleRequest is a struct that contains the information needed to make a `POST projects/{project_id}/roles` request
 type UpdateRoleRequest struct {
 	UserID uint   `json:"user_id,required"`
 	Kind   string `json:"kind,required"`
 }
 
+// UpdateRoleResponse is a struct that contains the response from a `POST projects/{project_id}/roles` request
 type UpdateRoleResponse struct {
 	*Role
 }
 
+// DeleteRoleRequest is a struct that contains the response from a `DELETE projects/{project_id}/roles` request
 type DeleteRoleRequest struct {
 	UserID uint `schema:"user_id,required"`
 }
 
+// DeleteRoleResponse is a struct that contains the response from a `DELETE projects/{project_id}/roles` request
 type DeleteRoleResponse struct {
 	*Role
 }
 
-type GetBillingTokenResponse struct {
-	Token  string `json:"token"`
-	TeamID string `json:"team_id"`
-}
-
+// GetProjectBillingResponse is a struct that contains the response from a `GET projects/{project_id}/billing` request
 type GetProjectBillingResponse struct {
 	HasBilling bool `json:"has_billing"`
 }
 
+// StepEnum is a type describing the current onboarding step
 type StepEnum string
 
 const (
+	// StepConnectSource is a value describing the current onboarding step as `connect_source` (the first step)
 	StepConnectSource StepEnum = "connect_source"
-	StepGithub        StepEnum = "github"
 )
 
+// ConnectedSourceType describes the source of an onboarding
 type ConnectedSourceType string
 
 const (
+	// ConnectedSourceTypeGithub is the github source
 	ConnectedSourceTypeGithub = "github"
+	// ConnectedSourceTypeDocker is the docker source
 	ConnectedSourceTypeDocker = "docker"
 )
 
+// OnboardingData is an onboarding step
 type OnboardingData struct {
 	CurrentStep                    StepEnum            `json:"current_step"`
 	ConnectedSource                ConnectedSourceType `json:"connected_source"`
@@ -161,8 +187,10 @@ type OnboardingData struct {
 	ClusterInfraProvider           string              `json:"cluster_infra_provider"`
 }
 
+// UpdateOnboardingRequest is a struct that contains the information needed to make a `POST projects/{project_id}/onboarding` request
 type UpdateOnboardingRequest OnboardingData
 
+// UpdateOnboardingStepRequest is a struct that contains the information needed to make a `POST projects/{project_id}/onboarding_step` request
 type UpdateOnboardingStepRequest struct {
 	Step              string `json:"step" form:"required,max=255"`
 	Provider          string `json:"provider"`

+ 1 - 7
dashboard/.eslintrc.json

@@ -45,12 +45,6 @@
         "ignoreVoid": true
       }
     ],
-    "@typescript-eslint/prefer-nullish-coalescing": [
-      "error",
-      {
-        "ignoreConditionalTests": true,
-        "ignoreMixedLogicalExpressions": true
-      }
-    ]
+    "@typescript-eslint/prefer-nullish-coalescing": "off"
   }
 }

+ 7 - 7
dashboard/package-lock.json

@@ -91,7 +91,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.51",
+        "@porter-dev/api-contracts": "^0.2.52",
         "@testing-library/jest-dom": "^4.2.4",
         "@testing-library/react": "^9.3.2",
         "@testing-library/user-event": "^7.1.2",
@@ -2658,9 +2658,9 @@
       }
     },
     "node_modules/@porter-dev/api-contracts": {
-      "version": "0.2.51",
-      "resolved": "https://registry.npmjs.org/@porter-dev/api-contracts/-/api-contracts-0.2.51.tgz",
-      "integrity": "sha512-DjqEgB7gZbCuLwCoYyHmdtzMxlyPYCJPElmQOXDFqyX9FZBIYl3GTRgjITl7n0t7VzI8u5VALejK4F6vLfF3kQ==",
+      "version": "0.2.52",
+      "resolved": "https://registry.npmjs.org/@porter-dev/api-contracts/-/api-contracts-0.2.52.tgz",
+      "integrity": "sha512-mzRnBLm56vbX5wwgLnPqGHu9DpMacuESRYwQGszdWzK0T8xZiGEApHYCU1m+G9yYUUXaGUAGYgMrEBbkYwkj2g==",
       "dev": true,
       "dependencies": {
         "@bufbuild/protobuf": "^1.1.0"
@@ -19826,9 +19826,9 @@
       "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A=="
     },
     "@porter-dev/api-contracts": {
-      "version": "0.2.51",
-      "resolved": "https://registry.npmjs.org/@porter-dev/api-contracts/-/api-contracts-0.2.51.tgz",
-      "integrity": "sha512-DjqEgB7gZbCuLwCoYyHmdtzMxlyPYCJPElmQOXDFqyX9FZBIYl3GTRgjITl7n0t7VzI8u5VALejK4F6vLfF3kQ==",
+      "version": "0.2.52",
+      "resolved": "https://registry.npmjs.org/@porter-dev/api-contracts/-/api-contracts-0.2.52.tgz",
+      "integrity": "sha512-mzRnBLm56vbX5wwgLnPqGHu9DpMacuESRYwQGszdWzK0T8xZiGEApHYCU1m+G9yYUUXaGUAGYgMrEBbkYwkj2g==",
       "dev": true,
       "requires": {
         "@bufbuild/protobuf": "^1.1.0"

+ 1 - 1
dashboard/package.json

@@ -98,7 +98,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.51",
+    "@porter-dev/api-contracts": "^0.2.52",
     "@testing-library/jest-dom": "^4.2.4",
     "@testing-library/react": "^9.3.2",
     "@testing-library/user-event": "^7.1.2",

+ 5 - 0
dashboard/src/assets/sparkle.svg

@@ -0,0 +1,5 @@
+<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M8.93789 28.6018C9.33889 27.5181 10.8716 27.5181 11.2726 28.6018L13.2685 33.9957C13.3946 34.3364 13.6632 34.605 14.0039 34.7311L19.3978 36.727C20.4814 37.128 20.4814 38.6607 19.3978 39.0617L14.0039 41.0576C13.6632 41.1837 13.3946 41.4523 13.2685 41.793L11.2726 47.1868C10.8716 48.2705 9.33889 48.2705 8.93789 47.1868L6.942 41.793C6.81592 41.4523 6.5473 41.1837 6.20659 41.0576L0.812762 39.0617C-0.27092 38.6607 -0.270921 37.128 0.812761 36.727L6.20659 34.7311C6.5473 34.605 6.81592 34.3364 6.942 33.9957L8.93789 28.6018Z" fill="#FDFDFD"/>
+<path d="M32.5005 13.7484C33.0519 12.2583 35.1594 12.2583 35.7108 13.7484L38.4552 21.1649C38.6285 21.6334 38.9979 22.0028 39.4663 22.1761L46.8829 24.9205C48.3729 25.4718 48.3729 27.5794 46.8829 28.1307L39.4663 30.8751C38.9979 31.0484 38.6285 31.4178 38.4552 31.8863L35.7108 39.3028C35.1594 40.7928 33.0519 40.7929 32.5005 39.3028L29.7562 31.8863C29.5828 31.4178 29.2135 31.0484 28.745 30.8751L21.3285 28.1307C19.8384 27.5794 19.8384 25.4718 21.3285 24.9205L28.745 22.1761C29.2135 22.0028 29.5828 21.6334 29.7562 21.1649L32.5005 13.7484Z" fill="#FDFDFD"/>
+<path d="M13.9906 0.812762C14.3916 -0.27092 15.9244 -0.270921 16.3254 0.812761L18.3213 6.20659C18.4473 6.5473 18.716 6.81592 19.0567 6.942L24.4505 8.93789C25.5342 9.33889 25.5342 10.8716 24.4505 11.2726L19.0567 13.2685C18.716 13.3946 18.4473 13.6632 18.3213 14.0039L16.3254 19.3978C15.9244 20.4814 14.3916 20.4814 13.9906 19.3978L11.9947 14.0039C11.8687 13.6632 11.6 13.3946 11.2593 13.2685L5.8655 11.2726C4.78181 10.8716 4.78181 9.33889 5.8655 8.93789L11.2593 6.942C11.6 6.81592 11.8687 6.5473 11.9947 6.20659L13.9906 0.812762Z" fill="#FDFDFD"/>
+</svg>

+ 30 - 3
dashboard/src/components/ProvisionerSettings.tsx

@@ -47,7 +47,6 @@ import { Integer } from "type-fest";
 import InputSlider from "./porter/InputSlider";
 import GPUProvisionSettings from "./GPUProvisionSettings";
 
-
 const regionOptions = [
   { value: "us-east-1", label: "US East (N. Virginia) us-east-1" },
   { value: "us-east-2", label: "US East (Ohio) us-east-2" },
@@ -116,6 +115,7 @@ const initialClusterState: ClusterState = {
   clusterName: "",
   awsRegion: "us-east-1",
   machineType: "t3.medium",
+  ecrScanningEnabled: false,
   guardDutyEnabled: false,
   kmsEncryptionEnabled: false,
   loadBalancerType: false,
@@ -372,6 +372,7 @@ const ProvisionerSettings: React.FC<Props> = (props) => {
             logging: controlPlaneLogs,
             enableGuardDuty: clusterState.guardDutyEnabled,
             enableKmsEncryption: clusterState.kmsEncryptionEnabled,
+            enableEcrScanning: clusterState.ecrScanningEnabled,
             network: new AWSClusterNetwork({
               vpcCidr: clusterState.cidrRangeVPC || defaultCidrVpc,
               serviceCidr: clusterState.cidrRangeServices || defaultCidrServices,
@@ -555,6 +556,10 @@ const ProvisionerSettings: React.FC<Props> = (props) => {
         "kmsEncryptionEnabled",
         eksValues.enableKmsEncryption
       );
+      handleClusterStateChange(
+        "ecrScanningEnabled",
+        eksValues.enableEcrScanning
+      );
     }
   }, [isExpanded, props.selectedClusterVersion]);
 
@@ -1073,6 +1078,28 @@ const ProvisionerSettings: React.FC<Props> = (props) => {
                     <Spacer y={1} />
                   </>
                 )}
+
+                <FlexCenter>
+                  <Checkbox
+                    checked={clusterState.ecrScanningEnabled}
+                    disabled={isReadOnly}
+                    toggleChecked={() => {
+                      handleClusterStateChange(
+                        "ecrScanningEnabled",
+                        !clusterState.ecrScanningEnabled
+                      );
+                    }}
+                    disabledTooltip={
+                      "Wait for provisioning to complete before editing this field."
+                    }
+                  >
+                    <Text color="helper">
+                      Enable ECR scanning for this cluster
+                    </Text>
+                  </Checkbox>
+                </FlexCenter>
+                <Spacer y={1} />
+
                 <FlexCenter>
                   <Checkbox
                     checked={clusterState.guardDutyEnabled}
@@ -1106,7 +1133,7 @@ const ProvisionerSettings: React.FC<Props> = (props) => {
                 <FlexCenter>
                   <Checkbox
                     checked={clusterState.kmsEncryptionEnabled}
-                    disabled={isReadOnly || currentCluster != null}
+                    disabled={isReadOnly}
                     toggleChecked={() => {
                       handleClusterStateChange(
                         "kmsEncryptionEnabled",
@@ -1116,7 +1143,7 @@ const ProvisionerSettings: React.FC<Props> = (props) => {
                     disabledTooltip={
                       clusterState.kmsEncryptionEnabled
                         ? "KMS encryption can never be disabled."
-                        : "Encryption is only supported at cluster creation."
+                        : "Wait for provisioning to complete before editing this field."
                     }
                   >
                     <Text color="helper">

+ 1 - 1
dashboard/src/components/porter/ExpandableSection.tsx

@@ -132,7 +132,7 @@ const StyledExpandableSection = styled.div<{
   max-height: 350px;
   overflow: hidden;
   border-radius: 5px;
-  background: ${props => !props.noWrapper && (props.background || "#26292e")};
+  background: ${props => !props.noWrapper && (props.background || "#181B20")};
   border: ${props => !props.noWrapper && "1px solid #494b4f"};
   :hover {
     border: ${props => !props.noWrapper && "1px solid #7a7b80"};

+ 80 - 0
dashboard/src/components/porter/ToggleRow.tsx

@@ -0,0 +1,80 @@
+import React from "react";
+import styled from "styled-components";
+import Tooltip from "./Tooltip";
+
+type Props = {
+  isToggled: boolean;
+  onToggle: () => void;
+  children: React.ReactNode;
+  disabled?: boolean;
+  disabledTooltip?: string;
+};
+
+const ToggleRow: React.FC<Props> = ({
+  isToggled,
+  onToggle,
+  children,
+  disabled = false,
+  disabledTooltip,
+}) => {
+  return (
+    disabled && disabledTooltip ?
+      <Tooltip content={disabledTooltip} position="right">
+        <StyledToggle>
+          <ToggleContainer
+            isToggled={isToggled}
+            onClick={disabled ? () => { } : onToggle}
+            disabled={disabled}
+          >
+            <ToggleDot />
+          </ToggleContainer>
+          {children}
+        </StyledToggle>
+      </Tooltip>
+      :
+      <StyledToggle>
+        <ToggleContainer
+          isToggled={isToggled}
+          onClick={disabled ? () => { } : onToggle}
+          disabled={disabled}
+        >
+          <ToggleDot />
+        </ToggleContainer>
+        {children}
+      </StyledToggle>
+  );
+};
+
+export default ToggleRow; 
+
+const StyledToggle = styled.div`
+  display: flex;
+  align-items: center;
+`;
+
+const ToggleDot = styled.div`
+  width: 9px;
+  height: 9px;
+  margin: 2px;
+  background: #aaaabb;
+  border-radius: 50px;
+`;
+
+const ToggleContainer = styled.div<{
+  isToggled: boolean;
+  disabled?: boolean;
+}>`
+  width: 30px;
+  height: 16px;
+  border: 1px solid #ffffff55;
+  margin-right: 10px;
+  border-radius: 50px;
+  background: ${(props) => (props.isToggled ? "#ffffff33" : "#ffffff11")};
+  display: flex;
+  align-items: center;
+  justify-content ${props => props.isToggled ? "flex-end" : "flex-start"};
+  cursor: ${(props) => (props.disabled ? "not-allowed" : "pointer")};
+  :hover {
+    border: 1px solid #aaaabb;
+  }
+`;

+ 1 - 1
dashboard/src/main/home/app-dashboard/app-view/tabs/activity-feed/events/cards/ServiceStatusDetail.tsx

@@ -149,7 +149,7 @@ const ServiceStatusDetail: React.FC<Props> = ({
                           showTargetBlankIcon={false}
                         >
                           <TagIcon src={link} />
-                          External Link
+                          External link
                         </Link>
                       </Tag>
                     </>

+ 1 - 1
dashboard/src/main/home/app-dashboard/expanded-app/activity-feed/events/cards/ServiceStatusDetail.tsx

@@ -89,7 +89,7 @@ const ServiceStatusDetail: React.FC<Props> = ({
                                             hoverColor="#949eff"
                                             target={"_blank"}
                                         >
-                                            External Link
+                                            External link
                                         </Link>
                                     </>
                                 }

+ 404 - 0
dashboard/src/main/home/cluster-dashboard/dashboard/Compliance.tsx

@@ -0,0 +1,404 @@
+import React, { useContext, useEffect, useMemo, useState } from "react";
+import type { JsonValue } from "@bufbuild/protobuf";
+import { Cluster, Contract, EKS, EKSLogging } from "@porter-dev/api-contracts";
+import axios from "axios";
+import styled from "styled-components";
+import { match } from "ts-pattern";
+
+import Loading from "components/Loading";
+import Button from "components/porter/Button";
+import Container from "components/porter/Container";
+import Error from "components/porter/Error";
+import Spacer from "components/porter/Spacer";
+import Text from "components/porter/Text";
+import ToggleRow from "components/porter/ToggleRow";
+
+import api from "shared/api";
+import { Context } from "shared/Context";
+import sparkle from "assets/sparkle.svg";
+
+type Props = {
+  credentialId: string;
+  provisionerError?: string;
+  selectedClusterVersion: JsonValue;
+};
+
+const DEFAULT_ERROR_MESSAGE =
+  "An error occurred while provisioning your infrastructure. Please try again.";
+
+const Compliance: React.FC<Props> = (props) => {
+  const { currentProject, currentCluster, setShouldRefreshClusters } =
+    useContext(Context);
+
+  const [cloudTrailEnabled, setCloudTrailEnabled] = useState(false);
+  // const [cloudTrailRetention, setCloudTrailRetention] = useState(false);
+  const [ecrScanningEnabled, setEcrScanningEnabled] = useState(false);
+  const [isReadOnly, setIsReadOnly] = useState(false);
+  const [isClicked, setIsClicked] = useState(false);
+  const [isLoading, setIsLoading] = useState(false);
+  const [kmsEnabled, setKmsEnabled] = useState(false);
+  const [soc2Enabled, setSoc2Enabled] = useState(false);
+  const [clusterRegion, setClusterRegion] = useState("");
+  const [errorMessage, setErrorMessage] = useState<string>("");
+  const [errorDetails, setErrorDetails] = useState<string>("");
+
+  const applySettings = async (): Promise<void> => {
+    if (!currentCluster || !currentProject || !setShouldRefreshClusters) {
+      return;
+    }
+
+    try {
+      setIsLoading(true);
+      setIsClicked(true);
+      setIsReadOnly(true);
+
+      const contractResults = await api.getContracts(
+        "<token>",
+        { cluster_id: currentCluster.id },
+        { project_id: currentProject.id }
+      );
+
+      if (contractResults.data.length === 0) {
+        setErrorMessage("Unable to retrieve contract results");
+        setErrorDetails("");
+        return;
+      }
+
+      const result = contractResults.data.reduce(
+        (prev: { CreatedAt: string }, current: { CreatedAt: string }) =>
+          Date.parse(current.CreatedAt) > Date.parse(prev.CreatedAt)
+            ? current
+            : prev
+      );
+
+      const contract = createContract(result.base64_contract);
+
+      await api.createContract("<token>", contract, {
+        project_id: currentProject.id,
+      });
+      setShouldRefreshClusters(true);
+
+      setIsClicked(false);
+      setIsLoading(false);
+      setIsReadOnly(false);
+    } catch (err) {
+      let errMessage =
+        "Failed to provision cluster, please contact support@porter.run.";
+      if (axios.isAxiosError(err) && err.response?.data) {
+        errMessage = err.response.data.error.replace("unknown: ", "");
+      }
+
+      // hacky, need to standardize error contract with backend
+      setIsClicked(false);
+      setIsLoading(false);
+      void markStepStarted("provisioning-failed", errMessage);
+
+      // enable edit again only in the case of an error
+      setIsClicked(false);
+      setIsReadOnly(false);
+    } finally {
+      setIsLoading(false);
+    }
+  };
+
+  const createContract = (base64Contract: string): Contract => {
+    const contractData = JSON.parse(atob(base64Contract));
+    const latestCluster: Cluster = Cluster.fromJson(contractData.cluster, {
+      ignoreUnknownFields: true,
+    });
+
+    const updatedKindValues = match(latestCluster.kindValues)
+      .with({ case: "eksKind" }, ({ value }) => ({
+        value: new EKS({
+          ...value,
+          enableKmsEncryption: soc2Enabled || kmsEnabled || false,
+          enableEcrScanning:
+            soc2Enabled ||
+            ecrScanningEnabled ||
+            value.enableEcrScanning ||
+            false,
+          logging: new EKSLogging({
+            enableApiServerLogs: soc2Enabled || cloudTrailEnabled || false,
+            enableAuditLogs: soc2Enabled || cloudTrailEnabled || false,
+            enableAuthenticatorLogs: soc2Enabled || cloudTrailEnabled || false,
+            enableCloudwatchLogsToS3: soc2Enabled || cloudTrailEnabled || false,
+            enableControllerManagerLogs:
+              soc2Enabled || cloudTrailEnabled || false,
+            enableSchedulerLogs: soc2Enabled || cloudTrailEnabled || false,
+          }),
+        }),
+        case: "eksKind" as const,
+      }))
+      .with({ case: "gkeKind" }, ({ value }) => ({
+        value,
+        case: "gkeKind" as const,
+      }))
+      .with({ case: "aksKind" }, ({ value }) => ({
+        value,
+        case: "aksKind" as const,
+      }))
+      .with({ case: undefined }, () => ({
+        value: undefined,
+        case: undefined,
+      }))
+      .exhaustive();
+    const cluster = new Cluster({
+      ...latestCluster,
+      kindValues: updatedKindValues,
+    });
+
+    return new Contract({
+      cluster,
+    });
+  };
+
+  const getStatus = (): JSX.Element | string => {
+    if (isLoading) {
+      return <Loading />;
+    }
+    if (isReadOnly && props.provisionerError === "") {
+      return "Provisioning is still in progress...";
+    } else if (errorMessage !== "") {
+      return (
+        <Error
+          message={
+            errorDetails !== ""
+              ? errorMessage + " (" + errorDetails + ")"
+              : errorMessage
+          }
+          ctaText={
+            errorMessage !== DEFAULT_ERROR_MESSAGE
+              ? "Troubleshooting steps"
+              : undefined
+          }
+        />
+      );
+    }
+    return "";
+  };
+
+  const isDisabled = (): boolean | undefined => {
+    return (
+      isUserProvisioning ||
+      isClicked ||
+      (currentCluster && !currentProject?.enable_reprovision)
+    );
+  };
+
+  const markStepStarted = async (
+    step: string,
+    errMessage?: string
+  ): Promise<void> => {
+    try {
+      await api.updateOnboardingStep(
+        "<token>",
+        {
+          step,
+          error_message: errMessage,
+          region: clusterRegion,
+          provider: "aws",
+        },
+        {
+          project_id: currentProject ? currentProject.id : 0,
+        }
+      );
+    } catch (err) {}
+  };
+
+  const isUserProvisioning = useMemo(() => {
+    return isReadOnly && props.provisionerError === "";
+  }, [isReadOnly, props.provisionerError]);
+
+  useEffect(() => {
+    const contract: Contract = Contract.fromJson(props.selectedClusterVersion, {
+      ignoreUnknownFields: true,
+    });
+
+    if (contract.cluster && contract.cluster.kindValues.case === "eksKind") {
+      const eksValues = contract.cluster.kindValues.value;
+      const cloudTrailEnabled =
+        eksValues.logging != null &&
+        eksValues.logging.enableApiServerLogs &&
+        eksValues.logging.enableAuditLogs &&
+        eksValues.logging.enableAuthenticatorLogs &&
+        eksValues.logging.enableControllerManagerLogs;
+
+      setCloudTrailEnabled(cloudTrailEnabled);
+      setClusterRegion(eksValues.region);
+      setEcrScanningEnabled(eksValues.enableEcrScanning);
+      setKmsEnabled(eksValues.enableKmsEncryption);
+
+      setSoc2Enabled(
+        cloudTrailEnabled &&
+          eksValues.enableKmsEncryption &&
+          eksValues.enableEcrScanning
+      );
+    }
+  }, [props.selectedClusterVersion]);
+
+  useEffect(() => {
+    if (!currentCluster) {
+      return;
+    }
+
+    setIsReadOnly(
+      currentCluster.status === "UPDATING" ||
+        currentCluster.status === "UPDATING_UNAVAILABLE"
+    );
+  }, []);
+
+  useEffect(() => {
+    if (soc2Enabled) {
+      setCloudTrailEnabled(true);
+      // setCloudTrailRetention(true);
+      setEcrScanningEnabled(true);
+      setKmsEnabled(true);
+    }
+  }, [soc2Enabled]);
+  return (
+    <StyledCompliance>
+      <Spacer y={1} />
+      <Container row>
+        <Text size={16}>SOC 2 compliance</Text>
+        <Spacer inline x={1} />
+        <NewBadge>
+          <img src={sparkle} />
+          New
+        </NewBadge>
+      </Container>
+      <Spacer y={0.5} />
+      <Text color="helper">
+        Configure your AWS infrastructure to be SOC 2 compliant with Porter.
+      </Text>
+      <Spacer y={0.5} />
+      <ToggleRow
+        isToggled={soc2Enabled}
+        onToggle={() => {
+          setSoc2Enabled((prev) => !prev);
+        }}
+        disabled={isReadOnly}
+        disabledTooltip={
+          "Wait for provisioning to complete before editing this field."
+        }
+      >
+        <Container row>
+          <Text>Enable all SOC 2 settings</Text>
+        </Container>
+      </ToggleRow>
+      <Spacer y={0.5} />
+      <GutterContainer>
+        <ToggleRow
+          isToggled={cloudTrailEnabled}
+          onToggle={() => {
+            setCloudTrailEnabled((prev) => !prev);
+          }}
+          disabled={soc2Enabled || isReadOnly}
+          disabledTooltip={
+            soc2Enabled
+              ? "Global SOC 2 setting must be disabled to toggle this"
+              : "Wait for provisioning to complete before editing this field."
+          }
+        >
+          <Container row>
+            <Text>EKS CloudTrail Forwarding</Text>
+            <Spacer inline x={1} />
+            <Text color="helper">
+              Forward all application and control plane logs to CloudTrail.
+            </Text>
+          </Container>
+        </ToggleRow>
+        {/* <Spacer y={0.5} />
+        <ToggleRow
+          isToggled={cloudTrailRetention}
+          onToggle={() => { setCloudTrailRetention((prev) => !prev) }}
+          disabled={soc2Enabled || isReadOnly}
+          disabledTooltip={
+            soc2Enabled ? "Global SOC 2 setting must be disabled to toggle this" : "Wait for provisioning to complete before editing this field."
+          }
+        >
+          <Container row>
+            <Text>Retain CloudTrail logs for 365 days</Text>
+            <Spacer inline x={1} />
+            <Text color="helper">Store CloudTrail logs in an S3 bucket for 365 days.</Text>
+          </Container>
+        </ToggleRow> */}
+        <Spacer y={0.5} />
+        <ToggleRow
+          isToggled={kmsEnabled}
+          onToggle={() => {
+            setKmsEnabled((prev) => !prev);
+          }}
+          disabled={soc2Enabled || isReadOnly || kmsEnabled}
+          disabledTooltip={
+            kmsEnabled
+              ? "KMS encryption can never be disabled."
+              : soc2Enabled
+              ? "Global SOC 2 setting must be disabled to toggle this"
+              : "Wait for provisioning to complete before editing this field."
+          }
+        >
+          <Container row>
+            <Text>AWS KMS Secret Encryption</Text>
+            <Spacer inline x={1} />
+            <Text color="helper">
+              Encrypt secrets with AWS Key Management Service.
+            </Text>
+          </Container>
+        </ToggleRow>
+        <Spacer y={0.5} />
+        <ToggleRow
+          isToggled={ecrScanningEnabled}
+          onToggle={() => {
+            setEcrScanningEnabled((prev) => !prev);
+          }}
+          disabled={soc2Enabled || isReadOnly}
+          disabledTooltip={
+            soc2Enabled
+              ? "Global SOC 2 setting must be disabled to toggle this"
+              : "Wait for provisioning to complete before editing this field."
+          }
+        >
+          <Container row>
+            <Text>Enhanced ECR scanning</Text>
+            <Spacer inline x={1} />
+            <Text color="helper">
+              Scan ECR image repositories for vulnerabilities.
+            </Text>
+          </Container>
+        </ToggleRow>
+      </GutterContainer>
+      <Spacer y={1} />
+      <Button
+        disabled={isDisabled() ?? isLoading}
+        onClick={applySettings}
+        status={getStatus()}
+      >
+        Save settings
+      </Button>
+    </StyledCompliance>
+  );
+};
+
+export default Compliance;
+
+const StyledCompliance = styled.div``;
+
+const GutterContainer = styled.div`
+  border-left: 1px solid #313237;
+  margin-left: 5px;
+  padding-left: 15px;
+`;
+
+const NewBadge = styled.div`
+  font-size: 13px;
+  padding: 5px 10px;
+  background: linear-gradient(110deg, #b6d5f2, #6836e2);
+  border-radius: 5px;
+  display: flex;
+  align-items: center;
+
+  > img {
+    height: 14px;
+    margin-right: 5px;
+  }
+`;

+ 58 - 24
dashboard/src/main/home/cluster-dashboard/dashboard/Dashboard.tsx

@@ -1,28 +1,28 @@
 import React, { useContext, useEffect, useState } from "react";
-import styled from "styled-components";
 import { useLocation } from "react-router";
-import editIcon from "assets/edit-button.svg";
+import styled from "styled-components";
+
+import AzureProvisionerSettings from "components/AzureProvisionerSettings";
+import GCPProvisionerSettings from "components/GCPProvisionerSettings";
+import Spacer from "components/porter/Spacer";
+import ProvisionerSettings from "components/ProvisionerSettings";
+import TabSelector from "components/TabSelector";
 
 import api from "shared/api";
-import { getQueryParam } from "shared/routing";
 import useAuth from "shared/auth/useAuth";
 import { Context } from "shared/Context";
+import { getQueryParam } from "shared/routing";
+import editIcon from "assets/edit-button.svg";
 
-import ClusterRevisionSelector from "./ClusterRevisionSelector";
 import DashboardHeader from "../DashboardHeader";
-import TabSelector from "components/TabSelector";
-import ProvisionerSettings from "components/ProvisionerSettings";
-import ProvisionerStatus from "./ProvisionerStatus";
-import NodeList from "./NodeList";
-import { NamespaceList } from "./NamespaceList";
+import ClusterRevisionSelector from "./ClusterRevisionSelector";
 import ClusterSettings from "./ClusterSettings";
-import Metrics from "./Metrics";
 import ClusterSettingsModal from "./ClusterSettingsModal";
-
-import Loading from "components/Loading";
-import Spacer from "components/porter/Spacer";
-import AzureProvisionerSettings from "components/AzureProvisionerSettings";
-import GCPProvisionerSettings from "components/GCPProvisionerSettings";
+import Compliance from "./Compliance";
+import Metrics from "./Metrics";
+import { NamespaceList } from "./NamespaceList";
+import NodeList from "./NodeList";
+import ProvisionerStatus from "./ProvisionerStatus";
 
 type TabEnum =
   | "nodes"
@@ -30,6 +30,7 @@ type TabEnum =
   | "namespaces"
   | "metrics"
   | "incidents"
+  | "compliance"
   | "configuration";
 
 var tabOptions: {
@@ -37,6 +38,20 @@ var tabOptions: {
   value: TabEnum;
 }[] = [{ label: "Additional settings", value: "settings" }];
 
+const COMPLIANCE_SUPPORTED_PROVIDERS = ["AWS"];
+
+const showComplianceTab = (
+  capi_provisioner_enabled: boolean,
+  soc2_controls_enabled: boolean,
+  cloud_provider: string
+): boolean => {
+  return (
+    capi_provisioner_enabled &&
+    soc2_controls_enabled &&
+    COMPLIANCE_SUPPORTED_PROVIDERS.includes(cloud_provider)
+  );
+};
+
 export const Dashboard: React.FunctionComponent = () => {
   const [currentTab, setCurrentTab] = useState<TabEnum>("settings");
   const [currentTabOptions, setCurrentTabOptions] = useState(tabOptions);
@@ -47,7 +62,6 @@ export const Dashboard: React.FunctionComponent = () => {
   const [provisionFailureReason, setProvisionFailureReason] = useState("");
   const [ingressIp, setIngressIp] = useState("");
   const [ingressError, setIngressError] = useState("");
-  const [cloudProvider, setCloudProvider] = useState("azure");
 
   const context = useContext(Context);
   const renderTab = () => {
@@ -66,11 +80,21 @@ export const Dashboard: React.FunctionComponent = () => {
         return <Metrics />;
       case "namespaces":
         return <NamespaceList />;
+      case "compliance":
+        return selectedClusterVersion ? (
+          <Compliance
+            provisionerError={provisionFailureReason}
+            selectedClusterVersion={selectedClusterVersion}
+            credentialId={
+              context.currentCluster.cloud_provider_credential_identifier
+            }
+          />
+        ) : null;
       case "configuration":
         return (
           <>
             <Br />
-            {context.currentCluster.cloud_provider == "AWS" && (
+            {context.currentCluster.cloud_provider === "AWS" && (
               <ProvisionerSettings
                 selectedClusterVersion={selectedClusterVersion}
                 provisionerError={provisionFailureReason}
@@ -80,7 +104,7 @@ export const Dashboard: React.FunctionComponent = () => {
                 }
               />
             )}
-            {context.currentCluster.cloud_provider == "Azure" && (
+            {context.currentCluster.cloud_provider === "Azure" && (
               <AzureProvisionerSettings
                 selectedClusterVersion={selectedClusterVersion}
                 provisionerError={provisionFailureReason}
@@ -90,7 +114,7 @@ export const Dashboard: React.FunctionComponent = () => {
                 }
               />
             )}
-            {context.currentCluster.cloud_provider == "GCP" && (
+            {context.currentCluster.cloud_provider === "GCP" && (
               <GCPProvisionerSettings
                 selectedClusterVersion={selectedClusterVersion}
                 provisionerError={provisionFailureReason}
@@ -118,8 +142,17 @@ export const Dashboard: React.FunctionComponent = () => {
         tabOptions.unshift({ label: "Metrics", value: "metrics" });
         tabOptions.unshift({ label: "Nodes", value: "nodes" });
       }
-      // tabOptions.unshift({ label: "Metrics", value: "metrics" });
-      // tabOptions.unshift({ label: "Nodes", value: "nodes" });
+    }
+
+    if (
+      showComplianceTab(
+        context.currentProject?.capi_provisioner_enabled,
+        context.currentProject?.soc2_controls_enabled,
+        context.currentCluster.cloud_provider
+      ) &&
+      !tabOptions.find((tab) => tab.value === "compliance")
+    ) {
+      tabOptions.unshift({ value: "compliance", label: "Compliance" });
     }
 
     if (
@@ -173,7 +206,7 @@ export const Dashboard: React.FunctionComponent = () => {
         setIngressIp(ingress_ip);
         setIngressError(ingress_error);
       }
-    } catch (error) { }
+    } catch (error) {}
   };
 
   useEffect(() => {
@@ -300,8 +333,9 @@ export const Dashboard: React.FunctionComponent = () => {
             </EditIconStyle>
           </Flex>
         }
-        description={`Cluster settings and status for ${context.currentCluster.vanity_name || context.currentCluster.name
-          }.`}
+        description={`Cluster settings and status for ${
+          context.currentCluster.vanity_name || context.currentCluster.name
+        }.`}
         disableLineBreak
         capitalize={false}
       />

+ 0 - 1
dashboard/src/main/home/cluster-dashboard/dashboard/ProvisionerStatus.tsx

@@ -10,7 +10,6 @@ import { Context } from "shared/Context";
 import LoadingBar from "components/porter/LoadingBar";
 import Spacer from "components/porter/Spacer";
 import Text from "components/porter/Text";
-import Button from "components/porter/Button";
 
 type Props = {
   provisionFailureReason: string;

+ 13 - 11
dashboard/src/shared/types.tsx

@@ -269,24 +269,25 @@ export type ProjectListType = {
 export type ProjectType = {
   id: number;
   name: string;
-  preview_envs_enabled: boolean;
-  db_enabled: boolean;
-  enable_rds_databases: boolean;
-  managed_infra_enabled: boolean;
-  capi_provisioner_enabled: boolean;
   api_tokens_enabled: boolean;
-  stacks_enabled: boolean;
-  simplified_view_enabled: boolean;
   azure_enabled: boolean;
+  beta_features_enabled: boolean;
+  capi_provisioner_enabled: boolean;
+  db_enabled: boolean;
+  efs_enabled: boolean;
+  enable_rds_databases: boolean;
+  enable_reprovision: boolean;
+  full_add_ons: boolean;
   gpu_enabled: boolean;
   helm_values_enabled: boolean;
+  managed_infra_enabled: boolean;
   multi_cluster: boolean;
-  full_add_ons: boolean;
-  enable_reprovision: boolean;
+  preview_envs_enabled: boolean;
   quota_increase: boolean;
-  efs_enabled: boolean;
+  simplified_view_enabled: boolean;
+  soc2_controls_enabled: boolean;
+  stacks_enabled: boolean;
   validate_apply_v2: boolean;
-  beta_features_enabled: boolean;
   roles: Array<{
     id: number;
     kind: string;
@@ -681,6 +682,7 @@ export type ClusterState = {
   clusterName: string;
   awsRegion: string;
   machineType: string;
+  ecrScanningEnabled: boolean;
   guardDutyEnabled: boolean;
   kmsEncryptionEnabled: boolean;
   loadBalancerType: boolean;

+ 22 - 15
internal/models/project.go

@@ -61,6 +61,9 @@ const (
 	// SimplifiedViewEnabled shows the new UI dashboard or not
 	SimplifiedViewEnabled FeatureFlagLabel = "simplified_view_enabled"
 
+	// SOC2ControlsEnabled decides on whether the SOC2 Compliance UI is shown on the infrastructure tab
+	SOC2ControlsEnabled FeatureFlagLabel = "soc2_controls_enabled"
+
 	// StacksEnabled uses stack view for legacy (simplified_view_enabled=false)
 	StacksEnabled FeatureFlagLabel = "stacks_enabled"
 
@@ -77,7 +80,9 @@ const (
 // ProjectFeatureFlags keeps track of all project-related feature flags
 var ProjectFeatureFlags = map[FeatureFlagLabel]bool{
 	APITokensEnabled:       false,
+	AWSACKAuthEnabled:      false,
 	AzureEnabled:           false,
+	BetaFeaturesEnabled:    false,
 	CapiProvisionerEnabled: true,
 	DBEnabled:              false,
 	EFSEnabled:             false,
@@ -88,13 +93,12 @@ var ProjectFeatureFlags = map[FeatureFlagLabel]bool{
 	ManagedInfraEnabled:    false,
 	MultiCluster:           false,
 	PreviewEnvsEnabled:     false,
-	RDSDatabasesEnabled:    false,
 	QuotaIncrease:          false,
+	RDSDatabasesEnabled:    false,
 	SimplifiedViewEnabled:  true,
+	SOC2ControlsEnabled:    false,
 	StacksEnabled:          false,
 	ValidateApplyV2:        true,
-	BetaFeaturesEnabled:    false,
-	AWSACKAuthEnabled:      false,
 }
 
 type ProjectPlan string
@@ -223,6 +227,8 @@ func (p *Project) GetFeatureFlag(flagName FeatureFlagLabel, launchDarklyClient *
 			return p.RDSDatabasesEnabled
 		case "simplified_view_enabled":
 			return p.SimplifiedViewEnabled
+		case "soc2_controls_enabled":
+			return false
 		case "stacks_enabled":
 			return p.StacksEnabled
 		case "validate_apply_v2":
@@ -258,25 +264,26 @@ func (p *Project) ToProjectType(launchDarklyClient *features.Client) types.Proje
 		Name:  projectName,
 		Roles: roles,
 
-		PreviewEnvsEnabled:     p.GetFeatureFlag(PreviewEnvsEnabled, launchDarklyClient),
-		RDSDatabasesEnabled:    p.GetFeatureFlag(RDSDatabasesEnabled, launchDarklyClient),
-		ManagedInfraEnabled:    p.GetFeatureFlag(ManagedInfraEnabled, launchDarklyClient),
-		StacksEnabled:          p.GetFeatureFlag(StacksEnabled, launchDarklyClient),
 		APITokensEnabled:       p.GetFeatureFlag(APITokensEnabled, launchDarklyClient),
+		AWSACKAuthEnabled:      p.GetFeatureFlag(AWSACKAuthEnabled, launchDarklyClient),
+		AzureEnabled:           p.GetFeatureFlag(AzureEnabled, launchDarklyClient),
+		BetaFeaturesEnabled:    p.GetFeatureFlag(BetaFeaturesEnabled, launchDarklyClient),
 		CapiProvisionerEnabled: p.GetFeatureFlag(CapiProvisionerEnabled, launchDarklyClient),
 		DBEnabled:              p.GetFeatureFlag(DBEnabled, launchDarklyClient),
-		SimplifiedViewEnabled:  p.GetFeatureFlag(SimplifiedViewEnabled, launchDarklyClient),
-		AzureEnabled:           p.GetFeatureFlag(AzureEnabled, launchDarklyClient),
+		EFSEnabled:             p.GetFeatureFlag(EFSEnabled, launchDarklyClient),
+		EnableReprovision:      p.GetFeatureFlag(EnableReprovision, launchDarklyClient),
+		FullAddOns:             p.GetFeatureFlag(FullAddOns, launchDarklyClient),
 		GPUEnabled:             p.GetFeatureFlag(GPUEnabled, launchDarklyClient),
 		HelmValuesEnabled:      p.GetFeatureFlag(HelmValuesEnabled, launchDarklyClient),
+		ManagedInfraEnabled:    p.GetFeatureFlag(ManagedInfraEnabled, launchDarklyClient),
 		MultiCluster:           p.GetFeatureFlag(MultiCluster, launchDarklyClient),
-		EnableReprovision:      p.GetFeatureFlag(EnableReprovision, launchDarklyClient),
-		ValidateApplyV2:        p.GetFeatureFlag(ValidateApplyV2, launchDarklyClient),
-		FullAddOns:             p.GetFeatureFlag(FullAddOns, launchDarklyClient),
+		PreviewEnvsEnabled:     p.GetFeatureFlag(PreviewEnvsEnabled, launchDarklyClient),
 		QuotaIncrease:          p.GetFeatureFlag(QuotaIncrease, launchDarklyClient),
-		EFSEnabled:             p.GetFeatureFlag(EFSEnabled, launchDarklyClient),
-		BetaFeaturesEnabled:    p.GetFeatureFlag(BetaFeaturesEnabled, launchDarklyClient),
-		AWSACKAuthEnabled:      p.GetFeatureFlag(AWSACKAuthEnabled, launchDarklyClient),
+		RDSDatabasesEnabled:    p.GetFeatureFlag(RDSDatabasesEnabled, launchDarklyClient),
+		SimplifiedViewEnabled:  p.GetFeatureFlag(SimplifiedViewEnabled, launchDarklyClient),
+		SOC2ControlsEnabled:    p.GetFeatureFlag(SOC2ControlsEnabled, launchDarklyClient),
+		StacksEnabled:          p.GetFeatureFlag(StacksEnabled, launchDarklyClient),
+		ValidateApplyV2:        p.GetFeatureFlag(ValidateApplyV2, launchDarklyClient),
 	}
 }