Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 7 additions & 4 deletions backend/src/ee/routes/v1/external-kms-router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ import { z } from "zod";
import { ExternalKmsSchema, KmsKeysSchema } from "@app/db/schemas";
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import {
ExternalKmsAwsSchema,
ExternalKmsGcpSchema,
ExternalKmsInputSchema,
ExternalKmsInputUpdateSchema
ExternalKmsInputUpdateSchema,
SanitizedExternalKmsAwsSchema
} from "@app/ee/services/external-kms/providers/model";
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
Expand Down Expand Up @@ -45,8 +45,11 @@ const sanitizedExternalSchemaForGetById = KmsKeysSchema.extend({
statusDetails: true,
provider: true
}).extend({
// for GCP, we don't return the credential object as it is sensitive data that should not be exposed
providerInput: z.union([ExternalKmsAwsSchema, ExternalKmsGcpSchema.pick({ gcpRegion: true, keyName: true })])
// neither provider returns sensitive credential material in read responses: AWS via the sanitized schema (access key only, no secret key), GCP via the picked fields only
providerInput: z.union([
SanitizedExternalKmsAwsSchema,
ExternalKmsGcpSchema.pick({ gcpRegion: true, keyName: true })
])
})
});

Expand Down
51 changes: 51 additions & 0 deletions backend/src/ee/services/external-kms/providers/model.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { describe, expect, test } from "vitest";

import { ExternalKmsAwsSchema, KmsAwsCredentialType, SanitizedExternalKmsAwsSchema } from "./model";

describe("SanitizedExternalKmsAwsSchema (external-KMS read response)", () => {
const awsAccessKeyInput = {
credential: {
type: KmsAwsCredentialType.AccessKey,
data: {
accessKey: "AKIAEXAMPLE",
secretKey: "super-secret-value"
}
},
awsRegion: "us-east-1",
kmsKeyId: "key-123"
};

test("strips secretKey from an access-key credential on read", () => {
const parsed = SanitizedExternalKmsAwsSchema.parse(awsAccessKeyInput);

expect(parsed.credential.type).toBe(KmsAwsCredentialType.AccessKey);
expect(parsed.credential.data).toHaveProperty("accessKey", "AKIAEXAMPLE");
expect(parsed.credential.data).not.toHaveProperty("secretKey");
// the secret value must not survive serialization anywhere in the response
expect(JSON.stringify(parsed)).not.toContain("super-secret-value");
});

test("negative control: the pre-fix unsanitized schema would have returned secretKey", () => {
const parsed = ExternalKmsAwsSchema.parse(awsAccessKeyInput);

expect(parsed.credential.data).toHaveProperty("secretKey", "super-secret-value");
});

test("retains assume-role identifiers (that branch carries no secret material)", () => {
const parsed = SanitizedExternalKmsAwsSchema.parse({
credential: {
type: KmsAwsCredentialType.AssumeRole,
data: {
assumeRoleArn: "arn:aws:iam::123456789012:role/infisical",
externalId: "ext-1"
}
},
awsRegion: "us-east-1"
});

expect(parsed.credential.data).toMatchObject({
assumeRoleArn: "arn:aws:iam::123456789012:role/infisical",
externalId: "ext-1"
});
});
});
Loading