mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-07 01:27:41 +00:00
fix: removed recovery
This commit is contained in:
Generated
-7
@@ -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",
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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";
|
||||||
|
|||||||
@@ -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 };
|
|
||||||
};
|
|
||||||
@@ -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()
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -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"`
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -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)
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,8 +1,6 @@
|
|||||||
export {
|
export {
|
||||||
useAdminDeleteUser,
|
useAdminDeleteUser,
|
||||||
useCreateAdminUser,
|
useCreateAdminUser,
|
||||||
useExportServerDecryptionKey,
|
|
||||||
useImportServerDecryptionKey,
|
|
||||||
useUpdateAdminSlackConfig,
|
useUpdateAdminSlackConfig,
|
||||||
useUpdateServerConfig,
|
useUpdateServerConfig,
|
||||||
useUpdateServerEncryptionStrategy
|
useUpdateServerEncryptionStrategy
|
||||||
|
|||||||
@@ -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());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -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>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
Reference in New Issue
Block a user