Skip to content

Commit 4e8cc30

Browse files
authored
feat: migrate aws kms flow to v5 (#626)
* feat: migrate aws kms flow to v5 * chore: revert hardcoded test backend wallet address * Shared signature logic + move function defintions inside body * remove twAddress override * feat: GCP KMS + refactor - use multiple wallet types simultaneously - resourcePath or ARN is source of truth: treat config AWS/GCP details as "default" - credentials override * add unit tests for kms accounts with prool * fix .env file ordering for tests * bugfix: split awn arn correctly * ignore engines globally (temp fix) * fix yarnrc * Address CR Comments * tests for AWS ARN and GCP KMS * always store kms credentials in WalletDetails * modify healthCheck function to include HETEROGENEOUS_WALLET_TYPES feature * remove bad comment * refactor invalid check --------- Signed-off-by: Prithvish Baidya <deformercoding@gmail.com>
1 parent 3015e63 commit 4e8cc30

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+2988
-500
lines changed

.env.test

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,13 @@ ENCRYPTION_PASSWORD="test"
55
ENABLE_KEYPAIR_AUTH="true"
66
ENABLE_HTTPS="true"
77
REDIS_URL="redis://127.0.0.1:6379/0"
8-
THIRDWEB_API_SECRET_KEY="my-thirdweb-secret-key"
8+
THIRDWEB_API_SECRET_KEY="my-thirdweb-secret-key"
9+
10+
TEST_AWS_KMS_KEY_ID=""
11+
TEST_AWS_KMS_ACCESS_KEY_ID=""
12+
TEST_AWS_KMS_SECRET_ACCESS_KEY=""
13+
TEST_AWS_KMS_REGION=""
14+
15+
TEST_GCP_KMS_RESOURCE_PATH=""
16+
TEST_GCP_KMS_EMAIL=""
17+
TEST_GCP_KMS_PK=""

.yarnrc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
--ignore-engines true

package.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@
2929
"dependencies": {
3030
"@aws-sdk/client-kms": "^3.398.0",
3131
"@bull-board/fastify": "^5.21.1",
32+
"@cloud-cryptographic-wallet/cloud-kms-signer": "^0.1.2",
33+
"@cloud-cryptographic-wallet/signer": "^0.0.5",
3234
"@fastify/basic-auth": "^5.1.1",
3335
"@fastify/cookie": "^8.3.0",
3436
"@fastify/express": "^2.3.0",
@@ -44,6 +46,7 @@
4446
"@thirdweb-dev/sdk": "^4.0.89",
4547
"@thirdweb-dev/service-utils": "^0.4.28",
4648
"@types/base-64": "^1.0.2",
49+
"aws-kms-signer": "^0.5.3",
4750
"base-64": "^1.0.0",
4851
"body-parser": "^1.20.2",
4952
"bullmq": "^5.11.0",
@@ -69,6 +72,7 @@
6972
"pg": "^8.11.3",
7073
"prisma": "^5.14.0",
7174
"prom-client": "^15.1.3",
75+
"prool": "^0.0.16",
7276
"superjson": "^2.2.1",
7377
"thirdweb": "^5.58.4",
7478
"uuid": "^9.0.1",

src/db/configuration/getConfiguration.ts

Lines changed: 53 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
1-
import { Configuration } from "@prisma/client";
2-
import { Static } from "@sinclair/typebox";
1+
import type { Configuration } from "@prisma/client";
2+
import type { Static } from "@sinclair/typebox";
33
import { LocalWallet } from "@thirdweb-dev/wallets";
44
import { ethers } from "ethers";
5-
import { Chain } from "thirdweb";
6-
import { ParsedConfig } from "../../schema/config";
5+
import type { Chain } from "thirdweb";
6+
import type {
7+
AwsWalletConfiguration,
8+
GcpWalletConfiguration,
9+
ParsedConfig,
10+
} from "../../schema/config";
711
import { WalletType } from "../../schema/wallet";
812
import { mandatoryAllowedCorsUrls } from "../../server/utils/cors-urls";
9-
import { networkResponseSchema } from "../../utils/cache/getSdk";
13+
import type { networkResponseSchema } from "../../utils/cache/getSdk";
1014
import { decrypt } from "../../utils/crypto";
1115
import { env } from "../../utils/env";
1216
import { logger } from "../../utils/logger";
@@ -53,6 +57,18 @@ const toParsedConfig = async (config: Configuration): Promise<ParsedConfig> => {
5357
}
5458
}
5559

60+
// LEGACY COMPATIBILITY
61+
// legacy behaviour was to check for these in order:
62+
// 1. AWS KMS Configuration - if found, wallet type is AWS KMS
63+
// 2. GCP KMS Configuration - if found, wallet type is GCP KMS
64+
// 3. If neither are found, wallet type is Local
65+
// to maintain compatibility where users expect to call create new backend wallet endpoint without an explicit wallet type
66+
// we need to preserve the wallet type in the configuration but only as the "default" wallet type
67+
let legacyWalletType_removeInNextBreakingChange: WalletType =
68+
WalletType.local;
69+
70+
let awsWalletConfiguration: AwsWalletConfiguration | null = null;
71+
5672
// TODO: Remove backwards compatibility with next breaking change
5773
if (awsAccessKeyId && awsSecretAccessKey && awsRegion) {
5874
// First try to load the aws secret using the encryption password
@@ -73,7 +89,8 @@ const toParsedConfig = async (config: Configuration): Promise<ParsedConfig> => {
7389
logger({
7490
service: "worker",
7591
level: "info",
76-
message: `[Encryption] Updating awsSecretAccessKey to use ENCRYPTION_PASSWORD`,
92+
message:
93+
"[Encryption] Updating awsSecretAccessKey to use ENCRYPTION_PASSWORD",
7794
});
7895

7996
await updateConfiguration({
@@ -85,28 +102,18 @@ const toParsedConfig = async (config: Configuration): Promise<ParsedConfig> => {
85102
// Renaming contractSubscriptionsRetryDelaySeconds
86103
// to contractSubscriptionsRequeryDelaySeconds to reflect its purpose
87104
// as we are requerying (& not retrying) with different delays
88-
return {
89-
...restConfig,
90-
contractSubscriptionsRequeryDelaySeconds:
91-
contractSubscriptionsRetryDelaySeconds,
92-
chainOverridesParsed,
93-
walletConfiguration: {
94-
type: WalletType.awsKms,
95-
awsRegion,
96-
awsAccessKeyId,
97-
awsSecretAccessKey: decryptedSecretAccessKey,
98-
},
105+
awsWalletConfiguration = {
106+
awsAccessKeyId,
107+
awsSecretAccessKey: decryptedSecretAccessKey,
108+
defaultAwsRegion: awsRegion,
99109
};
110+
111+
legacyWalletType_removeInNextBreakingChange = WalletType.awsKms;
100112
}
101113

114+
let gcpWalletConfiguration: GcpWalletConfiguration | null = null;
102115
// TODO: Remove backwards compatibility with next breaking change
103-
if (
104-
gcpApplicationProjectId &&
105-
gcpKmsLocationId &&
106-
gcpKmsKeyRingId &&
107-
gcpApplicationCredentialEmail &&
108-
gcpApplicationCredentialPrivateKey
109-
) {
116+
if (gcpApplicationCredentialEmail && gcpApplicationCredentialPrivateKey) {
110117
// First try to load the gcp secret using the encryption password
111118
let decryptedGcpKey = decrypt(
112119
gcpApplicationCredentialPrivateKey,
@@ -125,7 +132,8 @@ const toParsedConfig = async (config: Configuration): Promise<ParsedConfig> => {
125132
logger({
126133
service: "worker",
127134
level: "info",
128-
message: `[Encryption] Updating gcpApplicationCredentialPrivateKey to use ENCRYPTION_PASSWORD`,
135+
message:
136+
"[Encryption] Updating gcpApplicationCredentialPrivateKey to use ENCRYPTION_PASSWORD",
129137
});
130138

131139
await updateConfiguration({
@@ -134,20 +142,24 @@ const toParsedConfig = async (config: Configuration): Promise<ParsedConfig> => {
134142
}
135143
}
136144

137-
return {
138-
...restConfig,
139-
contractSubscriptionsRequeryDelaySeconds:
140-
contractSubscriptionsRetryDelaySeconds,
141-
chainOverridesParsed,
142-
walletConfiguration: {
143-
type: WalletType.gcpKms,
144-
gcpApplicationProjectId,
145-
gcpKmsLocationId,
146-
gcpKmsKeyRingId,
147-
gcpApplicationCredentialEmail,
148-
gcpApplicationCredentialPrivateKey: decryptedGcpKey,
149-
},
145+
if (!gcpKmsLocationId || !gcpKmsKeyRingId || !gcpApplicationProjectId) {
146+
throw new Error(
147+
"GCP KMS location ID, project ID, and key ring ID are required configuration for this wallet type",
148+
);
149+
}
150+
151+
gcpWalletConfiguration = {
152+
gcpApplicationCredentialEmail,
153+
gcpApplicationCredentialPrivateKey: decryptedGcpKey,
154+
155+
// TODO: Remove these with the next breaking change
156+
// These are used because import endpoint does not yet support GCP KMS resource path
157+
defaultGcpKmsLocationId: gcpKmsLocationId,
158+
defaultGcpKmsKeyRingId: gcpKmsKeyRingId,
159+
defaultGcpApplicationProjectId: gcpApplicationProjectId,
150160
};
161+
162+
legacyWalletType_removeInNextBreakingChange = WalletType.gcpKms;
151163
}
152164

153165
return {
@@ -156,7 +168,9 @@ const toParsedConfig = async (config: Configuration): Promise<ParsedConfig> => {
156168
contractSubscriptionsRetryDelaySeconds,
157169
chainOverridesParsed,
158170
walletConfiguration: {
159-
type: WalletType.local,
171+
aws: awsWalletConfiguration,
172+
gcp: gcpWalletConfiguration,
173+
legacyWalletType_removeInNextBreakingChange,
160174
},
161175
};
162176
};

src/db/wallets/createWalletDetails.ts

Lines changed: 50 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,33 @@
1-
import { PrismaTransaction } from "../../schema/prisma";
2-
import type { WalletType } from "../../schema/wallet";
1+
import type { PrismaTransaction } from "../../schema/prisma";
2+
import { encrypt } from "../../utils/crypto";
33
import { getPrismaWithPostgresTx } from "../client";
44

55
// TODO: Case on types by wallet type
6-
interface CreateWalletDetailsParams {
6+
type CreateWalletDetailsParams = {
77
pgtx?: PrismaTransaction;
88
address: string;
9-
type: WalletType;
109
label?: string;
11-
awsKmsKeyId?: string;
12-
awsKmsArn?: string;
13-
gcpKmsKeyRingId?: string;
14-
gcpKmsKeyId?: string;
15-
gcpKmsKeyVersionId?: string;
16-
gcpKmsLocationId?: string;
17-
gcpKmsResourcePath?: string;
18-
}
10+
} & (
11+
| {
12+
type: "aws-kms";
13+
awsKmsKeyId?: string; // depcrecated and unused, todo: remove with next breaking change
14+
awsKmsArn: string;
15+
16+
awsKmsSecretAccessKey?: string; // will be encrypted and stored, pass plaintext to this function
17+
awsKmsAccessKeyId?: string;
18+
}
19+
| {
20+
type: "gcp-kms";
21+
gcpKmsResourcePath: string;
22+
gcpKmsKeyRingId?: string; // depcrecated and unused, todo: remove with next breaking change
23+
gcpKmsKeyId?: string; // depcrecated and unused, todo: remove with next breaking change
24+
gcpKmsKeyVersionId?: string; // depcrecated and unused, todo: remove with next breaking change
25+
gcpKmsLocationId?: string; // depcrecated and unused, todo: remove with next breaking change
26+
27+
gcpApplicationCredentialPrivateKey?: string; // encrypted
28+
gcpApplicationCredentialEmail?: string;
29+
}
30+
);
1931

2032
export const createWalletDetails = async ({
2133
pgtx,
@@ -35,10 +47,30 @@ export const createWalletDetails = async ({
3547
);
3648
}
3749

38-
return prisma.walletDetails.create({
39-
data: {
40-
...walletDetails,
41-
address: walletDetails.address.toLowerCase(),
42-
},
43-
});
50+
if (walletDetails.type === "aws-kms") {
51+
return prisma.walletDetails.create({
52+
data: {
53+
...walletDetails,
54+
address: walletDetails.address.toLowerCase(),
55+
56+
awsKmsSecretAccessKey: walletDetails.awsKmsSecretAccessKey
57+
? encrypt(walletDetails.awsKmsSecretAccessKey)
58+
: undefined,
59+
},
60+
});
61+
}
62+
63+
if (walletDetails.type === "gcp-kms") {
64+
return prisma.walletDetails.create({
65+
data: {
66+
...walletDetails,
67+
address: walletDetails.address.toLowerCase(),
68+
69+
gcpApplicationCredentialPrivateKey:
70+
walletDetails.gcpApplicationCredentialPrivateKey
71+
? encrypt(walletDetails.gcpApplicationCredentialPrivateKey)
72+
: undefined,
73+
},
74+
});
75+
}
4476
};

src/db/wallets/getWalletDetails.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { PrismaTransaction } from "../../schema/prisma";
1+
import type { PrismaTransaction } from "../../schema/prisma";
22
import { getPrismaWithPostgresTx } from "../client";
33

44
interface GetWalletDetailsParams {
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
-- AlterTable
2+
ALTER TABLE "wallet_details" ADD COLUMN "awsKmsAccessKeyId" TEXT,
3+
ADD COLUMN "awsKmsSecretAccessKey" TEXT,
4+
ADD COLUMN "gcpApplicationCredentialEmail" TEXT,
5+
ADD COLUMN "gcpApplicationCredentialPrivateKey" TEXT;

src/prisma/schema.prisma

Lines changed: 23 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -30,15 +30,15 @@ model Configuration {
3030
contractSubscriptionsRetryDelaySeconds String @default("10") @map("contractSubscriptionsRetryDelaySeconds")
3131
3232
// AWS
33-
awsAccessKeyId String? @map("awsAccessKeyId")
34-
awsSecretAccessKey String? @map("awsSecretAccessKey")
35-
awsRegion String? @map("awsRegion")
33+
awsAccessKeyId String? @map("awsAccessKeyId") /// global config, precedence goes to WalletDetails
34+
awsSecretAccessKey String? @map("awsSecretAccessKey") /// global config, precedence goes to WalletDetails
35+
awsRegion String? @map("awsRegion") /// global config, treat as "default", store in WalletDetails.awsKmsArn
3636
// GCP
37-
gcpApplicationProjectId String? @map("gcpApplicationProjectId")
38-
gcpKmsLocationId String? @map("gcpKmsLocationId")
39-
gcpKmsKeyRingId String? @map("gcpKmsKeyRingId")
40-
gcpApplicationCredentialEmail String? @map("gcpApplicationCredentialEmail")
41-
gcpApplicationCredentialPrivateKey String? @map("gcpApplicationCredentialPrivateKey")
37+
gcpApplicationProjectId String? @map("gcpApplicationProjectId") /// global config, treat as "default", store in WalletDetails.gcpKmsResourcePath
38+
gcpKmsLocationId String? @map("gcpKmsLocationId") /// global config, treat as "default", store in WalletDetails.gcpKmsResourcePath
39+
gcpKmsKeyRingId String? @map("gcpKmsKeyRingId") /// global config, treat as "default", store in WalletDetails.gcpKmsResourcePath
40+
gcpApplicationCredentialEmail String? @map("gcpApplicationCredentialEmail") /// global config, precedence goes to WalletDetails
41+
gcpApplicationCredentialPrivateKey String? @map("gcpApplicationCredentialPrivateKey") /// global config, precedence goes to WalletDetails
4242
// Auth
4343
authDomain String @default("") @map("authDomain") // TODO: Remove defaults on major
4444
authWalletEncryptedJson String @default("") @map("authWalletEncryptedJson") // TODO: Remove defaults on major
@@ -76,20 +76,24 @@ model Tokens {
7676
}
7777

7878
model WalletDetails {
79-
address String @id @map("address")
80-
type String @map("type")
81-
label String? @map("label")
79+
address String @id @map("address")
80+
type String @map("type")
81+
label String? @map("label")
8282
// Local
83-
encryptedJson String? @map("encryptedJson")
83+
encryptedJson String? @map("encryptedJson")
8484
// KMS
85-
awsKmsKeyId String? @map("awsKmsKeyId")
86-
awsKmsArn String? @map("awsKmsArn")
85+
awsKmsKeyId String? @map("awsKmsKeyId") /// deprecated and unused, todo: remove with next breaking change. Use awsKmsArn
86+
awsKmsArn String? @map("awsKmsArn")
87+
awsKmsSecretAccessKey String? @map("awsKmsSecretAccessKey") /// if not available, default to: Configuration.awsSecretAccessKey
88+
awsKmsAccessKeyId String? @map("awsKmsAccessKeyId") /// if not available, default to: Configuration.awsAccessKeyId
8789
// GCP
88-
gcpKmsKeyRingId String? @map("gcpKmsKeyRingId") @db.VarChar(50)
89-
gcpKmsKeyId String? @map("gcpKmsKeyId") @db.VarChar(50)
90-
gcpKmsKeyVersionId String? @map("gcpKmsKeyVersionId") @db.VarChar(20)
91-
gcpKmsLocationId String? @map("gcpKmsLocationId") @db.VarChar(20)
92-
gcpKmsResourcePath String? @map("gcpKmsResourcePath") @db.Text
90+
gcpKmsKeyRingId String? @map("gcpKmsKeyRingId") @db.VarChar(50) /// deprecated and unused. Use gcpKmsResourcePath instead, todo: remove with next breaking change
91+
gcpKmsKeyId String? @map("gcpKmsKeyId") @db.VarChar(50) /// deprecated and unused. Use gcpKmsResourcePath instead, todo: remove with next breaking change
92+
gcpKmsKeyVersionId String? @map("gcpKmsKeyVersionId") @db.VarChar(20) /// deprecated and unused. Use gcpKmsResourcePath instead, todo: remove with next breaking change
93+
gcpKmsLocationId String? @map("gcpKmsLocationId") @db.VarChar(20) /// deprecated and unused. Use gcpKmsResourcePath instead, todo: remove with next breaking change
94+
gcpKmsResourcePath String? @map("gcpKmsResourcePath") @db.Text
95+
gcpApplicationCredentialEmail String? @map("gcpApplicationCredentialEmail") /// if not available, default to: Configuration.gcpApplicationCredentialEmail
96+
gcpApplicationCredentialPrivateKey String? @map("gcpApplicationCredentialPrivateKey") /// if not available, default to: Configuration.gcpApplicationCredentialPrivateKey
9397
9498
@@map("wallet_details")
9599
}

0 commit comments

Comments
 (0)