fix: removed recovery

This commit is contained in:
Daniel Hougaard
2024-11-11 21:45:06 +04:00
parent 472f02e8b1
commit a3ec1a27de
19 changed files with 34 additions and 591 deletions
-7
View File
@@ -90,7 +90,6 @@
"safe-regex": "^2.1.1", "safe-regex": "^2.1.1",
"scim-patch": "^0.8.3", "scim-patch": "^0.8.3",
"scim2-parse-filter": "^0.2.10", "scim2-parse-filter": "^0.2.10",
"secrets.js-grempe": "^2.0.0",
"sjcl": "^1.0.8", "sjcl": "^1.0.8",
"smee-client": "^2.0.0", "smee-client": "^2.0.0",
"snowflake-sdk": "^1.14.0", "snowflake-sdk": "^1.14.0",
@@ -18366,12 +18365,6 @@
"resolved": "https://registry.npmjs.org/scim2-parse-filter/-/scim2-parse-filter-0.2.10.tgz", "resolved": "https://registry.npmjs.org/scim2-parse-filter/-/scim2-parse-filter-0.2.10.tgz",
"integrity": "sha512-k5TgGSuQEbR4jXRgw/GPAYVL9fMp1pWA2abLF5z3q9IGWSuZTqbrZBOSUezvc+rtViXr+czSZjg3eAN4QSTvxQ==" "integrity": "sha512-k5TgGSuQEbR4jXRgw/GPAYVL9fMp1pWA2abLF5z3q9IGWSuZTqbrZBOSUezvc+rtViXr+czSZjg3eAN4QSTvxQ=="
}, },
"node_modules/secrets.js-grempe": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/secrets.js-grempe/-/secrets.js-grempe-2.0.0.tgz",
"integrity": "sha512-4xkOIaDAg998dTFXZUJTOoVbdLHfB818SMeLJ69ABccgGEKokxsoRFupAFfAImloUSKv4QUGNMgKVbKMf6z0Ug==",
"license": "MIT"
},
"node_modules/secure-json-parse": { "node_modules/secure-json-parse": {
"version": "2.7.0", "version": "2.7.0",
"resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-2.7.0.tgz", "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-2.7.0.tgz",
-1
View File
@@ -195,7 +195,6 @@
"safe-regex": "^2.1.1", "safe-regex": "^2.1.1",
"scim-patch": "^0.8.3", "scim-patch": "^0.8.3",
"scim2-parse-filter": "^0.2.10", "scim2-parse-filter": "^0.2.10",
"secrets.js-grempe": "^2.0.0",
"sjcl": "^1.0.8", "sjcl": "^1.0.8",
"smee-client": "^2.0.0", "smee-client": "^2.0.0",
"snowflake-sdk": "^1.14.0", "snowflake-sdk": "^1.14.0",
@@ -29,6 +29,7 @@ export const getDefaultOnPremFeatures = (): TFeatureSet => ({
auditLogStreams: false, auditLogStreams: false,
auditLogStreamLimit: 3, auditLogStreamLimit: 3,
samlSSO: false, samlSSO: false,
hsm: true,
oidcSSO: false, oidcSSO: false,
scim: false, scim: false,
ldap: false, ldap: false,
@@ -46,6 +46,7 @@ export type TFeatureSet = {
auditLogStreams: false; auditLogStreams: false;
auditLogStreamLimit: 3; auditLogStreamLimit: 3;
samlSSO: false; samlSSO: false;
hsm: false;
oidcSSO: false; oidcSSO: false;
scim: false; scim: false;
ldap: false; ldap: false;
-1
View File
@@ -18,6 +18,5 @@ export {
decryptSecrets, decryptSecrets,
decryptSecretVersions decryptSecretVersions
} from "./secret-encryption"; } from "./secret-encryption";
export { shamirsService } from "./shamirs";
export { verifyOfflineLicense } from "./signing"; export { verifyOfflineLicense } from "./signing";
export { generateSrpServerKey, srpCheckClientProof } from "./srp"; export { generateSrpServerKey, srpCheckClientProof } from "./srp";
-38
View File
@@ -1,38 +0,0 @@
import shamirs from "secrets.js-grempe";
import { getConfig } from "../config/env";
import { symmetricCipherService, SymmetricEncryption } from "./cipher";
export const shamirsService = () => {
const $generateBasicEncryptionKey = () => {
const appCfg = getConfig();
const encryptionKey = appCfg.ENCRYPTION_KEY || appCfg.ROOT_ENCRYPTION_KEY;
const isBase64 = !appCfg.ENCRYPTION_KEY;
if (!encryptionKey)
throw new Error(
"Root encryption key not found for KMS service. Did you set the ENCRYPTION_KEY or ROOT_ENCRYPTION_KEY environment variables?"
);
return Buffer.from(encryptionKey, isBase64 ? "base64" : "utf8");
};
const share = (secretBuffer: Buffer, partsCount: number, thresholdCount: number) => {
const cipher = symmetricCipherService(SymmetricEncryption.AES_GCM_256);
const hexSecret = Buffer.from(cipher.encrypt(secretBuffer, $generateBasicEncryptionKey())).toString("hex");
const secretParts = shamirs.share(hexSecret, partsCount, thresholdCount);
return secretParts;
};
const combine = (parts: string[]) => {
const encryptedSecret = shamirs.combine(parts);
const cipher = symmetricCipherService(SymmetricEncryption.AES_GCM_256);
const decryptedSecret = cipher.decrypt(Buffer.from(encryptedSecret, "hex"), $generateBasicEncryptionKey());
return decryptedSecret;
};
return { share, combine };
};
+1 -50
View File
@@ -196,54 +196,6 @@ export const registerAdminRouter = async (server: FastifyZodProvider) => {
} }
}); });
server.route({
method: "POST",
url: "/kms-export",
config: {
rateLimit: writeLimit
},
schema: {
response: {
200: z.object({
secretParts: z.array(z.string())
})
}
},
onRequest: (req, res, done) => {
verifyAuth([AuthMode.JWT])(req, res, () => {
verifySuperAdmin(req, res, done);
});
},
handler: async () => {
const keyParts = await server.services.superAdmin.exportPlainKmsKey();
return {
secretParts: keyParts
};
}
});
server.route({
method: "POST",
url: "/kms-import",
config: {
rateLimit: writeLimit
},
schema: {
body: z.object({
secretParts: z.array(z.string())
})
},
onRequest: (req, res, done) => {
verifyAuth([AuthMode.JWT])(req, res, () => {
verifySuperAdmin(req, res, done);
});
},
handler: async (req) => {
await server.services.superAdmin.importPlainKmsKey(req.body.secretParts);
}
});
server.route({ server.route({
method: "GET", method: "GET",
url: "/root-kms-config", url: "/root-kms-config",
@@ -259,8 +211,7 @@ export const registerAdminRouter = async (server: FastifyZodProvider) => {
name: z.string(), name: z.string(),
enabled: z.boolean() enabled: z.boolean()
}) })
.array(), .array()
keyExported: z.boolean()
}) })
} }
}, },
+2 -29
View File
@@ -11,7 +11,7 @@ import {
} from "@app/ee/services/external-kms/providers/model"; } from "@app/ee/services/external-kms/providers/model";
import { KeyStorePrefixes, TKeyStoreFactory } from "@app/keystore/keystore"; import { KeyStorePrefixes, TKeyStoreFactory } from "@app/keystore/keystore";
import { getConfig } from "@app/lib/config/env"; import { getConfig } from "@app/lib/config/env";
import { randomSecureBytes, shamirsService } from "@app/lib/crypto"; import { randomSecureBytes } from "@app/lib/crypto";
import { symmetricCipherService, SymmetricEncryption } from "@app/lib/crypto/cipher"; import { symmetricCipherService, SymmetricEncryption } from "@app/lib/crypto/cipher";
import { generateHash } from "@app/lib/crypto/encryption"; import { generateHash } from "@app/lib/crypto/encryption";
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors"; import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
@@ -667,31 +667,6 @@ export const kmsServiceFactory = ({
throw new Error(`Invalid root key encryption strategy: ${strategy}`); throw new Error(`Invalid root key encryption strategy: ${strategy}`);
}; };
const exportRootEncryptionKeyParts = () => {
if (!ROOT_ENCRYPTION_KEY) {
throw new Error("Root encryption key not set");
}
const parts = shamirsService().share(ROOT_ENCRYPTION_KEY, 8, 4);
return parts;
};
const importRootEncryptionKey = async (parts: string[]) => {
const decryptedRootKey = shamirsService().combine(parts);
const encryptedRootKey = symmetricCipherService(SymmetricEncryption.AES_GCM_256).encrypt(
decryptedRootKey,
$getBasicEncryptionKey()
);
await kmsRootConfigDAL.updateById(KMS_ROOT_CONFIG_UUID, {
encryptedRootKey,
encryptionStrategy: RootKeyEncryptionStrategy.Basic
});
ROOT_ENCRYPTION_KEY = decryptedRootKey;
};
// by keeping the decrypted data key in inner scope // by keeping the decrypted data key in inner scope
// none of the entities outside can interact directly or expose the data key // none of the entities outside can interact directly or expose the data key
// NOTICE: If changing here update migrations/utils/kms // NOTICE: If changing here update migrations/utils/kms
@@ -972,8 +947,6 @@ export const kmsServiceFactory = ({
getProjectKeyBackup, getProjectKeyBackup,
loadProjectKeyBackup, loadProjectKeyBackup,
getKmsById, getKmsById,
createCipherPairWithDataKey, createCipherPairWithDataKey
exportRootEncryptionKeyParts,
importRootEncryptionKey
}; };
}; };
@@ -23,14 +23,7 @@ type TSuperAdminServiceFactoryDep = {
serverCfgDAL: TSuperAdminDALFactory; serverCfgDAL: TSuperAdminDALFactory;
userDAL: TUserDALFactory; userDAL: TUserDALFactory;
authService: Pick<TAuthLoginFactory, "generateUserTokens">; authService: Pick<TAuthLoginFactory, "generateUserTokens">;
kmsService: Pick< kmsService: Pick<TKmsServiceFactory, "encryptWithRootKey" | "decryptWithRootKey" | "updateEncryptionStrategy">;
TKmsServiceFactory,
| "encryptWithRootKey"
| "decryptWithRootKey"
| "exportRootEncryptionKeyParts"
| "importRootEncryptionKey"
| "updateEncryptionStrategy"
>;
kmsRootConfigDAL: TKmsRootConfigDALFactory; kmsRootConfigDAL: TKmsRootConfigDALFactory;
orgService: Pick<TOrgServiceFactory, "createOrganization">; orgService: Pick<TOrgServiceFactory, "createOrganization">;
keyStore: Pick<TKeyStoreFactory, "getItem" | "setItemWithExpiry" | "deleteItem">; keyStore: Pick<TKeyStoreFactory, "getItem" | "setItemWithExpiry" | "deleteItem">;
@@ -162,35 +155,6 @@ export const superAdminServiceFactory = ({
return updatedServerCfg; return updatedServerCfg;
}; };
const exportPlainKmsKey = async () => {
const kmsRootConfig = await kmsRootConfigDAL.findById(KMS_ROOT_CONFIG_UUID);
if (!kmsRootConfig) {
throw new NotFoundError({ name: "KmsRootConfig", message: "KMS root configuration not found" });
}
if (kmsRootConfig.exported) {
throw new BadRequestError({ name: "KmsRootConfig", message: "KMS root configuration already exported" });
}
await kmsRootConfigDAL.updateById(KMS_ROOT_CONFIG_UUID, { exported: true });
return kmsService.exportRootEncryptionKeyParts();
};
const importPlainKmsKey = async (secretParts: string[]) => {
const kmsRootConfig = await kmsRootConfigDAL.findById(KMS_ROOT_CONFIG_UUID);
if (!kmsRootConfig) {
throw new NotFoundError({ name: "KmsRootConfig", message: "KMS root configuration not found" });
}
if (!kmsRootConfig.exported) {
throw new BadRequestError({ name: "KmsRootConfig", message: "KMS root configuration was never exported" });
}
await kmsService.importRootEncryptionKey(secretParts);
};
const adminSignUp = async ({ const adminSignUp = async ({
lastName, lastName,
firstName, firstName,
@@ -361,12 +325,17 @@ export const superAdminServiceFactory = ({
} }
return { return {
strategies: enabledStrategies, strategies: enabledStrategies
keyExported: kmsRootCfg.exported
}; };
}; };
const updateRootEncryptionStrategy = async (strategy: RootKeyEncryptionStrategy) => { const updateRootEncryptionStrategy = async (strategy: RootKeyEncryptionStrategy) => {
if (!licenseService.onPremFeatures.hsm) {
throw new BadRequestError({
message: "Failed to update encryption strategy due to plan restriction. Upgrade to Infisical's Enterprise plan."
});
}
const configuredStrategies = await getConfiguredEncryptionStrategies(); const configuredStrategies = await getConfiguredEncryptionStrategies();
const foundStrategy = configuredStrategies.strategies.find((s) => s.strategy === strategy); const foundStrategy = configuredStrategies.strategies.find((s) => s.strategy === strategy);
@@ -390,8 +359,6 @@ export const superAdminServiceFactory = ({
deleteUser, deleteUser,
getAdminSlackConfig, getAdminSlackConfig,
updateRootEncryptionStrategy, updateRootEncryptionStrategy,
getConfiguredEncryptionStrategies, getConfiguredEncryptionStrategies
exportPlainKmsKey,
importPlainKmsKey
}; };
}; };
-37
View File
@@ -525,40 +525,3 @@ func CallUpdateRawSecretsV3(httpClient *resty.Client, request UpdateRawSecretByN
return nil return nil
} }
func CallExportKmsRootEncryptionKey(httpClient *resty.Client) (ExportKmsRootKeyResponse, error) {
var exportKmsKeyResponse ExportKmsRootKeyResponse
response, err := httpClient.
R().
SetResult(&exportKmsKeyResponse).
SetHeader("User-Agent", USER_AGENT).
Post(fmt.Sprintf("%v/v1/admin/kms-export", config.INFISICAL_URL))
if err != nil {
return ExportKmsRootKeyResponse{}, fmt.Errorf("CallSuperAdminExportKmsKey: Unable to complete api request [err=%w]", err)
}
if response.IsError() {
return ExportKmsRootKeyResponse{}, fmt.Errorf("CallSuperAdminExportKmsKey: Unsuccessful response [%v %v] [status-code=%v] [response=%v]", response.Request.Method, response.Request.URL, response.StatusCode(), response.String())
}
return exportKmsKeyResponse, nil
}
func CallImportKmsRootEncryptionKey(httpClient *resty.Client, request ImportKmsRootKeyRequest) error {
response, err := httpClient.
R().
SetHeader("User-Agent", USER_AGENT).
SetBody(request).
Post(fmt.Sprintf("%v/v1/admin/kms-import", config.INFISICAL_URL))
if err != nil {
return fmt.Errorf("CallSuperAdminImportKmsKey: Unable to complete api request [err=%w]", err)
}
if response.IsError() {
return fmt.Errorf("CallSuperAdminImportKmsKey: Unsuccessful response [%v %v] [status-code=%v] [response=%v]", response.Request.Method, response.Request.URL, response.StatusCode(), response.String())
}
return nil
}
-8
View File
@@ -617,11 +617,3 @@ type GetRawSecretV3ByNameResponse struct {
} `json:"secret"` } `json:"secret"`
ETag string ETag string
} }
type ExportKmsRootKeyResponse struct {
SecretParts []string `json:"secretParts"`
}
type ImportKmsRootKeyRequest struct {
SecretParts []string `json:"secretParts"`
}
-128
View File
@@ -1,128 +0,0 @@
/*
Copyright (c) 2023 Infisical Inc.
*/
package cmd
import (
"fmt"
"strings"
"time"
"github.com/Infisical/infisical-merge/packages/api"
"github.com/Infisical/infisical-merge/packages/util"
"github.com/fatih/color"
"github.com/go-resty/resty/v2"
"github.com/spf13/cobra"
)
var kmsCmd = &cobra.Command{
Use: "kms",
Short: "Manage your Infisical KMS encryption keys",
DisableFlagsInUseLine: true,
Example: "infisical kms",
Args: cobra.ExactArgs(0),
PreRun: func(cmd *cobra.Command, args []string) {
util.RequireLogin()
},
Run: func(cmd *cobra.Command, args []string) {
},
}
// exportCmd represents the export command
var exportKeyCmd = &cobra.Command{
Use: "export",
Short: "Used to export your Infisical root encryption key parts, to be used for recovery (infisical import-key [...parts])",
DisableFlagsInUseLine: true,
Example: "infisical kms export",
Args: cobra.NoArgs,
Run: func(cmd *cobra.Command, args []string) {
loggedInDetails, err := util.GetCurrentLoggedInUserDetails()
if err != nil {
util.HandleError(err)
}
if !loggedInDetails.IsUserLoggedIn || loggedInDetails.LoginExpired {
util.HandleError(fmt.Errorf("You must be logged in to run this command"))
}
httpClient := resty.New()
httpClient.SetAuthToken(loggedInDetails.UserCredentials.JTWToken).
SetHeader("Accept", "application/json")
res, err := api.CallExportKmsRootEncryptionKey(httpClient)
if err != nil {
if strings.Contains(err.Error(), "configuration already exported") {
util.HandleError(fmt.Errorf("This KMS encryption key has already been exported. You can only export the decryption key once."))
} else {
util.HandleError(err)
}
}
boldGreen := color.New(color.FgGreen).Add(color.Bold)
time.Sleep(time.Second * 1)
boldGreen.Printf(">>>> Successfully exported KMS encryption key\n\n")
plainBold := color.New(color.Bold)
for i, part := range res.SecretParts {
plainBold.Printf("Part %d: %v\n", i+1, part)
}
boldYellow := color.New(color.FgYellow).Add(color.Bold)
boldYellow.Printf("\nPlease store these parts in a secure location. You will need them to recover your KMS encryption key.\nYou will not be able to export these credentials again in the future.\n\n")
},
}
var importKeyCmd = &cobra.Command{
Use: "import",
Short: "Used to import your Infisical root encryption key parts, to be used for recovery (infisical import-key [...parts])",
DisableFlagsInUseLine: true,
Example: "infisical kms import",
Args: cobra.MinimumNArgs(6),
Run: func(cmd *cobra.Command, args []string) {
loggedInDetails, err := util.GetCurrentLoggedInUserDetails()
if err != nil {
util.HandleError(err)
}
if !loggedInDetails.IsUserLoggedIn || loggedInDetails.LoginExpired {
util.HandleError(fmt.Errorf("You must be logged in to run this command"))
}
httpClient := resty.New()
httpClient.SetAuthToken(loggedInDetails.UserCredentials.JTWToken).
SetHeader("Accept", "application/json")
err = api.CallImportKmsRootEncryptionKey(httpClient, api.ImportKmsRootKeyRequest{
SecretParts: args,
})
if err != nil {
if strings.Contains(err.Error(), "configuration was never exported") {
util.HandleError(fmt.Errorf("This KMS encryption key has not been exported yet. You must export the key first before you can import it."))
} else {
util.HandleError(err)
}
}
boldGreen := color.New(color.FgGreen).Add(color.Bold)
time.Sleep(time.Second * 1)
boldGreen.Printf(">>>> Successfully imported KMS encryption key\n\n")
boldYellow := color.New(color.FgYellow).Add(color.Bold)
boldYellow.Printf("Important: Make sure to set the `ROOT_KEY_ENCRYPTION_STRATEGY` environment variable to `BASIC` on your Infisical instance.\nNot doing this will likely result in having to re-import the key on the next instance restart.\n\n")
},
}
func init() {
kmsCmd.AddCommand(exportKeyCmd)
kmsCmd.AddCommand(importKeyCmd)
rootCmd.AddCommand(kmsCmd)
}
-2
View File
@@ -1,8 +1,6 @@
export { export {
useAdminDeleteUser, useAdminDeleteUser,
useCreateAdminUser, useCreateAdminUser,
useExportServerDecryptionKey,
useImportServerDecryptionKey,
useUpdateAdminSlackConfig, useUpdateAdminSlackConfig,
useUpdateServerConfig, useUpdateServerConfig,
useUpdateServerEncryptionStrategy useUpdateServerEncryptionStrategy
-21
View File
@@ -98,24 +98,3 @@ export const useUpdateServerEncryptionStrategy = () => {
} }
}); });
}; };
export const useExportServerDecryptionKey = () => {
return useMutation({
mutationFn: async () => {
const { data } = await apiRequest.post<{ secretParts: string[] }>("/api/v1/admin/kms-export");
return data.secretParts;
}
});
};
export const useImportServerDecryptionKey = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async (secretParts: string[]) => {
await apiRequest.post("/api/v1/admin/kms-import", { secretParts });
},
onSuccess: () => {
queryClient.invalidateQueries(adminQueryKeys.serverConfig());
}
});
};
-1
View File
@@ -61,7 +61,6 @@ export type TGetServerRootKmsEncryptionDetails = {
enabled: boolean; enabled: boolean;
name: string; name: string;
}[]; }[];
keyExported: boolean;
}; };
export enum RootKeyEncryptionStrategy { export enum RootKeyEncryptionStrategy {
@@ -23,6 +23,7 @@ export type SubscriptionPlan = {
workspacesUsed: number; workspacesUsed: number;
environmentLimit: number; environmentLimit: number;
samlSSO: boolean; samlSSO: boolean;
hsm: boolean;
oidcSSO: boolean; oidcSSO: boolean;
scim: boolean; scim: boolean;
ldap: boolean; ldap: boolean;
@@ -1,12 +1,11 @@
import { useCallback } from "react"; import { useCallback } from "react";
import { Controller, useForm } from "react-hook-form"; import { Controller, useForm } from "react-hook-form";
import { faExclamationCircle } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod"; import { z } from "zod";
import { createNotification } from "@app/components/notifications"; import { createNotification } from "@app/components/notifications";
import { Button, FormControl, Modal, Select, SelectItem, Tooltip } from "@app/components/v2"; import { Button, FormControl, Select, SelectItem, UpgradePlanModal } from "@app/components/v2";
import { useSubscription } from "@app/context";
import { usePopUp } from "@app/hooks"; import { usePopUp } from "@app/hooks";
import { useUpdateServerEncryptionStrategy } from "@app/hooks/api"; import { useUpdateServerEncryptionStrategy } from "@app/hooks/api";
import { import {
@@ -14,9 +13,6 @@ import {
TGetServerRootKmsEncryptionDetails TGetServerRootKmsEncryptionDetails
} from "@app/hooks/api/admin/types"; } from "@app/hooks/api/admin/types";
import { ExportRootKmsKeyModalContent } from "./components/ExportRootKmsKeyModalContent";
import { RestoreRootKmsKeyModalContent } from "./components/RestoreRootKmsKeyModalContent";
const formSchema = z.object({ const formSchema = z.object({
encryptionStrategy: z.nativeEnum(RootKeyEncryptionStrategy) encryptionStrategy: z.nativeEnum(RootKeyEncryptionStrategy)
}); });
@@ -29,10 +25,9 @@ type Props = {
export const EncryptionPanel = ({ rootKmsDetails }: Props) => { export const EncryptionPanel = ({ rootKmsDetails }: Props) => {
const { mutateAsync: updateEncryptionStrategy } = useUpdateServerEncryptionStrategy(); const { mutateAsync: updateEncryptionStrategy } = useUpdateServerEncryptionStrategy();
const { handlePopUpToggle, handlePopUpOpen, popUp } = usePopUp([ const { subscription } = useSubscription();
"exportKey",
"restoreKey" const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp(["upgradePlan"] as const);
] as const);
const { const {
control, control,
@@ -48,16 +43,18 @@ export const EncryptionPanel = ({ rootKmsDetails }: Props) => {
}); });
const onSubmit = useCallback(async (formData: TForm) => { const onSubmit = useCallback(async (formData: TForm) => {
if (!subscription) return;
if (!subscription.hsm) {
handlePopUpOpen("upgradePlan", {
description: "Hardware Security Module's (HSM's), are only available on Enterprise plans."
});
return;
}
try { try {
await updateEncryptionStrategy(formData.encryptionStrategy); await updateEncryptionStrategy(formData.encryptionStrategy);
if (
!rootKmsDetails.keyExported &&
formData.encryptionStrategy !== RootKeyEncryptionStrategy.Basic
) {
handlePopUpOpen("exportKey");
}
createNotification({ createNotification({
type: "success", type: "success",
text: "Encryption strategy updated successfully" text: "Encryption strategy updated successfully"
@@ -81,50 +78,6 @@ export const EncryptionPanel = ({ rootKmsDetails }: Props) => {
<div className="mb-2 text-xl font-semibold text-mineshaft-100"> <div className="mb-2 text-xl font-semibold text-mineshaft-100">
KMS Encryption Strategy KMS Encryption Strategy
</div> </div>
<Tooltip
content={
<div>
{!rootKmsDetails.keyExported && (
<div className="mb-2 text-sm">
<FontAwesomeIcon icon={faExclamationCircle} className="mr-1 text-red-500" />
You have not exported the KMS root encryption key. Switch to HSM encryption or
run the{" "}
<code>
<span className="mt-2 rounded-md bg-mineshaft-600 p-1 text-xs text-primary-500">
infisical kms export
</span>
</code>{" "}
CLI command to export the key parts.
</div>
)}
<br />
If you experience issues with accessing projects while not using Regular
Encryption (default), you can restore the KMS root encryption key by using your
exported key parts.
<br /> <br />
If you do not have the exported key parts, you can export them by using the CLI
command
<br />
<code>
<span className="mt-2 rounded-md bg-mineshaft-600 p-1 text-xs text-primary-500">
infisical kms export
</span>
</code>
. <br />
<br />
<span className="font-bold">
Please keep in mind that you can only export the key parts once.
</span>
</div>
}
>
<Button
isDisabled={!rootKmsDetails.keyExported}
onClick={() => handlePopUpToggle("restoreKey", true)}
>
Restore Root KMS Encryption Key
</Button>
</Tooltip>
</div> </div>
<div className="mb-4 max-w-sm text-sm text-mineshaft-400"> <div className="mb-4 max-w-sm text-sm text-mineshaft-400">
Select which type of encryption strategy you want to use for your KMS root key. HSM is Select which type of encryption strategy you want to use for your KMS root key. HSM is
@@ -163,20 +116,11 @@ export const EncryptionPanel = ({ rootKmsDetails }: Props) => {
Save Save
</Button> </Button>
</form> </form>
<UpgradePlanModal
<Modal isOpen={popUp.upgradePlan.isOpen}
isOpen={popUp.exportKey.isOpen} onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)}
onOpenChange={(state) => handlePopUpToggle("exportKey", state)} text={(popUp.upgradePlan?.data as { description: string })?.description}
> />
<ExportRootKmsKeyModalContent handlePopUpToggle={handlePopUpToggle} />
</Modal>
<Modal
isOpen={popUp.restoreKey.isOpen}
onOpenChange={(state) => handlePopUpToggle("restoreKey", state)}
>
<RestoreRootKmsKeyModalContent handlePopUpToggle={handlePopUpToggle} />
</Modal>
</> </>
); );
}; };
@@ -1,53 +0,0 @@
import { useCallback, useState } from "react";
import { Button, ModalContent } from "@app/components/v2";
import { useExportServerDecryptionKey } from "@app/hooks/api";
import { useFileDownload } from "@app/hooks/useFileDownload";
import { UsePopUpState } from "@app/hooks/usePopUp";
type Props = {
handlePopUpToggle: (popUpName: keyof UsePopUpState<["exportKey"]>, state?: boolean) => void;
};
export const ExportRootKmsKeyModalContent = ({ handlePopUpToggle }: Props) => {
const { mutateAsync: exportKey, isLoading } = useExportServerDecryptionKey();
const downloadFile = useFileDownload();
const [downloaded, setDownloaded] = useState(false);
const onExport = useCallback(async () => {
const keyParts = await exportKey();
downloadFile(keyParts.join("\n\n"), "infisical-encryption-key-parts.txt");
setDownloaded(true);
}, []);
return (
<ModalContent
title="Export Root KMS Encryption Key"
subTitle="We highly recommend exporting the KMS root encryption key and storing it in a secure location. Incase of a disaster, you can use our CLI to recover your projects with zero loss."
>
<div className="flex w-full justify-end">
{!downloaded ? (
<>
<Button
variant="plain"
colorSchema="secondary"
onClick={() => handlePopUpToggle("exportKey", false)}
>
Close
</Button>
<Button isLoading={isLoading} className="ml-2" onClick={onExport}>
Download Key
</Button>
</>
) : (
<div className="flex max-w-fit flex-col overflow-clip break-words px-2 text-sm font-normal text-gray-400">
The key parts have been downloaded. Please store them in a safe place. You will need
these keys incase you need to recovery the KMS root encryption key. Please consult our
documentation for further instructions.
</div>
)}
</div>
</ModalContent>
);
};
@@ -1,98 +0,0 @@
import { useMemo } from "react";
import { Controller, useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
import { createNotification } from "@app/components/notifications";
import { Button, FormControl, Input, ModalContent } from "@app/components/v2";
import { useImportServerDecryptionKey } from "@app/hooks/api";
import { UsePopUpState } from "@app/hooks/usePopUp";
type Props = {
handlePopUpToggle: (popUpName: keyof UsePopUpState<["restoreKey"]>, state?: boolean) => void;
};
const formSchema = z.object({
keyParts: z
.array(z.string())
.refine((data) => data.length === 4 && data.every((part) => part.length > 0), {
message: "Enter at least 4 key parts in order to restore the KMS root decryption key."
})
});
type TForm = z.infer<typeof formSchema>;
export const RestoreRootKmsKeyModalContent = ({ handlePopUpToggle }: Props) => {
const { mutateAsync: importKmsRootKey } = useImportServerDecryptionKey();
const {
control,
handleSubmit,
watch,
formState: { isSubmitting, errors, isLoading, isValid }
} = useForm<TForm>({
resolver: zodResolver(formSchema),
values: {
keyParts: ["", "", "", ""]
}
});
const keyParts = useMemo(() => watch("keyParts"), []);
return (
<ModalContent
title="Export Root KMS Encryption Key"
subTitle="Recover the KMS root encryption key by entering the key parts. You can recover the key if you have 4 out of 8 key parts."
footerContent={
<div className="flex w-full justify-end">
<Button
variant="plain"
colorSchema="secondary"
onClick={() => handlePopUpToggle("restoreKey", false)}
>
Close
</Button>
<Button
isDisabled={!!errors.keyParts || !isValid}
isLoading={isSubmitting || isLoading}
className="ml-2"
onClick={handleSubmit(async (data) => {
await importKmsRootKey(data.keyParts);
createNotification({
type: "success",
title: "Successfully restored KMS root key",
text: "The KMS root key has been successfully restored."
});
handlePopUpToggle("restoreKey", false);
})}
>
Restore Key
</Button>
</div>
}
>
<form>
<div className="flex w-full flex-col justify-end">
{keyParts.map((_, index) => (
<Controller
key={`key-part-${index + 1}`}
name={`keyParts.${index}`}
control={control}
render={({ field }) => (
<div>
<FormControl label={`Key Part ${index + 1}`}>
<Input {...field} placeholder={`Enter key part ${index + 1}`} />
</FormControl>
</div>
)}
/>
))}
{errors.keyParts && (
<div className="mt-2 text-sm font-normal text-red-500">{errors.keyParts.message}</div>
)}
</div>
</form>
</ModalContent>
);
};