mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-07 05:27:48 +00:00
Merge pull request #4976 from Infisical/chore/external-kms-api-refactor
chore: external-kms API refactor
This commit is contained in:
@@ -4,15 +4,10 @@ import { ExternalKmsSchema, KmsKeysSchema } from "@app/db/schemas";
|
|||||||
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
||||||
import {
|
import {
|
||||||
ExternalKmsAwsSchema,
|
ExternalKmsAwsSchema,
|
||||||
ExternalKmsGcpCredentialSchema,
|
|
||||||
ExternalKmsGcpSchema,
|
ExternalKmsGcpSchema,
|
||||||
ExternalKmsInputSchema,
|
ExternalKmsInputSchema,
|
||||||
ExternalKmsInputUpdateSchema,
|
ExternalKmsInputUpdateSchema
|
||||||
KmsGcpKeyFetchAuthType,
|
|
||||||
KmsProviders,
|
|
||||||
TExternalKmsGcpCredentialSchema
|
|
||||||
} from "@app/ee/services/external-kms/providers/model";
|
} from "@app/ee/services/external-kms/providers/model";
|
||||||
import { NotFoundError } from "@app/lib/errors";
|
|
||||||
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
import { AuthMode } from "@app/services/auth/auth-type";
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
@@ -293,67 +288,4 @@ export const registerExternalKmsRouter = async (server: FastifyZodProvider) => {
|
|||||||
return { externalKms };
|
return { externalKms };
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
server.route({
|
|
||||||
method: "POST",
|
|
||||||
url: "/gcp/keys",
|
|
||||||
config: {
|
|
||||||
rateLimit: writeLimit
|
|
||||||
},
|
|
||||||
schema: {
|
|
||||||
body: z.discriminatedUnion("authMethod", [
|
|
||||||
z.object({
|
|
||||||
authMethod: z.literal(KmsGcpKeyFetchAuthType.Credential),
|
|
||||||
region: z.string().trim().min(1),
|
|
||||||
credential: ExternalKmsGcpCredentialSchema
|
|
||||||
}),
|
|
||||||
z.object({
|
|
||||||
authMethod: z.literal(KmsGcpKeyFetchAuthType.Kms),
|
|
||||||
region: z.string().trim().min(1),
|
|
||||||
kmsId: z.string().trim().min(1)
|
|
||||||
})
|
|
||||||
]),
|
|
||||||
response: {
|
|
||||||
200: z.object({
|
|
||||||
keys: z.string().array()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
|
||||||
handler: async (req) => {
|
|
||||||
const { region, authMethod } = req.body;
|
|
||||||
let credentialJson: TExternalKmsGcpCredentialSchema | undefined;
|
|
||||||
|
|
||||||
if (authMethod === KmsGcpKeyFetchAuthType.Credential) {
|
|
||||||
credentialJson = req.body.credential;
|
|
||||||
} else if (authMethod === KmsGcpKeyFetchAuthType.Kms) {
|
|
||||||
const externalKms = await server.services.externalKms.findById({
|
|
||||||
actor: req.permission.type,
|
|
||||||
actorId: req.permission.id,
|
|
||||||
actorAuthMethod: req.permission.authMethod,
|
|
||||||
actorOrgId: req.permission.orgId,
|
|
||||||
id: req.body.kmsId
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!externalKms || externalKms.external.provider !== KmsProviders.Gcp) {
|
|
||||||
throw new NotFoundError({ message: "KMS not found or not of type GCP" });
|
|
||||||
}
|
|
||||||
|
|
||||||
credentialJson = externalKms.external.providerInput.credential as TExternalKmsGcpCredentialSchema;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!credentialJson) {
|
|
||||||
throw new NotFoundError({
|
|
||||||
message: "Something went wrong while fetching the GCP credential, please check inputs and try again"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const results = await server.services.externalKms.fetchGcpKeys({
|
|
||||||
credential: credentialJson,
|
|
||||||
gcpRegion: region
|
|
||||||
});
|
|
||||||
|
|
||||||
return results;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import { ExternalKmsAwsSchema, KmsProviders } from "@app/ee/services/external-kms/providers/model";
|
||||||
|
|
||||||
|
import { registerExternalKmsEndpoints } from "./external-kms-endpoints";
|
||||||
|
|
||||||
|
export const registerAwsKmsRouter = async (server: FastifyZodProvider) => {
|
||||||
|
registerExternalKmsEndpoints({
|
||||||
|
server,
|
||||||
|
provider: KmsProviders.Aws,
|
||||||
|
createSchema: ExternalKmsAwsSchema,
|
||||||
|
updateSchema: ExternalKmsAwsSchema.partial()
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -0,0 +1,288 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { ExternalKmsSchema, KmsKeysSchema } from "@app/db/schemas";
|
||||||
|
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
||||||
|
import {
|
||||||
|
KmsProviders,
|
||||||
|
SanitizedExternalKmsAwsSchema,
|
||||||
|
SanitizedExternalKmsGcpSchema,
|
||||||
|
TExternalKmsInputSchema,
|
||||||
|
TExternalKmsInputUpdateSchema
|
||||||
|
} from "@app/ee/services/external-kms/providers/model";
|
||||||
|
import { crypto } from "@app/lib/crypto/cryptography";
|
||||||
|
import { BadRequestError } from "@app/lib/errors";
|
||||||
|
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||||
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
|
const sanitizedExternalSchema = KmsKeysSchema.extend({
|
||||||
|
externalKms: ExternalKmsSchema.pick({
|
||||||
|
id: true,
|
||||||
|
status: true,
|
||||||
|
statusDetails: true,
|
||||||
|
provider: true
|
||||||
|
}).extend({
|
||||||
|
configuration: z.union([SanitizedExternalKmsAwsSchema, SanitizedExternalKmsGcpSchema]),
|
||||||
|
credentialsHash: z.string().optional()
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
export const registerExternalKmsEndpoints = <
|
||||||
|
T extends { type: KmsProviders; inputs: TExternalKmsInputSchema["inputs"] }
|
||||||
|
>({
|
||||||
|
server,
|
||||||
|
provider,
|
||||||
|
createSchema,
|
||||||
|
updateSchema
|
||||||
|
}: {
|
||||||
|
server: FastifyZodProvider;
|
||||||
|
provider: T["type"];
|
||||||
|
createSchema: z.ZodType<T["inputs"]>;
|
||||||
|
updateSchema: z.ZodType<Partial<T["inputs"]>>;
|
||||||
|
}) => {
|
||||||
|
server.route({
|
||||||
|
method: "GET",
|
||||||
|
url: "/:id",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
params: z.object({
|
||||||
|
id: z.string().trim().min(1)
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: sanitizedExternalSchema
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const externalKms = await server.services.externalKms.findById({
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
id: req.params.id
|
||||||
|
});
|
||||||
|
|
||||||
|
// Validate that the KMS is of the expected provider type
|
||||||
|
if (externalKms.external.provider !== provider) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: `KMS provider mismatch. Expected ${provider}, got ${externalKms.external.provider}`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await server.services.auditLog.createAuditLog({
|
||||||
|
...req.auditLogInfo,
|
||||||
|
orgId: req.permission.orgId,
|
||||||
|
event: {
|
||||||
|
type: EventType.GET_KMS,
|
||||||
|
metadata: {
|
||||||
|
kmsId: externalKms.id,
|
||||||
|
name: externalKms.name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const {
|
||||||
|
external: { providerInput: configuration, ...externalKmsData },
|
||||||
|
...rest
|
||||||
|
} = externalKms;
|
||||||
|
|
||||||
|
const credentialsHash = crypto.nativeCrypto
|
||||||
|
.createHash("sha256")
|
||||||
|
.update(externalKmsData.encryptedProviderInputs)
|
||||||
|
.digest("hex");
|
||||||
|
return { ...rest, externalKms: { ...externalKmsData, configuration, credentialsHash } };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "POST",
|
||||||
|
url: "/",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
body: z.object({
|
||||||
|
name: z.string().min(1).trim().toLowerCase(),
|
||||||
|
description: z.string().trim().optional(),
|
||||||
|
configuration: createSchema
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: sanitizedExternalSchema
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const { name, description, configuration } = req.body as {
|
||||||
|
name: string;
|
||||||
|
description?: string;
|
||||||
|
configuration: T["inputs"];
|
||||||
|
};
|
||||||
|
|
||||||
|
const providerInput = {
|
||||||
|
type: provider,
|
||||||
|
inputs: configuration
|
||||||
|
} as TExternalKmsInputSchema;
|
||||||
|
|
||||||
|
const externalKms = await server.services.externalKms.create({
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
name,
|
||||||
|
provider: providerInput,
|
||||||
|
description
|
||||||
|
});
|
||||||
|
|
||||||
|
await server.services.auditLog.createAuditLog({
|
||||||
|
...req.auditLogInfo,
|
||||||
|
orgId: req.permission.orgId,
|
||||||
|
event: {
|
||||||
|
type: EventType.CREATE_KMS,
|
||||||
|
metadata: {
|
||||||
|
kmsId: externalKms.id,
|
||||||
|
provider,
|
||||||
|
name,
|
||||||
|
description
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const {
|
||||||
|
external: { providerInput: externalKmsConfiguration, ...externalKmsData },
|
||||||
|
...rest
|
||||||
|
} = externalKms;
|
||||||
|
const credentialsHash = crypto.nativeCrypto
|
||||||
|
.createHash("sha256")
|
||||||
|
.update(externalKmsData.encryptedProviderInputs)
|
||||||
|
.digest("hex");
|
||||||
|
return { ...rest, externalKms: { ...externalKmsData, configuration: externalKmsConfiguration, credentialsHash } };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "PATCH",
|
||||||
|
url: "/:id",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
params: z.object({
|
||||||
|
id: z.string().trim().min(1)
|
||||||
|
}),
|
||||||
|
body: z.object({
|
||||||
|
name: z.string().min(1).trim().toLowerCase().optional(),
|
||||||
|
description: z.string().trim().optional(),
|
||||||
|
configuration: updateSchema.optional()
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: sanitizedExternalSchema
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const { name, description, configuration } = req.body as {
|
||||||
|
name?: string;
|
||||||
|
description?: string;
|
||||||
|
configuration: Partial<T["inputs"]>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const providerInput = {
|
||||||
|
type: provider,
|
||||||
|
inputs: configuration
|
||||||
|
} as TExternalKmsInputUpdateSchema;
|
||||||
|
|
||||||
|
const externalKms = await server.services.externalKms.updateById({
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
name,
|
||||||
|
provider: providerInput,
|
||||||
|
description,
|
||||||
|
id: req.params.id
|
||||||
|
});
|
||||||
|
|
||||||
|
await server.services.auditLog.createAuditLog({
|
||||||
|
...req.auditLogInfo,
|
||||||
|
orgId: req.permission.orgId,
|
||||||
|
event: {
|
||||||
|
type: EventType.UPDATE_KMS,
|
||||||
|
metadata: {
|
||||||
|
kmsId: externalKms.id,
|
||||||
|
provider,
|
||||||
|
name,
|
||||||
|
description
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const {
|
||||||
|
external: { providerInput: externalKmsConfiguration, ...externalKmsData },
|
||||||
|
...rest
|
||||||
|
} = externalKms;
|
||||||
|
const credentialsHash = crypto.nativeCrypto
|
||||||
|
.createHash("sha256")
|
||||||
|
.update(externalKmsData.encryptedProviderInputs)
|
||||||
|
.digest("hex");
|
||||||
|
return { ...rest, externalKms: { ...externalKmsData, configuration: externalKmsConfiguration, credentialsHash } };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "DELETE",
|
||||||
|
url: "/:id",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
params: z.object({
|
||||||
|
id: z.string().trim().min(1)
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: sanitizedExternalSchema
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const externalKms = await server.services.externalKms.deleteById({
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
id: req.params.id
|
||||||
|
});
|
||||||
|
|
||||||
|
// Validate that the KMS is of the expected provider type
|
||||||
|
if (externalKms.external.provider !== provider) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: `KMS provider mismatch. Expected ${provider}, got ${externalKms.external.provider}`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await server.services.auditLog.createAuditLog({
|
||||||
|
...req.auditLogInfo,
|
||||||
|
orgId: req.permission.orgId,
|
||||||
|
event: {
|
||||||
|
type: EventType.DELETE_KMS,
|
||||||
|
metadata: {
|
||||||
|
kmsId: externalKms.id,
|
||||||
|
name: externalKms.name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const {
|
||||||
|
external: { providerInput: configuration, ...externalKmsData },
|
||||||
|
...rest
|
||||||
|
} = externalKms;
|
||||||
|
const credentialsHash = crypto.nativeCrypto
|
||||||
|
.createHash("sha256")
|
||||||
|
.update(externalKmsData.encryptedProviderInputs)
|
||||||
|
.digest("hex");
|
||||||
|
|
||||||
|
return { ...rest, externalKms: { ...externalKmsData, configuration, credentialsHash } };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import {
|
||||||
|
ExternalKmsGcpCredentialSchema,
|
||||||
|
ExternalKmsGcpSchema,
|
||||||
|
KmsGcpKeyFetchAuthType,
|
||||||
|
KmsProviders,
|
||||||
|
TExternalKmsGcpCredentialSchema
|
||||||
|
} from "@app/ee/services/external-kms/providers/model";
|
||||||
|
import { NotFoundError } from "@app/lib/errors";
|
||||||
|
import { writeLimit } from "@app/server/config/rateLimiter";
|
||||||
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
|
import { registerExternalKmsEndpoints } from "./external-kms-endpoints";
|
||||||
|
|
||||||
|
export const registerGcpKmsRouter = async (server: FastifyZodProvider) => {
|
||||||
|
registerExternalKmsEndpoints({
|
||||||
|
server,
|
||||||
|
provider: KmsProviders.Gcp,
|
||||||
|
createSchema: ExternalKmsGcpSchema,
|
||||||
|
updateSchema: ExternalKmsGcpSchema.partial()
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "POST",
|
||||||
|
url: "/keys",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
body: z.discriminatedUnion("authMethod", [
|
||||||
|
z.object({
|
||||||
|
authMethod: z.literal(KmsGcpKeyFetchAuthType.Credential),
|
||||||
|
region: z.string().trim().min(1),
|
||||||
|
credential: ExternalKmsGcpCredentialSchema
|
||||||
|
}),
|
||||||
|
z.object({
|
||||||
|
authMethod: z.literal(KmsGcpKeyFetchAuthType.Kms),
|
||||||
|
region: z.string().trim().min(1),
|
||||||
|
kmsId: z.string().trim().min(1)
|
||||||
|
})
|
||||||
|
]),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
keys: z.string().array()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const { region, authMethod } = req.body;
|
||||||
|
let credentialJson: TExternalKmsGcpCredentialSchema | undefined;
|
||||||
|
|
||||||
|
if (authMethod === KmsGcpKeyFetchAuthType.Credential && "credential" in req.body) {
|
||||||
|
credentialJson = req.body.credential;
|
||||||
|
} else if (authMethod === KmsGcpKeyFetchAuthType.Kms && "kmsId" in req.body) {
|
||||||
|
const externalKms = await server.services.externalKms.findById({
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
id: req.body.kmsId
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!externalKms || externalKms.external.provider !== KmsProviders.Gcp) {
|
||||||
|
throw new NotFoundError({ message: "KMS not found or not of type GCP" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const providerInput = externalKms.external.providerInput as { credential: TExternalKmsGcpCredentialSchema };
|
||||||
|
credentialJson = providerInput.credential;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!credentialJson) {
|
||||||
|
throw new NotFoundError({
|
||||||
|
message: "Something went wrong while fetching the GCP credential, please check inputs and try again"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const results = await server.services.externalKms.fetchGcpKeys({
|
||||||
|
credential: credentialJson,
|
||||||
|
gcpRegion: region
|
||||||
|
});
|
||||||
|
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import { KmsProviders } from "@app/ee/services/external-kms/providers/model";
|
||||||
|
|
||||||
|
import { registerAwsKmsRouter } from "./aws-kms-router";
|
||||||
|
import { registerGcpKmsRouter } from "./gcp-kms-router";
|
||||||
|
|
||||||
|
export const EXTERNAL_KMS_REGISTER_ROUTER_MAP: Record<KmsProviders, (server: FastifyZodProvider) => Promise<void>> = {
|
||||||
|
[KmsProviders.Aws]: registerAwsKmsRouter,
|
||||||
|
[KmsProviders.Gcp]: registerGcpKmsRouter
|
||||||
|
};
|
||||||
@@ -12,6 +12,8 @@ import { registerDynamicSecretLeaseRouter } from "./dynamic-secret-lease-router"
|
|||||||
import { registerKubernetesDynamicSecretLeaseRouter } from "./dynamic-secret-lease-routers/kubernetes-lease-router";
|
import { registerKubernetesDynamicSecretLeaseRouter } from "./dynamic-secret-lease-routers/kubernetes-lease-router";
|
||||||
import { registerDynamicSecretRouter } from "./dynamic-secret-router";
|
import { registerDynamicSecretRouter } from "./dynamic-secret-router";
|
||||||
import { registerExternalKmsRouter } from "./external-kms-router";
|
import { registerExternalKmsRouter } from "./external-kms-router";
|
||||||
|
|
||||||
|
import { EXTERNAL_KMS_REGISTER_ROUTER_MAP } from "./external-kms-routers";
|
||||||
import { registerGatewayRouter } from "./gateway-router";
|
import { registerGatewayRouter } from "./gateway-router";
|
||||||
import { registerGithubOrgSyncRouter } from "./github-org-sync-router";
|
import { registerGithubOrgSyncRouter } from "./github-org-sync-router";
|
||||||
import { registerGroupRouter } from "./group-router";
|
import { registerGroupRouter } from "./group-router";
|
||||||
@@ -162,9 +164,19 @@ export const registerV1EERoutes = async (server: FastifyZodProvider) => {
|
|||||||
{ prefix: "/additional-privilege" }
|
{ prefix: "/additional-privilege" }
|
||||||
);
|
);
|
||||||
|
|
||||||
await server.register(registerExternalKmsRouter, {
|
await server.register(
|
||||||
prefix: "/external-kms"
|
async (externalKmsRouter) => {
|
||||||
});
|
await externalKmsRouter.register(registerExternalKmsRouter);
|
||||||
|
|
||||||
|
// Provider-specific endpoints
|
||||||
|
await Promise.all(
|
||||||
|
Object.entries(EXTERNAL_KMS_REGISTER_ROUTER_MAP).map(([provider, router]) =>
|
||||||
|
externalKmsRouter.register(router, { prefix: `/${provider}` })
|
||||||
|
)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
{ prefix: "/external-kms" }
|
||||||
|
);
|
||||||
await server.register(registerIdentityTemplateRouter, { prefix: "/identity-templates" });
|
await server.register(registerIdentityTemplateRouter, { prefix: "/identity-templates" });
|
||||||
|
|
||||||
await server.register(registerProjectTemplateRouter, { prefix: "/project-templates" });
|
await server.register(registerProjectTemplateRouter, { prefix: "/project-templates" });
|
||||||
|
|||||||
@@ -24,7 +24,13 @@ import {
|
|||||||
} from "./external-kms-types";
|
} from "./external-kms-types";
|
||||||
import { AwsKmsProviderFactory } from "./providers/aws-kms";
|
import { AwsKmsProviderFactory } from "./providers/aws-kms";
|
||||||
import { GcpKmsProviderFactory } from "./providers/gcp-kms";
|
import { GcpKmsProviderFactory } from "./providers/gcp-kms";
|
||||||
import { ExternalKmsAwsSchema, ExternalKmsGcpSchema, KmsProviders, TExternalKmsGcpSchema } from "./providers/model";
|
import {
|
||||||
|
ExternalKmsAwsSchema,
|
||||||
|
ExternalKmsGcpSchema,
|
||||||
|
KmsProviders,
|
||||||
|
TExternalKmsAwsSchema,
|
||||||
|
TExternalKmsGcpSchema
|
||||||
|
} from "./providers/model";
|
||||||
|
|
||||||
type TExternalKmsServiceFactoryDep = {
|
type TExternalKmsServiceFactoryDep = {
|
||||||
externalKmsDAL: TExternalKmsDALFactory;
|
externalKmsDAL: TExternalKmsDALFactory;
|
||||||
@@ -72,6 +78,7 @@ export const externalKmsServiceFactory = ({
|
|||||||
const kmsName = name ? slugify(name) : slugify(alphaNumericNanoId(8).toLowerCase());
|
const kmsName = name ? slugify(name) : slugify(alphaNumericNanoId(8).toLowerCase());
|
||||||
|
|
||||||
let sanitizedProviderInput = "";
|
let sanitizedProviderInput = "";
|
||||||
|
let sanitizedProviderInputObject: TExternalKmsAwsSchema | TExternalKmsGcpSchema;
|
||||||
switch (provider.type) {
|
switch (provider.type) {
|
||||||
case KmsProviders.Aws:
|
case KmsProviders.Aws:
|
||||||
{
|
{
|
||||||
@@ -88,9 +95,18 @@ export const externalKmsServiceFactory = ({
|
|||||||
try {
|
try {
|
||||||
// if missing kms key this generate a new kms key id and returns new provider input
|
// if missing kms key this generate a new kms key id and returns new provider input
|
||||||
const newProviderInput = await externalKms.generateInputKmsKey();
|
const newProviderInput = await externalKms.generateInputKmsKey();
|
||||||
|
sanitizedProviderInputObject = newProviderInput;
|
||||||
sanitizedProviderInput = JSON.stringify(newProviderInput);
|
sanitizedProviderInput = JSON.stringify(newProviderInput);
|
||||||
|
|
||||||
await externalKms.validateConnection();
|
await externalKms.validateConnection();
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof BadRequestError) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: error instanceof Error ? `AWS error: ${error.message}` : "Failed to validate AWS connection"
|
||||||
|
});
|
||||||
} finally {
|
} finally {
|
||||||
await externalKms.cleanup();
|
await externalKms.cleanup();
|
||||||
}
|
}
|
||||||
@@ -101,7 +117,16 @@ export const externalKmsServiceFactory = ({
|
|||||||
const externalKms = await GcpKmsProviderFactory({ inputs: provider.inputs });
|
const externalKms = await GcpKmsProviderFactory({ inputs: provider.inputs });
|
||||||
try {
|
try {
|
||||||
await externalKms.validateConnection();
|
await externalKms.validateConnection();
|
||||||
|
sanitizedProviderInputObject = provider.inputs;
|
||||||
sanitizedProviderInput = JSON.stringify(provider.inputs);
|
sanitizedProviderInput = JSON.stringify(provider.inputs);
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof BadRequestError) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: error instanceof Error ? `GCP error: ${error.message}` : "Failed to validate GCP connection"
|
||||||
|
});
|
||||||
} finally {
|
} finally {
|
||||||
await externalKms.cleanup();
|
await externalKms.cleanup();
|
||||||
}
|
}
|
||||||
@@ -139,7 +164,10 @@ export const externalKmsServiceFactory = ({
|
|||||||
},
|
},
|
||||||
tx
|
tx
|
||||||
);
|
);
|
||||||
return { ...kms, external: externalKmsCfg };
|
return {
|
||||||
|
...kms,
|
||||||
|
external: { ...externalKmsCfg, providerInput: sanitizedProviderInputObject }
|
||||||
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
return externalKms;
|
return externalKms;
|
||||||
@@ -179,6 +207,7 @@ export const externalKmsServiceFactory = ({
|
|||||||
if (!externalKmsDoc) throw new NotFoundError({ message: `External KMS with ID '${kmsId}' not found` });
|
if (!externalKmsDoc) throw new NotFoundError({ message: `External KMS with ID '${kmsId}' not found` });
|
||||||
|
|
||||||
let sanitizedProviderInput = "";
|
let sanitizedProviderInput = "";
|
||||||
|
let sanitizedProviderInputObject: TExternalKmsAwsSchema | TExternalKmsGcpSchema;
|
||||||
const { encryptor: orgDataKeyEncryptor, decryptor: orgDataKeyDecryptor } =
|
const { encryptor: orgDataKeyEncryptor, decryptor: orgDataKeyDecryptor } =
|
||||||
await kmsService.createCipherPairWithDataKey({
|
await kmsService.createCipherPairWithDataKey({
|
||||||
type: KmsDataKey.Organization,
|
type: KmsDataKey.Organization,
|
||||||
@@ -199,7 +228,16 @@ export const externalKmsServiceFactory = ({
|
|||||||
const externalKms = await AwsKmsProviderFactory({ inputs: updatedProviderInput });
|
const externalKms = await AwsKmsProviderFactory({ inputs: updatedProviderInput });
|
||||||
try {
|
try {
|
||||||
await externalKms.validateConnection();
|
await externalKms.validateConnection();
|
||||||
|
sanitizedProviderInputObject = updatedProviderInput;
|
||||||
sanitizedProviderInput = JSON.stringify(updatedProviderInput);
|
sanitizedProviderInput = JSON.stringify(updatedProviderInput);
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof BadRequestError) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: error instanceof Error ? `AWS error: ${error.message}` : "Failed to validate AWS connection"
|
||||||
|
});
|
||||||
} finally {
|
} finally {
|
||||||
await externalKms.cleanup();
|
await externalKms.cleanup();
|
||||||
}
|
}
|
||||||
@@ -214,7 +252,16 @@ export const externalKmsServiceFactory = ({
|
|||||||
const externalKms = await GcpKmsProviderFactory({ inputs: updatedProviderInput });
|
const externalKms = await GcpKmsProviderFactory({ inputs: updatedProviderInput });
|
||||||
try {
|
try {
|
||||||
await externalKms.validateConnection();
|
await externalKms.validateConnection();
|
||||||
|
sanitizedProviderInputObject = updatedProviderInput;
|
||||||
sanitizedProviderInput = JSON.stringify(updatedProviderInput);
|
sanitizedProviderInput = JSON.stringify(updatedProviderInput);
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof BadRequestError) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: error instanceof Error ? `GCP error: ${error.message}` : "Failed to validate GCP connection"
|
||||||
|
});
|
||||||
} finally {
|
} finally {
|
||||||
await externalKms.cleanup();
|
await externalKms.cleanup();
|
||||||
}
|
}
|
||||||
@@ -234,7 +281,9 @@ export const externalKmsServiceFactory = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const externalKms = await externalKmsDAL.transaction(async (tx) => {
|
const externalKms = await externalKmsDAL.transaction(async (tx) => {
|
||||||
const kms = await kmsDAL.updateById(
|
let kms = kmsDoc;
|
||||||
|
if (kmsName || description) {
|
||||||
|
kms = await kmsDAL.updateById(
|
||||||
kmsDoc.id,
|
kmsDoc.id,
|
||||||
{
|
{
|
||||||
description,
|
description,
|
||||||
@@ -242,6 +291,7 @@ export const externalKmsServiceFactory = ({
|
|||||||
},
|
},
|
||||||
tx
|
tx
|
||||||
);
|
);
|
||||||
|
}
|
||||||
if (encryptedProviderInputs) {
|
if (encryptedProviderInputs) {
|
||||||
const externalKmsCfg = await externalKmsDAL.updateById(
|
const externalKmsCfg = await externalKmsDAL.updateById(
|
||||||
externalKmsDoc.id,
|
externalKmsDoc.id,
|
||||||
@@ -250,9 +300,9 @@ export const externalKmsServiceFactory = ({
|
|||||||
},
|
},
|
||||||
tx
|
tx
|
||||||
);
|
);
|
||||||
return { ...kms, external: externalKmsCfg };
|
return { ...kms, external: { ...externalKmsCfg, providerInput: sanitizedProviderInputObject } };
|
||||||
}
|
}
|
||||||
return { ...kms, external: externalKmsDoc };
|
return { ...kms, external: { ...externalKmsDoc, providerInput: sanitizedProviderInputObject } };
|
||||||
});
|
});
|
||||||
|
|
||||||
return externalKms;
|
return externalKms;
|
||||||
@@ -273,9 +323,40 @@ export const externalKmsServiceFactory = ({
|
|||||||
const externalKmsDoc = await externalKmsDAL.findOne({ kmsKeyId: kmsDoc.id });
|
const externalKmsDoc = await externalKmsDAL.findOne({ kmsKeyId: kmsDoc.id });
|
||||||
if (!externalKmsDoc) throw new NotFoundError({ message: `External KMS with ID '${kmsId}' not found` });
|
if (!externalKmsDoc) throw new NotFoundError({ message: `External KMS with ID '${kmsId}' not found` });
|
||||||
|
|
||||||
|
let decryptedProviderInputObject: TExternalKmsAwsSchema | TExternalKmsGcpSchema;
|
||||||
|
|
||||||
|
const { decryptor: orgDataKeyDecryptor } = await kmsService.createCipherPairWithDataKey({
|
||||||
|
type: KmsDataKey.Organization,
|
||||||
|
orgId: actorOrgId
|
||||||
|
});
|
||||||
|
|
||||||
|
const decryptedProviderInputBlob = orgDataKeyDecryptor({
|
||||||
|
cipherTextBlob: externalKmsDoc.encryptedProviderInputs
|
||||||
|
});
|
||||||
|
|
||||||
|
switch (externalKmsDoc.provider) {
|
||||||
|
case KmsProviders.Aws: {
|
||||||
|
const decryptedProviderInput = await ExternalKmsAwsSchema.parseAsync(
|
||||||
|
JSON.parse(decryptedProviderInputBlob.toString())
|
||||||
|
);
|
||||||
|
decryptedProviderInputObject = decryptedProviderInput;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case KmsProviders.Gcp: {
|
||||||
|
const decryptedProviderInput = await ExternalKmsGcpSchema.parseAsync(
|
||||||
|
JSON.parse(decryptedProviderInputBlob.toString())
|
||||||
|
);
|
||||||
|
|
||||||
|
decryptedProviderInputObject = decryptedProviderInput;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
const externalKms = await externalKmsDAL.transaction(async (tx) => {
|
const externalKms = await externalKmsDAL.transaction(async (tx) => {
|
||||||
const kms = await kmsDAL.deleteById(kmsDoc.id, tx);
|
const kms = await kmsDAL.deleteById(kmsDoc.id, tx);
|
||||||
return { ...kms, external: externalKmsDoc };
|
return { ...kms, external: { ...externalKmsDoc, providerInput: decryptedProviderInputObject } };
|
||||||
});
|
});
|
||||||
|
|
||||||
return externalKms;
|
return externalKms;
|
||||||
@@ -393,6 +474,14 @@ export const externalKmsServiceFactory = ({
|
|||||||
const externalKms = await GcpKmsProviderFactory({ inputs: { credential, gcpRegion, keyName: "" } });
|
const externalKms = await GcpKmsProviderFactory({ inputs: { credential, gcpRegion, keyName: "" } });
|
||||||
try {
|
try {
|
||||||
return await externalKms.getKeysList();
|
return await externalKms.getKeysList();
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof BadRequestError) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: error instanceof Error ? `GCP error: ${error.message}` : "Failed to fetch GCP keys"
|
||||||
|
});
|
||||||
} finally {
|
} finally {
|
||||||
await externalKms.cleanup();
|
await externalKms.cleanup();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { AssumeRoleCommand, STSClient } from "@aws-sdk/client-sts";
|
|||||||
|
|
||||||
import { CustomAWSHasher } from "@app/lib/aws/hashing";
|
import { CustomAWSHasher } from "@app/lib/aws/hashing";
|
||||||
import { crypto } from "@app/lib/crypto/cryptography";
|
import { crypto } from "@app/lib/crypto/cryptography";
|
||||||
|
import { BadRequestError } from "@app/lib/errors";
|
||||||
|
|
||||||
import { ExternalKmsAwsSchema, KmsAwsCredentialType, TExternalKmsAwsSchema, TExternalKmsProviderFns } from "./model";
|
import { ExternalKmsAwsSchema, KmsAwsCredentialType, TExternalKmsAwsSchema, TExternalKmsProviderFns } from "./model";
|
||||||
|
|
||||||
@@ -22,7 +23,7 @@ const getAwsKmsClient = async (providerInputs: TExternalKmsAwsSchema) => {
|
|||||||
});
|
});
|
||||||
const response = await stsClient.send(command);
|
const response = await stsClient.send(command);
|
||||||
if (!response.Credentials?.AccessKeyId || !response.Credentials?.SecretAccessKey)
|
if (!response.Credentials?.AccessKeyId || !response.Credentials?.SecretAccessKey)
|
||||||
throw new Error("Failed to assume role");
|
throw new BadRequestError({ message: "Failed to assume role" });
|
||||||
|
|
||||||
const kmsClient = new KMSClient({
|
const kmsClient = new KMSClient({
|
||||||
region: providerInputs.awsRegion,
|
region: providerInputs.awsRegion,
|
||||||
@@ -67,7 +68,7 @@ export const AwsKmsProviderFactory = async ({ inputs }: AwsKmsProviderArgs): Pro
|
|||||||
const command = new CreateKeyCommand({ Tags: [{ TagKey: "author", TagValue: "infisical" }] });
|
const command = new CreateKeyCommand({ Tags: [{ TagKey: "author", TagValue: "infisical" }] });
|
||||||
const kmsKey = await awsClient.send(command);
|
const kmsKey = await awsClient.send(command);
|
||||||
|
|
||||||
if (!kmsKey.KeyMetadata?.KeyId) throw new Error("Failed to generate kms key");
|
if (!kmsKey.KeyMetadata?.KeyId) throw new BadRequestError({ message: "Failed to generate kms key" });
|
||||||
|
|
||||||
const updatedProviderInputs = await ExternalKmsAwsSchema.parseAsync({
|
const updatedProviderInputs = await ExternalKmsAwsSchema.parseAsync({
|
||||||
...providerInputs,
|
...providerInputs,
|
||||||
|
|||||||
@@ -19,27 +19,31 @@ export enum KmsGcpKeyFetchAuthType {
|
|||||||
Kms = "kmsId"
|
Kms = "kmsId"
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ExternalKmsAwsSchema = z.object({
|
const AwsConnectionAssumeRoleCredentialsSchema = z.object({
|
||||||
credential: z
|
|
||||||
.discriminatedUnion("type", [
|
|
||||||
z.object({
|
|
||||||
type: z.literal(KmsAwsCredentialType.AccessKey),
|
|
||||||
data: z.object({
|
|
||||||
accessKey: z.string().trim().min(1).describe("AWS user account access key"),
|
|
||||||
secretKey: z.string().trim().min(1).describe("AWS user account secret key")
|
|
||||||
})
|
|
||||||
}),
|
|
||||||
z.object({
|
|
||||||
type: z.literal(KmsAwsCredentialType.AssumeRole),
|
|
||||||
data: z.object({
|
|
||||||
assumeRoleArn: z.string().trim().min(1).describe("AWS user role to be assumed by infisical"),
|
assumeRoleArn: z.string().trim().min(1).describe("AWS user role to be assumed by infisical"),
|
||||||
externalId: z
|
externalId: z
|
||||||
.string()
|
.string()
|
||||||
.trim()
|
.trim()
|
||||||
.min(1)
|
.min(1)
|
||||||
.optional()
|
.optional()
|
||||||
.describe("AWS assume role external id for furthur security in authentication")
|
.describe("AWS assume role external id for further security in authentication")
|
||||||
})
|
});
|
||||||
|
|
||||||
|
const AwsConnectionAccessTokenCredentialsSchema = z.object({
|
||||||
|
accessKey: z.string().trim().min(1).describe("AWS user account access key"),
|
||||||
|
secretKey: z.string().trim().min(1).describe("AWS user account secret key")
|
||||||
|
});
|
||||||
|
|
||||||
|
export const ExternalKmsAwsSchema = z.object({
|
||||||
|
credential: z
|
||||||
|
.discriminatedUnion("type", [
|
||||||
|
z.object({
|
||||||
|
type: z.literal(KmsAwsCredentialType.AccessKey),
|
||||||
|
data: AwsConnectionAccessTokenCredentialsSchema
|
||||||
|
}),
|
||||||
|
z.object({
|
||||||
|
type: z.literal(KmsAwsCredentialType.AssumeRole),
|
||||||
|
data: AwsConnectionAssumeRoleCredentialsSchema
|
||||||
})
|
})
|
||||||
])
|
])
|
||||||
.describe("AWS credential information to connect"),
|
.describe("AWS credential information to connect"),
|
||||||
@@ -52,6 +56,22 @@ export const ExternalKmsAwsSchema = z.object({
|
|||||||
});
|
});
|
||||||
export type TExternalKmsAwsSchema = z.infer<typeof ExternalKmsAwsSchema>;
|
export type TExternalKmsAwsSchema = z.infer<typeof ExternalKmsAwsSchema>;
|
||||||
|
|
||||||
|
export const SanitizedExternalKmsAwsSchema = ExternalKmsAwsSchema.extend({
|
||||||
|
credential: z.discriminatedUnion("type", [
|
||||||
|
z.object({
|
||||||
|
type: z.literal(KmsAwsCredentialType.AccessKey),
|
||||||
|
data: AwsConnectionAccessTokenCredentialsSchema.pick({ accessKey: true })
|
||||||
|
}),
|
||||||
|
z.object({
|
||||||
|
type: z.literal(KmsAwsCredentialType.AssumeRole),
|
||||||
|
data: AwsConnectionAssumeRoleCredentialsSchema.pick({
|
||||||
|
assumeRoleArn: true,
|
||||||
|
externalId: true
|
||||||
|
})
|
||||||
|
})
|
||||||
|
])
|
||||||
|
});
|
||||||
|
|
||||||
export const ExternalKmsGcpCredentialSchema = z.object({
|
export const ExternalKmsGcpCredentialSchema = z.object({
|
||||||
type: z.literal(KmsGcpCredentialType.ServiceAccount),
|
type: z.literal(KmsGcpCredentialType.ServiceAccount),
|
||||||
project_id: z.string().min(1),
|
project_id: z.string().min(1),
|
||||||
@@ -75,6 +95,8 @@ export const ExternalKmsGcpSchema = z.object({
|
|||||||
});
|
});
|
||||||
export type TExternalKmsGcpSchema = z.infer<typeof ExternalKmsGcpSchema>;
|
export type TExternalKmsGcpSchema = z.infer<typeof ExternalKmsGcpSchema>;
|
||||||
|
|
||||||
|
export const SanitizedExternalKmsGcpSchema = ExternalKmsGcpSchema.pick({ gcpRegion: true, keyName: true });
|
||||||
|
|
||||||
const ExternalKmsGcpClientSchema = ExternalKmsGcpSchema.pick({ gcpRegion: true }).extend({
|
const ExternalKmsGcpClientSchema = ExternalKmsGcpSchema.pick({ gcpRegion: true }).extend({
|
||||||
credential: ExternalKmsGcpCredentialSchema
|
credential: ExternalKmsGcpCredentialSchema
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -253,7 +253,7 @@ export const kmsServiceFactory = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!org.kmsDefaultKeyId) {
|
if (!org.kmsDefaultKeyId) {
|
||||||
throw new Error("Invalid organization KMS");
|
throw new BadRequestError({ message: "Invalid organization KMS" });
|
||||||
}
|
}
|
||||||
|
|
||||||
return org.kmsDefaultKeyId;
|
return org.kmsDefaultKeyId;
|
||||||
@@ -292,7 +292,7 @@ export const kmsServiceFactory = ({
|
|||||||
let externalKms: TExternalKmsProviderFns;
|
let externalKms: TExternalKmsProviderFns;
|
||||||
|
|
||||||
if (!kmsDoc.orgKms.id || !kmsDoc.orgKms.encryptedDataKey) {
|
if (!kmsDoc.orgKms.id || !kmsDoc.orgKms.encryptedDataKey) {
|
||||||
throw new Error("Invalid organization KMS");
|
throw new BadRequestError({ message: "Invalid organization KMS" });
|
||||||
}
|
}
|
||||||
|
|
||||||
// The idea is external kms connection info is encrypted by an org default KMS
|
// The idea is external kms connection info is encrypted by an org default KMS
|
||||||
@@ -338,7 +338,7 @@ export const kmsServiceFactory = ({
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
throw new Error("Invalid KMS provider.");
|
throw new BadRequestError({ message: "Invalid KMS provider." });
|
||||||
}
|
}
|
||||||
|
|
||||||
return async ({ cipherTextBlob }: Pick<TDecryptWithKmsDTO, "cipherTextBlob">) => {
|
return async ({ cipherTextBlob }: Pick<TDecryptWithKmsDTO, "cipherTextBlob">) => {
|
||||||
@@ -509,7 +509,7 @@ export const kmsServiceFactory = ({
|
|||||||
if (kmsDoc.externalKms) {
|
if (kmsDoc.externalKms) {
|
||||||
let externalKms: TExternalKmsProviderFns;
|
let externalKms: TExternalKmsProviderFns;
|
||||||
if (!kmsDoc.orgKms.id || !kmsDoc.orgKms.encryptedDataKey) {
|
if (!kmsDoc.orgKms.id || !kmsDoc.orgKms.encryptedDataKey) {
|
||||||
throw new Error("Invalid organization KMS");
|
throw new BadRequestError({ message: "Invalid organization KMS" });
|
||||||
}
|
}
|
||||||
|
|
||||||
const orgKmsDecryptor = await decryptWithKmsKey({
|
const orgKmsDecryptor = await decryptWithKmsKey({
|
||||||
@@ -550,7 +550,7 @@ export const kmsServiceFactory = ({
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
throw new Error("Invalid KMS provider.");
|
throw new BadRequestError({ message: "Invalid KMS provider." });
|
||||||
}
|
}
|
||||||
|
|
||||||
return async ({ plainText }: Pick<TEncryptWithKmsDTO, "plainText">) => {
|
return async ({ plainText }: Pick<TEncryptWithKmsDTO, "plainText">) => {
|
||||||
@@ -651,7 +651,7 @@ export const kmsServiceFactory = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!org.kmsEncryptedDataKey) {
|
if (!org.kmsEncryptedDataKey) {
|
||||||
throw new Error("Invalid organization KMS");
|
throw new BadRequestError({ message: "Invalid organization KMS" });
|
||||||
}
|
}
|
||||||
|
|
||||||
const kmsDecryptor = await decryptWithKmsKey({
|
const kmsDecryptor = await decryptWithKmsKey({
|
||||||
@@ -723,7 +723,7 @@ export const kmsServiceFactory = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!project.kmsSecretManagerKeyId) {
|
if (!project.kmsSecretManagerKeyId) {
|
||||||
throw new Error("Missing project KMS key ID");
|
throw new BadRequestError({ message: "Missing project KMS key ID" });
|
||||||
}
|
}
|
||||||
|
|
||||||
return project.kmsSecretManagerKeyId;
|
return project.kmsSecretManagerKeyId;
|
||||||
@@ -832,9 +832,10 @@ export const kmsServiceFactory = ({
|
|||||||
|
|
||||||
const isBase64 = !envConfig.ENCRYPTION_KEY;
|
const isBase64 = !envConfig.ENCRYPTION_KEY;
|
||||||
if (!encryptionKey)
|
if (!encryptionKey)
|
||||||
throw new Error(
|
throw new BadRequestError({
|
||||||
|
message:
|
||||||
"Root encryption key not found for KMS service. Did you set the ENCRYPTION_KEY or ROOT_ENCRYPTION_KEY environment variables?"
|
"Root encryption key not found for KMS service. Did you set the ENCRYPTION_KEY or ROOT_ENCRYPTION_KEY environment variables?"
|
||||||
);
|
});
|
||||||
|
|
||||||
const encryptionKeyBuffer = Buffer.from(encryptionKey, isBase64 ? "base64" : "utf8");
|
const encryptionKeyBuffer = Buffer.from(encryptionKey, isBase64 ? "base64" : "utf8");
|
||||||
|
|
||||||
@@ -846,7 +847,9 @@ export const kmsServiceFactory = ({
|
|||||||
if (kmsRootConfig.encryptionStrategy === RootKeyEncryptionStrategy.HSM) {
|
if (kmsRootConfig.encryptionStrategy === RootKeyEncryptionStrategy.HSM) {
|
||||||
const hsmIsActive = await hsmService.isActive();
|
const hsmIsActive = await hsmService.isActive();
|
||||||
if (!hsmIsActive) {
|
if (!hsmIsActive) {
|
||||||
throw new Error("Unable to decrypt root KMS key. HSM service is inactive. Did you configure the HSM?");
|
throw new BadRequestError({
|
||||||
|
message: "Unable to decrypt root KMS key. HSM service is inactive. Did you configure the HSM?"
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const decryptedKey = await hsmService.decrypt(kmsRootConfig.encryptedRootKey);
|
const decryptedKey = await hsmService.decrypt(kmsRootConfig.encryptedRootKey);
|
||||||
@@ -861,14 +864,16 @@ export const kmsServiceFactory = ({
|
|||||||
return cipher.decrypt(kmsRootConfig.encryptedRootKey, encryptionKeyBuffer);
|
return cipher.decrypt(kmsRootConfig.encryptedRootKey, encryptionKeyBuffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new Error(`Invalid root key encryption strategy: ${kmsRootConfig.encryptionStrategy}`);
|
throw new BadRequestError({ message: `Invalid root key encryption strategy: ${kmsRootConfig.encryptionStrategy}` });
|
||||||
};
|
};
|
||||||
|
|
||||||
const $encryptRootKey = async (plainKeyBuffer: Buffer, strategy: RootKeyEncryptionStrategy) => {
|
const $encryptRootKey = async (plainKeyBuffer: Buffer, strategy: RootKeyEncryptionStrategy) => {
|
||||||
if (strategy === RootKeyEncryptionStrategy.HSM) {
|
if (strategy === RootKeyEncryptionStrategy.HSM) {
|
||||||
const hsmIsActive = await hsmService.isActive();
|
const hsmIsActive = await hsmService.isActive();
|
||||||
if (!hsmIsActive) {
|
if (!hsmIsActive) {
|
||||||
throw new Error("Unable to encrypt root KMS key. HSM service is inactive. Did you configure the HSM?");
|
throw new BadRequestError({
|
||||||
|
message: "Unable to encrypt root KMS key. HSM service is inactive. Did you configure the HSM?"
|
||||||
|
});
|
||||||
}
|
}
|
||||||
const encrypted = await hsmService.encrypt(plainKeyBuffer);
|
const encrypted = await hsmService.encrypt(plainKeyBuffer);
|
||||||
return encrypted;
|
return encrypted;
|
||||||
@@ -882,7 +887,7 @@ export const kmsServiceFactory = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
||||||
throw new Error(`Invalid root key encryption strategy: ${strategy}`);
|
throw new BadRequestError({ message: `Invalid root key encryption strategy: ${strategy}` });
|
||||||
};
|
};
|
||||||
|
|
||||||
// by keeping the decrypted data key in inner scope
|
// by keeping the decrypted data key in inner scope
|
||||||
@@ -1130,7 +1135,7 @@ export const kmsServiceFactory = ({
|
|||||||
|
|
||||||
if (!encryptedRootKey) {
|
if (!encryptedRootKey) {
|
||||||
logger.error("KMS: Failed to re-encrypt ROOT Key with selected strategy");
|
logger.error("KMS: Failed to re-encrypt ROOT Key with selected strategy");
|
||||||
throw new Error("Failed to re-encrypt ROOT Key with selected strategy");
|
throw new BadRequestError({ message: "Failed to re-encrypt ROOT Key with selected strategy" });
|
||||||
}
|
}
|
||||||
|
|
||||||
await kmsRootConfigDAL.updateById(KMS_ROOT_CONFIG_UUID, {
|
await kmsRootConfigDAL.updateById(KMS_ROOT_CONFIG_UUID, {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { kmsKeys } from "./queries";
|
|||||||
import {
|
import {
|
||||||
AddExternalKmsType,
|
AddExternalKmsType,
|
||||||
ExternalKmsGcpSchemaType,
|
ExternalKmsGcpSchemaType,
|
||||||
|
ExternalKmsProvider,
|
||||||
KmsGcpKeyFetchAuthType,
|
KmsGcpKeyFetchAuthType,
|
||||||
KmsType,
|
KmsType,
|
||||||
UpdateExternalKmsType
|
UpdateExternalKmsType
|
||||||
@@ -14,11 +15,12 @@ import {
|
|||||||
export const useAddExternalKms = (orgId: string) => {
|
export const useAddExternalKms = (orgId: string) => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: async ({ name, description, provider }: AddExternalKmsType) => {
|
mutationFn: async ({ name, description, configuration }: AddExternalKmsType) => {
|
||||||
const { data } = await apiRequest.post("/api/v1/external-kms", {
|
const providerPath = configuration.type === ExternalKmsProvider.Aws ? "aws" : "gcp";
|
||||||
|
const { data } = await apiRequest.post(`/api/v1/external-kms/${providerPath}`, {
|
||||||
name,
|
name,
|
||||||
description,
|
description,
|
||||||
provider
|
configuration: configuration.inputs
|
||||||
});
|
});
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
@@ -29,21 +31,21 @@ export const useAddExternalKms = (orgId: string) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useUpdateExternalKms = (orgId: string) => {
|
export const useUpdateExternalKms = (orgId: string, provider: ExternalKmsProvider) => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: async ({
|
mutationFn: async ({
|
||||||
kmsId,
|
kmsId,
|
||||||
name,
|
name,
|
||||||
description,
|
description,
|
||||||
provider
|
configuration
|
||||||
}: {
|
}: {
|
||||||
kmsId: string;
|
kmsId: string;
|
||||||
} & UpdateExternalKmsType) => {
|
} & UpdateExternalKmsType) => {
|
||||||
const { data } = await apiRequest.patch(`/api/v1/external-kms/${kmsId}`, {
|
const { data } = await apiRequest.patch(`/api/v1/external-kms/${provider}/${kmsId}`, {
|
||||||
name,
|
name,
|
||||||
description,
|
description,
|
||||||
provider
|
configuration: configuration?.inputs
|
||||||
});
|
});
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
@@ -58,8 +60,8 @@ export const useUpdateExternalKms = (orgId: string) => {
|
|||||||
export const useRemoveExternalKms = (orgId: string) => {
|
export const useRemoveExternalKms = (orgId: string) => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: async (kmsId: string) => {
|
mutationFn: async ({ kmsId, provider }: { kmsId: string; provider: ExternalKmsProvider }) => {
|
||||||
const { data } = await apiRequest.delete(`/api/v1/external-kms/${kmsId}`);
|
const { data } = await apiRequest.delete(`/api/v1/external-kms/${provider}/${kmsId}`);
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
},
|
},
|
||||||
@@ -130,11 +132,19 @@ export const useExternalKmsFetchGcpKeys = (orgId: string) => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { data } = await apiRequest.post("/api/v1/external-kms/gcp/keys", {
|
const requestBody = credential
|
||||||
authMethod: credential ? KmsGcpKeyFetchAuthType.Credential : KmsGcpKeyFetchAuthType.Kms,
|
? {
|
||||||
|
authMethod: KmsGcpKeyFetchAuthType.Credential,
|
||||||
region: gcpRegion,
|
region: gcpRegion,
|
||||||
...rest
|
credential
|
||||||
});
|
}
|
||||||
|
: {
|
||||||
|
authMethod: KmsGcpKeyFetchAuthType.Kms,
|
||||||
|
region: gcpRegion,
|
||||||
|
kmsId
|
||||||
|
};
|
||||||
|
|
||||||
|
const { data } = await apiRequest.post("/api/v1/external-kms/gcp/keys", requestBody);
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { useQuery } from "@tanstack/react-query";
|
|||||||
|
|
||||||
import { apiRequest } from "@app/config/request";
|
import { apiRequest } from "@app/config/request";
|
||||||
|
|
||||||
import { Kms, KmsListEntry } from "./types";
|
import { ExternalKmsProvider, Kms, KmsListEntry } from "./types";
|
||||||
|
|
||||||
export const kmsKeys = {
|
export const kmsKeys = {
|
||||||
getExternalKmsList: (orgId: string) => ["get-all-external-kms", { orgId }],
|
getExternalKmsList: (orgId: string) => ["get-all-external-kms", { orgId }],
|
||||||
@@ -23,15 +23,19 @@ export const useGetExternalKmsList = (orgId: string, { enabled }: { enabled?: bo
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useGetExternalKmsById = (kmsId: string) => {
|
export const useGetExternalKmsById = ({
|
||||||
|
kmsId,
|
||||||
|
provider
|
||||||
|
}: {
|
||||||
|
kmsId: string;
|
||||||
|
provider: ExternalKmsProvider;
|
||||||
|
}) => {
|
||||||
return useQuery({
|
return useQuery({
|
||||||
queryKey: kmsKeys.getExternalKmsById(kmsId),
|
queryKey: kmsKeys.getExternalKmsById(kmsId),
|
||||||
enabled: Boolean(kmsId),
|
enabled: Boolean(kmsId),
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const {
|
const { data } = await apiRequest.get<Kms>(`/api/v1/external-kms/${provider}/${kmsId}`);
|
||||||
data: { externalKms }
|
return data;
|
||||||
} = await apiRequest.get<{ externalKms: Kms }>(`/api/v1/external-kms/${kmsId}`);
|
|
||||||
return externalKms;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -8,12 +8,13 @@ export type Kms = {
|
|||||||
description: string;
|
description: string;
|
||||||
orgId: string;
|
orgId: string;
|
||||||
name: string;
|
name: string;
|
||||||
external: {
|
externalKms: {
|
||||||
id: string;
|
id: string;
|
||||||
status: string;
|
status: string;
|
||||||
statusDetails: string;
|
statusDetails: string;
|
||||||
provider: string;
|
provider: string;
|
||||||
providerInput: Record<string, any>;
|
configuration: Record<string, any>;
|
||||||
|
credentialsHash?: string;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -123,14 +124,14 @@ export const ExternalKmsInputSchema = z.discriminatedUnion("type", [
|
|||||||
export const AddExternalKmsSchema = z.object({
|
export const AddExternalKmsSchema = z.object({
|
||||||
name: slugSchema({ min: 1, field: "Alias" }),
|
name: slugSchema({ min: 1, field: "Alias" }),
|
||||||
description: z.string().trim().optional(),
|
description: z.string().trim().optional(),
|
||||||
provider: ExternalKmsInputSchema
|
configuration: ExternalKmsInputSchema
|
||||||
});
|
});
|
||||||
|
|
||||||
export type AddExternalKmsType = z.infer<typeof AddExternalKmsSchema>;
|
export type AddExternalKmsType = z.infer<typeof AddExternalKmsSchema>;
|
||||||
|
|
||||||
// we need separate schema for update because the credential field is not required on GCP
|
// we need separate schema for update because the credential field is not required on GCP
|
||||||
export const ExternalKmsUpdateInputSchema = z.discriminatedUnion("type", [
|
export const ExternalKmsUpdateInputSchema = z.discriminatedUnion("type", [
|
||||||
z.object({ type: z.literal(ExternalKmsProvider.Aws), inputs: ExternalKmsAwsSchema }),
|
z.object({ type: z.literal(ExternalKmsProvider.Aws), inputs: ExternalKmsAwsSchema.partial() }),
|
||||||
z.object({
|
z.object({
|
||||||
type: z.literal(ExternalKmsProvider.Gcp),
|
type: z.literal(ExternalKmsProvider.Gcp),
|
||||||
inputs: ExternalKmsGcpSchema.pick({ gcpRegion: true, keyName: true })
|
inputs: ExternalKmsGcpSchema.pick({ gcpRegion: true, keyName: true })
|
||||||
@@ -144,9 +145,10 @@ export const UpdateExternalKmsSchema = z.object({
|
|||||||
.min(1)
|
.min(1)
|
||||||
.refine((v) => slugify(v) === v, {
|
.refine((v) => slugify(v) === v, {
|
||||||
message: "Alias must be a valid slug"
|
message: "Alias must be a valid slug"
|
||||||
}),
|
})
|
||||||
|
.optional(),
|
||||||
description: z.string().trim().optional(),
|
description: z.string().trim().optional(),
|
||||||
provider: ExternalKmsUpdateInputSchema
|
configuration: ExternalKmsUpdateInputSchema.optional()
|
||||||
});
|
});
|
||||||
|
|
||||||
export type UpdateExternalKmsType = z.infer<typeof UpdateExternalKmsSchema>;
|
export type UpdateExternalKmsType = z.infer<typeof UpdateExternalKmsSchema>;
|
||||||
|
|||||||
+91
-31
@@ -11,7 +11,8 @@ import {
|
|||||||
AddExternalKmsType,
|
AddExternalKmsType,
|
||||||
ExternalKmsProvider,
|
ExternalKmsProvider,
|
||||||
Kms,
|
Kms,
|
||||||
KmsAwsCredentialType
|
KmsAwsCredentialType,
|
||||||
|
UpdateExternalKmsSchema
|
||||||
} from "@app/hooks/api/kms/types";
|
} from "@app/hooks/api/kms/types";
|
||||||
|
|
||||||
const AWS_REGIONS = [
|
const AWS_REGIONS = [
|
||||||
@@ -50,9 +51,12 @@ type Props = {
|
|||||||
onCompleted: () => void;
|
onCompleted: () => void;
|
||||||
onCancel: () => void;
|
onCancel: () => void;
|
||||||
kms?: Kms;
|
kms?: Kms;
|
||||||
|
mode?: "full" | "credentials" | "details";
|
||||||
};
|
};
|
||||||
|
|
||||||
export const AwsKmsForm = ({ onCompleted, onCancel, kms }: Props) => {
|
export const AwsKmsForm = ({ onCompleted, onCancel, kms, mode = "full" }: Props) => {
|
||||||
|
const validationSchema = kms ? UpdateExternalKmsSchema : AddExternalKmsSchema;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
control,
|
control,
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
@@ -60,24 +64,35 @@ export const AwsKmsForm = ({ onCompleted, onCancel, kms }: Props) => {
|
|||||||
setValue,
|
setValue,
|
||||||
formState: { isSubmitting }
|
formState: { isSubmitting }
|
||||||
} = useForm<AddExternalKmsType>({
|
} = useForm<AddExternalKmsType>({
|
||||||
resolver: zodResolver(AddExternalKmsSchema),
|
resolver: zodResolver(validationSchema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
name: kms?.name,
|
name: kms?.name,
|
||||||
description: kms?.description ?? "",
|
description: kms?.description ?? "",
|
||||||
provider: {
|
configuration: {
|
||||||
type: ExternalKmsProvider.Aws,
|
type: ExternalKmsProvider.Aws,
|
||||||
inputs: {
|
inputs: {
|
||||||
|
...(mode !== "details" &&
|
||||||
|
kms?.externalKms?.configuration?.credential?.type &&
|
||||||
|
kms.externalKms.configuration.credential.data
|
||||||
|
? {
|
||||||
credential: {
|
credential: {
|
||||||
type: kms?.external?.providerInput?.credential?.type,
|
type: kms.externalKms.configuration.credential.type,
|
||||||
data: {
|
data: {
|
||||||
accessKey: kms?.external?.providerInput?.credential?.data?.accessKey,
|
accessKey: kms.externalKms.configuration.credential.data?.accessKey ?? "",
|
||||||
secretKey: kms?.external?.providerInput?.credential?.data?.secretKey,
|
secretKey: kms.externalKms.configuration.credential.data?.secretKey ?? "",
|
||||||
assumeRoleArn: kms?.external?.providerInput?.credential?.data?.assumeRoleArn,
|
assumeRoleArn:
|
||||||
externalId: kms?.external?.providerInput?.credential?.data?.externalId
|
kms.externalKms.configuration.credential.data?.assumeRoleArn ?? "",
|
||||||
|
externalId: kms.externalKms.configuration.credential.data?.externalId ?? ""
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
awsRegion: kms?.external?.providerInput?.awsRegion,
|
}
|
||||||
kmsKeyId: kms?.external?.providerInput?.kmsKeyId
|
: {}),
|
||||||
|
...(mode !== "credentials"
|
||||||
|
? {
|
||||||
|
awsRegion: kms?.externalKms?.configuration?.awsRegion ?? "",
|
||||||
|
kmsKeyId: kms?.externalKms?.configuration?.kmsKeyId ?? ""
|
||||||
|
}
|
||||||
|
: {})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -85,30 +100,59 @@ export const AwsKmsForm = ({ onCompleted, onCancel, kms }: Props) => {
|
|||||||
|
|
||||||
const { currentOrg } = useOrganization();
|
const { currentOrg } = useOrganization();
|
||||||
const { mutateAsync: addAwsExternalKms } = useAddExternalKms(currentOrg.id);
|
const { mutateAsync: addAwsExternalKms } = useAddExternalKms(currentOrg.id);
|
||||||
const { mutateAsync: updateAwsExternalKms } = useUpdateExternalKms(currentOrg.id);
|
const { mutateAsync: updateAwsExternalKms } = useUpdateExternalKms(
|
||||||
|
currentOrg.id,
|
||||||
|
ExternalKmsProvider.Aws
|
||||||
|
);
|
||||||
|
|
||||||
const selectedAwsAuthType = watch("provider.inputs.credential.type");
|
const selectedAwsAuthType = watch("configuration.inputs.credential.type");
|
||||||
|
|
||||||
const handleAwsKmsFormSubmit = async (data: AddExternalKmsType) => {
|
const handleAwsKmsFormSubmit = async (data: AddExternalKmsType) => {
|
||||||
const { name, description, provider } = data;
|
const { name, description, configuration } = data;
|
||||||
try {
|
try {
|
||||||
if (kms) {
|
if (kms) {
|
||||||
|
if (configuration.type !== ExternalKmsProvider.Aws) {
|
||||||
|
throw new Error("Invalid configuration type");
|
||||||
|
}
|
||||||
|
const awsInputs = configuration.inputs;
|
||||||
|
|
||||||
|
if (mode === "credentials") {
|
||||||
|
await updateAwsExternalKms({
|
||||||
|
kmsId: kms.id,
|
||||||
|
configuration: {
|
||||||
|
type: ExternalKmsProvider.Aws,
|
||||||
|
inputs: {
|
||||||
|
credential: { ...awsInputs.credential }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
await updateAwsExternalKms({
|
await updateAwsExternalKms({
|
||||||
kmsId: kms.id,
|
kmsId: kms.id,
|
||||||
name,
|
name,
|
||||||
description,
|
description,
|
||||||
provider
|
configuration: {
|
||||||
|
type: ExternalKmsProvider.Aws,
|
||||||
|
inputs: {
|
||||||
|
awsRegion: awsInputs.awsRegion,
|
||||||
|
kmsKeyId: awsInputs.kmsKeyId
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
createNotification({
|
createNotification({
|
||||||
text: "Successfully updated AWS External KMS",
|
text:
|
||||||
|
mode === "credentials"
|
||||||
|
? "Successfully updated AWS External KMS credentials"
|
||||||
|
: "Successfully updated AWS External KMS Details",
|
||||||
type: "success"
|
type: "success"
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
await addAwsExternalKms({
|
await addAwsExternalKms({
|
||||||
name,
|
name,
|
||||||
description,
|
description,
|
||||||
provider
|
configuration
|
||||||
});
|
});
|
||||||
|
|
||||||
createNotification({
|
createNotification({
|
||||||
@@ -125,6 +169,8 @@ export const AwsKmsForm = ({ onCompleted, onCancel, kms }: Props) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<form onSubmit={handleSubmit(handleAwsKmsFormSubmit)} autoComplete="off">
|
<form onSubmit={handleSubmit(handleAwsKmsFormSubmit)} autoComplete="off">
|
||||||
|
{(mode === "full" || mode === "details") && (
|
||||||
|
<>
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
name="name"
|
name="name"
|
||||||
@@ -143,9 +189,13 @@ export const AwsKmsForm = ({ onCompleted, onCancel, kms }: Props) => {
|
|||||||
</FormControl>
|
</FormControl>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{(mode === "full" || mode === "credentials") && (
|
||||||
|
<>
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
name="provider.inputs.credential.type"
|
name="configuration.inputs.credential.type"
|
||||||
defaultValue={KmsAwsCredentialType.AssumeRole}
|
defaultValue={KmsAwsCredentialType.AssumeRole}
|
||||||
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
|
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
|
||||||
<FormControl
|
<FormControl
|
||||||
@@ -157,10 +207,10 @@ export const AwsKmsForm = ({ onCompleted, onCancel, kms }: Props) => {
|
|||||||
defaultValue={field.value}
|
defaultValue={field.value}
|
||||||
{...field}
|
{...field}
|
||||||
onValueChange={(e) => {
|
onValueChange={(e) => {
|
||||||
setValue("provider.inputs.credential.data.accessKey", "");
|
setValue("configuration.inputs.credential.data.accessKey", "");
|
||||||
setValue("provider.inputs.credential.data.secretKey", "");
|
setValue("configuration.inputs.credential.data.secretKey", "");
|
||||||
setValue("provider.inputs.credential.data.assumeRoleArn", "");
|
setValue("configuration.inputs.credential.data.assumeRoleArn", "");
|
||||||
setValue("provider.inputs.credential.data.externalId", "");
|
setValue("configuration.inputs.credential.data.externalId", "");
|
||||||
|
|
||||||
onChange(e);
|
onChange(e);
|
||||||
}}
|
}}
|
||||||
@@ -177,7 +227,7 @@ export const AwsKmsForm = ({ onCompleted, onCancel, kms }: Props) => {
|
|||||||
<>
|
<>
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
name="provider.inputs.credential.data.accessKey"
|
name="configuration.inputs.credential.data.accessKey"
|
||||||
render={({ field, fieldState: { error } }) => (
|
render={({ field, fieldState: { error } }) => (
|
||||||
<FormControl
|
<FormControl
|
||||||
label="Access Key ID"
|
label="Access Key ID"
|
||||||
@@ -190,7 +240,7 @@ export const AwsKmsForm = ({ onCompleted, onCancel, kms }: Props) => {
|
|||||||
/>
|
/>
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
name="provider.inputs.credential.data.secretKey"
|
name="configuration.inputs.credential.data.secretKey"
|
||||||
render={({ field, fieldState: { error } }) => (
|
render={({ field, fieldState: { error } }) => (
|
||||||
<FormControl
|
<FormControl
|
||||||
label="Secret Access Key"
|
label="Secret Access Key"
|
||||||
@@ -206,7 +256,7 @@ export const AwsKmsForm = ({ onCompleted, onCancel, kms }: Props) => {
|
|||||||
<>
|
<>
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
name="provider.inputs.credential.data.assumeRoleArn"
|
name="configuration.inputs.credential.data.assumeRoleArn"
|
||||||
render={({ field, fieldState: { error } }) => (
|
render={({ field, fieldState: { error } }) => (
|
||||||
<FormControl
|
<FormControl
|
||||||
label="IAM Role ARN For Role Assumption"
|
label="IAM Role ARN For Role Assumption"
|
||||||
@@ -219,7 +269,7 @@ export const AwsKmsForm = ({ onCompleted, onCancel, kms }: Props) => {
|
|||||||
/>
|
/>
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
name="provider.inputs.credential.data.externalId"
|
name="configuration.inputs.credential.data.externalId"
|
||||||
render={({ field, fieldState: { error } }) => (
|
render={({ field, fieldState: { error } }) => (
|
||||||
<FormControl
|
<FormControl
|
||||||
label="Assume Role External ID"
|
label="Assume Role External ID"
|
||||||
@@ -232,9 +282,13 @@ export const AwsKmsForm = ({ onCompleted, onCancel, kms }: Props) => {
|
|||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{(mode === "full" || mode === "details") && (
|
||||||
|
<>
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
name="provider.inputs.awsRegion"
|
name="configuration.inputs.awsRegion"
|
||||||
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
|
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
|
||||||
<FormControl label="AWS Region" errorText={error?.message} isError={Boolean(error)}>
|
<FormControl label="AWS Region" errorText={error?.message} isError={Boolean(error)}>
|
||||||
<Select
|
<Select
|
||||||
@@ -254,16 +308,22 @@ export const AwsKmsForm = ({ onCompleted, onCancel, kms }: Props) => {
|
|||||||
/>
|
/>
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
name="provider.inputs.kmsKeyId"
|
name="configuration.inputs.kmsKeyId"
|
||||||
render={({ field, fieldState: { error } }) => (
|
render={({ field, fieldState: { error } }) => (
|
||||||
<FormControl label="AWS KMS Key ID" errorText={error?.message} isError={Boolean(error)}>
|
<FormControl
|
||||||
|
label="AWS KMS Key ID"
|
||||||
|
errorText={error?.message}
|
||||||
|
isError={Boolean(error)}
|
||||||
|
>
|
||||||
<Input placeholder="" {...field} />
|
<Input placeholder="" {...field} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
<div className="mt-6 flex items-center space-x-4">
|
<div className="mt-6 flex items-center space-x-4">
|
||||||
<Button type="submit" isLoading={isSubmitting}>
|
<Button type="submit" isLoading={isSubmitting}>
|
||||||
Save
|
{mode === "credentials" ? "Update Credentials" : "Save"}
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant="outline_bg" onClick={onCancel}>
|
<Button variant="outline_bg" onClick={onCancel}>
|
||||||
Cancel
|
Cancel
|
||||||
|
|||||||
+20
-9
@@ -1,4 +1,4 @@
|
|||||||
import { ContentLoader, Modal, ModalContent } from "@app/components/v2";
|
import { Modal, ModalContent } from "@app/components/v2";
|
||||||
import { useGetExternalKmsById } from "@app/hooks/api";
|
import { useGetExternalKmsById } from "@app/hooks/api";
|
||||||
import { ExternalKmsProvider } from "@app/hooks/api/kms/types";
|
import { ExternalKmsProvider } from "@app/hooks/api/kms/types";
|
||||||
|
|
||||||
@@ -8,25 +8,36 @@ import { GcpKmsForm } from "./GcpKmsForm";
|
|||||||
type Props = {
|
type Props = {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
kmsId: string;
|
kmsId: string;
|
||||||
|
provider: ExternalKmsProvider;
|
||||||
onOpenChange: (state: boolean) => void;
|
onOpenChange: (state: boolean) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const UpdateExternalKmsForm = ({ isOpen, kmsId, onOpenChange }: Props) => {
|
export const EditExternalKmsCredentialsModal = ({
|
||||||
const { data: externalKms, isPending } = useGetExternalKmsById(kmsId);
|
isOpen,
|
||||||
|
kmsId,
|
||||||
|
provider,
|
||||||
|
onOpenChange
|
||||||
|
}: Props) => {
|
||||||
|
const { data: kms } = useGetExternalKmsById({ kmsId, provider });
|
||||||
return (
|
return (
|
||||||
<Modal isOpen={isOpen} onOpenChange={onOpenChange}>
|
<Modal isOpen={isOpen} onOpenChange={onOpenChange}>
|
||||||
<ModalContent title="Edit configuration" bodyClassName="overflow-visible">
|
<ModalContent
|
||||||
{isPending && <ContentLoader />}
|
title="Edit Credentials"
|
||||||
{externalKms?.external?.provider === ExternalKmsProvider.Aws && (
|
subTitle="Update the credentials for this KMS."
|
||||||
|
bodyClassName="overflow-visible"
|
||||||
|
>
|
||||||
|
{kms?.externalKms?.provider === ExternalKmsProvider.Aws && (
|
||||||
<AwsKmsForm
|
<AwsKmsForm
|
||||||
kms={externalKms}
|
kms={kms}
|
||||||
|
mode="credentials"
|
||||||
onCancel={() => onOpenChange(false)}
|
onCancel={() => onOpenChange(false)}
|
||||||
onCompleted={() => onOpenChange(false)}
|
onCompleted={() => onOpenChange(false)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{externalKms?.external?.provider === ExternalKmsProvider.Gcp && (
|
{kms?.externalKms?.provider === ExternalKmsProvider.Gcp && (
|
||||||
<GcpKmsForm
|
<GcpKmsForm
|
||||||
kms={externalKms}
|
kms={kms}
|
||||||
|
mode="credentials"
|
||||||
onCancel={() => onOpenChange(false)}
|
onCancel={() => onOpenChange(false)}
|
||||||
onCompleted={() => onOpenChange(false)}
|
onCompleted={() => onOpenChange(false)}
|
||||||
/>
|
/>
|
||||||
+44
@@ -0,0 +1,44 @@
|
|||||||
|
import { Modal, ModalContent } from "@app/components/v2";
|
||||||
|
import { useGetExternalKmsById } from "@app/hooks/api";
|
||||||
|
import { ExternalKmsProvider } from "@app/hooks/api/kms/types";
|
||||||
|
|
||||||
|
import { AwsKmsForm } from "./AwsKmsForm";
|
||||||
|
import { GcpKmsForm } from "./GcpKmsForm";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
isOpen: boolean;
|
||||||
|
onOpenChange: (isOpen: boolean) => void;
|
||||||
|
kmsId: string;
|
||||||
|
provider: ExternalKmsProvider;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const EditExternalKmsDetailsModal = ({ isOpen, onOpenChange, kmsId, provider }: Props) => {
|
||||||
|
const { data: kms } = useGetExternalKmsById({ kmsId, provider });
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal isOpen={isOpen} onOpenChange={onOpenChange}>
|
||||||
|
<ModalContent
|
||||||
|
title="Edit KMS Details"
|
||||||
|
subTitle="Update the name and description for this KMS."
|
||||||
|
bodyClassName="overflow-visible"
|
||||||
|
>
|
||||||
|
{kms?.externalKms?.provider === ExternalKmsProvider.Aws && (
|
||||||
|
<AwsKmsForm
|
||||||
|
kms={kms}
|
||||||
|
mode="details"
|
||||||
|
onCancel={() => onOpenChange(false)}
|
||||||
|
onCompleted={() => onOpenChange(false)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{kms?.externalKms?.provider === ExternalKmsProvider.Gcp && (
|
||||||
|
<GcpKmsForm
|
||||||
|
kms={kms}
|
||||||
|
mode="details"
|
||||||
|
onCancel={() => onOpenChange(false)}
|
||||||
|
onCompleted={() => onOpenChange(false)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</ModalContent>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
+31
-4
@@ -22,7 +22,9 @@ import { UsePopUpState } from "@app/hooks/usePopUp";
|
|||||||
type Props = {
|
type Props = {
|
||||||
kms: KmsListEntry;
|
kms: KmsListEntry;
|
||||||
handlePopUpOpen: (
|
handlePopUpOpen: (
|
||||||
popUpName: keyof UsePopUpState<["editExternalKms", "removeExternalKms", "upgradePlan"]>,
|
popUpName: keyof UsePopUpState<
|
||||||
|
["editExternalKmsDetails", "editExternalKmsCredentials", "removeExternalKms", "upgradePlan"]
|
||||||
|
>,
|
||||||
data?: {
|
data?: {
|
||||||
kmsId?: string;
|
kmsId?: string;
|
||||||
name?: string;
|
name?: string;
|
||||||
@@ -104,6 +106,7 @@ export const ExternalKmsItem = ({ kms, handlePopUpOpen, subscription }: Props) =
|
|||||||
<DropdownMenuContent align="start" className="p-1">
|
<DropdownMenuContent align="start" className="p-1">
|
||||||
<OrgPermissionCan I={OrgPermissionActions.Edit} an={OrgPermissionSubjects.Kms}>
|
<OrgPermissionCan I={OrgPermissionActions.Edit} an={OrgPermissionSubjects.Kms}>
|
||||||
{(isAllowed) => (
|
{(isAllowed) => (
|
||||||
|
<>
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
disabled={!isAllowed}
|
disabled={!isAllowed}
|
||||||
className={twMerge(
|
className={twMerge(
|
||||||
@@ -118,13 +121,37 @@ export const ExternalKmsItem = ({ kms, handlePopUpOpen, subscription }: Props) =
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
handlePopUpOpen("editExternalKms", {
|
handlePopUpOpen("editExternalKmsDetails", {
|
||||||
kmsId: kms.id
|
kmsId: kms.id,
|
||||||
|
provider: kms.externalKms.provider
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Edit
|
Edit Details
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem
|
||||||
|
disabled={!isAllowed}
|
||||||
|
className={twMerge(
|
||||||
|
!isAllowed && "pointer-events-none cursor-not-allowed opacity-50"
|
||||||
|
)}
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
if (subscription && !subscription?.externalKms) {
|
||||||
|
handlePopUpOpen("upgradePlan", {
|
||||||
|
isEnterpriseFeature: true
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
handlePopUpOpen("editExternalKmsCredentials", {
|
||||||
|
kmsId: kms.id,
|
||||||
|
provider: kms.externalKms.provider
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Edit Credentials
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
</OrgPermissionCan>
|
</OrgPermissionCan>
|
||||||
<OrgPermissionCan I={OrgPermissionActions.Delete} an={OrgPermissionSubjects.Kms}>
|
<OrgPermissionCan I={OrgPermissionActions.Delete} an={OrgPermissionSubjects.Kms}>
|
||||||
|
|||||||
+76
-34
@@ -24,6 +24,7 @@ type Props = {
|
|||||||
onCompleted: () => void;
|
onCompleted: () => void;
|
||||||
onCancel: () => void;
|
onCancel: () => void;
|
||||||
kms?: Kms;
|
kms?: Kms;
|
||||||
|
mode?: "full" | "credentials" | "details";
|
||||||
};
|
};
|
||||||
|
|
||||||
const GCP_REGIONS = [
|
const GCP_REGIONS = [
|
||||||
@@ -76,7 +77,7 @@ const formatOptionLabel = ({ value, label }: { value: string; label: string }) =
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
export const GcpKmsForm = ({ onCompleted, onCancel, kms }: Props) => {
|
export const GcpKmsForm = ({ onCompleted, onCancel, kms, mode = "full" }: Props) => {
|
||||||
const [isCredentialValid, setIsCredentialValid] = useState<boolean>(false);
|
const [isCredentialValid, setIsCredentialValid] = useState<boolean>(false);
|
||||||
const [keys, setKeys] = useState<{ value: string; label: string }[]>([]);
|
const [keys, setKeys] = useState<{ value: string; label: string }[]>([]);
|
||||||
|
|
||||||
@@ -88,7 +89,7 @@ export const GcpKmsForm = ({ onCompleted, onCancel, kms }: Props) => {
|
|||||||
getValues,
|
getValues,
|
||||||
resetField,
|
resetField,
|
||||||
setValue,
|
setValue,
|
||||||
formState: { isSubmitting }
|
formState: { isSubmitting, isDirty, isValid }
|
||||||
} = useForm<AddExternalKmsGcpFormSchemaType>({
|
} = useForm<AddExternalKmsGcpFormSchemaType>({
|
||||||
resolver: zodResolver(AddExternalKmsGcpFormSchema),
|
resolver: zodResolver(AddExternalKmsGcpFormSchema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
@@ -98,9 +99,9 @@ export const GcpKmsForm = ({ onCompleted, onCancel, kms }: Props) => {
|
|||||||
gcpRegion: kms
|
gcpRegion: kms
|
||||||
? {
|
? {
|
||||||
label:
|
label:
|
||||||
GCP_REGIONS.find((r) => r.value === kms.external.providerInput.gcpRegion)?.label ??
|
GCP_REGIONS.find((r) => r.value === kms.externalKms.configuration.gcpRegion)?.label ??
|
||||||
"",
|
"",
|
||||||
value: kms.external.providerInput.gcpRegion
|
value: kms.externalKms.configuration.gcpRegion
|
||||||
}
|
}
|
||||||
: undefined,
|
: undefined,
|
||||||
keyObject: undefined
|
keyObject: undefined
|
||||||
@@ -109,7 +110,11 @@ export const GcpKmsForm = ({ onCompleted, onCancel, kms }: Props) => {
|
|||||||
|
|
||||||
const { currentOrg } = useOrganization();
|
const { currentOrg } = useOrganization();
|
||||||
const { mutateAsync: addGcpExternalKms } = useAddExternalKms(currentOrg.id);
|
const { mutateAsync: addGcpExternalKms } = useAddExternalKms(currentOrg.id);
|
||||||
const { mutateAsync: updateGcpExternalKms } = useUpdateExternalKms(currentOrg.id);
|
const { mutateAsync: updateGcpExternalKms } = useUpdateExternalKms(
|
||||||
|
currentOrg.id,
|
||||||
|
ExternalKmsProvider.Gcp
|
||||||
|
);
|
||||||
|
|
||||||
const { mutateAsync: fetchGcpKeys, isPending: isFetchGcpKeysLoading } =
|
const { mutateAsync: fetchGcpKeys, isPending: isFetchGcpKeysLoading } =
|
||||||
useExternalKmsFetchGcpKeys(currentOrg?.id);
|
useExternalKmsFetchGcpKeys(currentOrg?.id);
|
||||||
|
|
||||||
@@ -140,8 +145,54 @@ export const GcpKmsForm = ({ onCompleted, onCancel, kms }: Props) => {
|
|||||||
|
|
||||||
// handles the form submission
|
// handles the form submission
|
||||||
const handleGcpKmsFormSubmit = async (data: AddExternalKmsGcpFormSchemaType) => {
|
const handleGcpKmsFormSubmit = async (data: AddExternalKmsGcpFormSchemaType) => {
|
||||||
const { name, description, gcpRegion: gcpRegionObject, keyObject } = data;
|
const { name, description, formType, gcpRegion: gcpRegionObject, keyObject } = data;
|
||||||
const gcpRegion = gcpRegionObject.value;
|
|
||||||
|
try {
|
||||||
|
if (kms) {
|
||||||
|
if (formType === "updateGcpKms") {
|
||||||
|
const gcpRegion = gcpRegionObject?.value;
|
||||||
|
if (!gcpRegion) {
|
||||||
|
setError("gcpRegion", {
|
||||||
|
message: "Please select a GCP region."
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (keyObject && !keys.find((k) => k.value === keyObject.value)) {
|
||||||
|
setError("keyObject", {
|
||||||
|
message: "Please select a valid key."
|
||||||
|
});
|
||||||
|
resetField("keyObject");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await updateGcpExternalKms({
|
||||||
|
kmsId: kms.id,
|
||||||
|
name,
|
||||||
|
description,
|
||||||
|
configuration: {
|
||||||
|
type: ExternalKmsProvider.Gcp,
|
||||||
|
inputs: {
|
||||||
|
gcpRegion,
|
||||||
|
keyName: keyObject?.value ?? kms.externalKms.configuration.keyName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
createNotification({
|
||||||
|
text: "Successfully updated GCP External KMS Details",
|
||||||
|
type: "success"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else if (formType === "newGcpKms") {
|
||||||
|
const gcpRegion = gcpRegionObject?.value;
|
||||||
|
if (!gcpRegion) {
|
||||||
|
setError("gcpRegion", {
|
||||||
|
message: "Please select a GCP region."
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!keys.find((k) => k.value === keyObject?.value)) {
|
if (!keys.find((k) => k.value === keyObject?.value)) {
|
||||||
setError("keyObject", {
|
setError("keyObject", {
|
||||||
message: "Please select a valid key."
|
message: "Please select a valid key."
|
||||||
@@ -150,26 +201,6 @@ export const GcpKmsForm = ({ onCompleted, onCancel, kms }: Props) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
|
||||||
if (kms) {
|
|
||||||
await updateGcpExternalKms({
|
|
||||||
kmsId: kms.id,
|
|
||||||
name,
|
|
||||||
description,
|
|
||||||
provider: {
|
|
||||||
type: ExternalKmsProvider.Gcp,
|
|
||||||
inputs: {
|
|
||||||
gcpRegion,
|
|
||||||
keyName: keyObject?.value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
createNotification({
|
|
||||||
text: "Successfully updated GCP External KMS",
|
|
||||||
type: "success"
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
const credentialJson = await getCredentialFileJson();
|
const credentialJson = await getCredentialFileJson();
|
||||||
if (!credentialJson) {
|
if (!credentialJson) {
|
||||||
return;
|
return;
|
||||||
@@ -177,11 +208,11 @@ export const GcpKmsForm = ({ onCompleted, onCancel, kms }: Props) => {
|
|||||||
await addGcpExternalKms({
|
await addGcpExternalKms({
|
||||||
name,
|
name,
|
||||||
description,
|
description,
|
||||||
provider: {
|
configuration: {
|
||||||
type: ExternalKmsProvider.Gcp,
|
type: ExternalKmsProvider.Gcp,
|
||||||
inputs: {
|
inputs: {
|
||||||
gcpRegion,
|
gcpRegion,
|
||||||
keyName: keyObject?.value,
|
keyName: keyObject?.value ?? "",
|
||||||
credential: credentialJson
|
credential: credentialJson
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -208,8 +239,9 @@ export const GcpKmsForm = ({ onCompleted, onCancel, kms }: Props) => {
|
|||||||
if (!kms && !credentialJson) {
|
if (!kms && !credentialJson) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const gcpRegion = getValues("gcpRegion").value;
|
const gcpRegionObject = getValues("gcpRegion");
|
||||||
if (!gcpRegion.length) {
|
const gcpRegion = gcpRegionObject?.value;
|
||||||
|
if (!gcpRegion) {
|
||||||
setError("gcpRegion", {
|
setError("gcpRegion", {
|
||||||
message: "Please select a GCP region to fetch GCP Keys."
|
message: "Please select a GCP region to fetch GCP Keys."
|
||||||
});
|
});
|
||||||
@@ -231,7 +263,9 @@ export const GcpKmsForm = ({ onCompleted, onCancel, kms }: Props) => {
|
|||||||
|
|
||||||
setKeys(returnedKeys);
|
setKeys(returnedKeys);
|
||||||
if (kms) {
|
if (kms) {
|
||||||
const existingKey = returnedKeys.find((k) => k.value === kms.external.providerInput.keyName);
|
const existingKey = returnedKeys.find(
|
||||||
|
(k) => k.value === kms.externalKms.configuration.keyName
|
||||||
|
);
|
||||||
if (existingKey) {
|
if (existingKey) {
|
||||||
setValue("keyObject", existingKey);
|
setValue("keyObject", existingKey);
|
||||||
}
|
}
|
||||||
@@ -260,6 +294,8 @@ export const GcpKmsForm = ({ onCompleted, onCancel, kms }: Props) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<form onSubmit={handleSubmit(handleGcpKmsFormSubmit)} autoComplete="off">
|
<form onSubmit={handleSubmit(handleGcpKmsFormSubmit)} autoComplete="off">
|
||||||
|
{(mode === "full" || mode === "details") && (
|
||||||
|
<>
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
name="name"
|
name="name"
|
||||||
@@ -342,14 +378,20 @@ export const GcpKmsForm = ({ onCompleted, onCancel, kms }: Props) => {
|
|||||||
</FormControl>
|
</FormControl>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
{kms && (
|
</>
|
||||||
|
)}
|
||||||
|
{kms && mode === "credentials" && (
|
||||||
<span className="text-xs text-mineshaft-300">
|
<span className="text-xs text-mineshaft-300">
|
||||||
To change your GCP credentials, create a new external KMS and assign it to project you
|
To change your GCP credentials, create a new external KMS and assign it to project you
|
||||||
want to use it with.
|
want to use it with.
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
<div className="mt-6 flex items-center space-x-4">
|
<div className="mt-6 flex items-center space-x-4">
|
||||||
<Button type="submit" isLoading={isSubmitting}>
|
<Button
|
||||||
|
type="submit"
|
||||||
|
isLoading={isSubmitting}
|
||||||
|
isDisabled={!isDirty || !isValid || mode === "credentials"}
|
||||||
|
>
|
||||||
Save
|
Save
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant="outline_bg" onClick={onCancel}>
|
<Button variant="outline_bg" onClick={onCancel}>
|
||||||
|
|||||||
+23
-8
@@ -25,10 +25,12 @@ import {
|
|||||||
import { withPermission } from "@app/hoc";
|
import { withPermission } from "@app/hoc";
|
||||||
import { usePopUp } from "@app/hooks";
|
import { usePopUp } from "@app/hooks";
|
||||||
import { useGetExternalKmsList, useRemoveExternalKms } from "@app/hooks/api";
|
import { useGetExternalKmsList, useRemoveExternalKms } from "@app/hooks/api";
|
||||||
|
import { ExternalKmsProvider } from "@app/hooks/api/kms/types";
|
||||||
|
|
||||||
import { AddExternalKmsForm } from "./AddExternalKmsForm";
|
import { AddExternalKmsForm } from "./AddExternalKmsForm";
|
||||||
|
import { EditExternalKmsCredentialsModal } from "./EditExternalKmsCredentialsModal";
|
||||||
|
import { EditExternalKmsDetailsModal } from "./EditExternalKmsDetailsModal";
|
||||||
import { ExternalKmsItem } from "./ExternalKmsItem";
|
import { ExternalKmsItem } from "./ExternalKmsItem";
|
||||||
import { UpdateExternalKmsForm } from "./UpdateExternalKmsForm";
|
|
||||||
|
|
||||||
export const OrgEncryptionTab = withPermission(
|
export const OrgEncryptionTab = withPermission(
|
||||||
() => {
|
() => {
|
||||||
@@ -38,7 +40,8 @@ export const OrgEncryptionTab = withPermission(
|
|||||||
const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp([
|
const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp([
|
||||||
"upgradePlan",
|
"upgradePlan",
|
||||||
"addExternalKms",
|
"addExternalKms",
|
||||||
"editExternalKms",
|
"editExternalKmsDetails",
|
||||||
|
"editExternalKmsCredentials",
|
||||||
"removeExternalKms"
|
"removeExternalKms"
|
||||||
] as const);
|
] as const);
|
||||||
const { data: externalKmsList, isPending: isExternalKmsListLoading } =
|
const { data: externalKmsList, isPending: isExternalKmsListLoading } =
|
||||||
@@ -47,11 +50,12 @@ export const OrgEncryptionTab = withPermission(
|
|||||||
const { mutateAsync: removeExternalKms } = useRemoveExternalKms(currentOrg.id);
|
const { mutateAsync: removeExternalKms } = useRemoveExternalKms(currentOrg.id);
|
||||||
|
|
||||||
const handleRemoveExternalKms = async () => {
|
const handleRemoveExternalKms = async () => {
|
||||||
const { kmsId } = popUp?.removeExternalKms?.data as {
|
const { kmsId, provider } = popUp?.removeExternalKms?.data as {
|
||||||
kmsId: string;
|
kmsId: string;
|
||||||
|
provider: ExternalKmsProvider;
|
||||||
};
|
};
|
||||||
|
|
||||||
await removeExternalKms(kmsId);
|
await removeExternalKms({ kmsId, provider });
|
||||||
|
|
||||||
createNotification({
|
createNotification({
|
||||||
text: "Successfully deleted external KMS",
|
text: "Successfully deleted external KMS",
|
||||||
@@ -128,10 +132,21 @@ export const OrgEncryptionTab = withPermission(
|
|||||||
isOpen={popUp.addExternalKms.isOpen}
|
isOpen={popUp.addExternalKms.isOpen}
|
||||||
onToggle={(state) => handlePopUpToggle("addExternalKms", state)}
|
onToggle={(state) => handlePopUpToggle("addExternalKms", state)}
|
||||||
/>
|
/>
|
||||||
<UpdateExternalKmsForm
|
<EditExternalKmsDetailsModal
|
||||||
isOpen={popUp.editExternalKms.isOpen}
|
isOpen={popUp.editExternalKmsDetails.isOpen}
|
||||||
kmsId={(popUp.editExternalKms.data as { kmsId: string })?.kmsId}
|
kmsId={(popUp.editExternalKmsDetails.data as { kmsId: string })?.kmsId}
|
||||||
onOpenChange={(state) => handlePopUpToggle("editExternalKms", state)}
|
provider={
|
||||||
|
(popUp.editExternalKmsDetails.data as { provider: ExternalKmsProvider })?.provider
|
||||||
|
}
|
||||||
|
onOpenChange={(state) => handlePopUpToggle("editExternalKmsDetails", state)}
|
||||||
|
/>
|
||||||
|
<EditExternalKmsCredentialsModal
|
||||||
|
isOpen={popUp.editExternalKmsCredentials.isOpen}
|
||||||
|
kmsId={(popUp.editExternalKmsCredentials.data as { kmsId: string })?.kmsId}
|
||||||
|
provider={
|
||||||
|
(popUp.editExternalKmsCredentials.data as { provider: ExternalKmsProvider })?.provider
|
||||||
|
}
|
||||||
|
onOpenChange={(state) => handlePopUpToggle("editExternalKmsCredentials", state)}
|
||||||
/>
|
/>
|
||||||
<DeleteActionModal
|
<DeleteActionModal
|
||||||
isOpen={popUp.removeExternalKms.isOpen}
|
isOpen={popUp.removeExternalKms.isOpen}
|
||||||
|
|||||||
Reference in New Issue
Block a user