@noy-db/at-gcp-kms 0.2.0-pre.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +92 -0
- package/dist/index.cjs +54 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +98 -0
- package/dist/index.d.ts +98 -0
- package/dist/index.js +29 -0
- package/dist/index.js.map +1 -0
- package/package.json +72 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 vLannaAi
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# @noy-db/at-gcp-kms
|
|
2
|
+
|
|
3
|
+
**Google Cloud KMS sealing key provider for noy-db [managed-passphrase mode](https://github.com/vLannaAi/noy-db/issues/14).**
|
|
4
|
+
|
|
5
|
+
An `at-*` provider that seals and unseals the hub-generated random passphrase via Google Cloud KMS Encrypt / Decrypt. Every seal and unseal is an authenticated KMS API call — giving you a Cloud Audit Logs-backed access log of every time a user's vault is opened, with no additional instrumentation required.
|
|
6
|
+
|
|
7
|
+
Like all `at-*` providers, this is a *trusted host* provider: the host you deploy it on CAN decrypt what it unseals. The security boundary is your GCP IAM policy — access is controlled by which service accounts hold `roles/cloudkms.cryptoKeyEncrypterDecrypter` on the KMS key, not by a secret the host keeps in memory.
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
pnpm add @noy-db/hub @noy-db/at-gcp-kms @noy-db/on-shamir
|
|
13
|
+
# or: npm install @noy-db/hub @noy-db/at-gcp-kms @noy-db/on-shamir
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Setup
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
# 1. Create a key ring and symmetric crypto key once:
|
|
20
|
+
gcloud kms keyrings create noy-db-ring --location global
|
|
21
|
+
gcloud kms keys create noy-db-sealing \
|
|
22
|
+
--location global \
|
|
23
|
+
--keyring noy-db-ring \
|
|
24
|
+
--purpose encryption
|
|
25
|
+
# Note the full resource name from the output.
|
|
26
|
+
|
|
27
|
+
# 2. Grant your host's service account the Cloud KMS CryptoKey Encrypter/Decrypter role:
|
|
28
|
+
gcloud kms keys add-iam-policy-binding noy-db-sealing \
|
|
29
|
+
--location global \
|
|
30
|
+
--keyring noy-db-ring \
|
|
31
|
+
--member serviceAccount:HOST_SA@PROJECT.iam.gserviceaccount.com \
|
|
32
|
+
--role roles/cloudkms.cryptoKeyEncrypterDecrypter
|
|
33
|
+
# Credentials are resolved automatically via Application Default Credentials (ADC):
|
|
34
|
+
# service account attached to GCE/GKE node, Workload Identity for GKE,
|
|
35
|
+
# GOOGLE_APPLICATION_CREDENTIALS env var, or `gcloud auth application-default login`
|
|
36
|
+
# for local dev.
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
```ts
|
|
40
|
+
// 3. In your app:
|
|
41
|
+
import { createNoydb } from '@noy-db/hub'
|
|
42
|
+
import { gcpKmsSealingProvider } from '@noy-db/at-gcp-kms'
|
|
43
|
+
import { shamirRecoveryProvider } from '@noy-db/on-shamir'
|
|
44
|
+
|
|
45
|
+
const db = await createNoydb({
|
|
46
|
+
store,
|
|
47
|
+
user: 'alice',
|
|
48
|
+
passphraseMode: 'managed',
|
|
49
|
+
sealingKey: gcpKmsSealingProvider({
|
|
50
|
+
keyName: 'projects/my-project/locations/global/keyRings/noy-db-ring/cryptoKeys/noy-db-sealing',
|
|
51
|
+
}),
|
|
52
|
+
shamirRecovery: shamirRecoveryProvider(),
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
const vault = await db.openVault('acme')
|
|
56
|
+
// Hub generated a 256-bit random on first open, sealed it via KMS Encrypt,
|
|
57
|
+
// and persisted to _meta/sealed-passphrase. The user never sees a passphrase.
|
|
58
|
+
// On reopen, at-gcp-kms calls KMS Decrypt transparently.
|
|
59
|
+
// Cloud Audit Logs record every Encrypt/Decrypt call with caller identity + key resource name.
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## When to use this provider
|
|
63
|
+
|
|
64
|
+
- Compliance regimes requiring auditable key access logs (FedRAMP, HIPAA with managed-encryption requirements, SOC 2 Type II).
|
|
65
|
+
- Workloads already running on GCP where a Cloud KMS key costs less than engineering an equivalent audit trail.
|
|
66
|
+
- Any case where you want automatic key version rotation without rotating app-side key material.
|
|
67
|
+
|
|
68
|
+
## When NOT to use this provider
|
|
69
|
+
|
|
70
|
+
- Non-GCP or multi-cloud deployments where adding a GCP dependency is undesirable. Use [`@noy-db/at-env`](../at-env) for a zero-extra-dependency option.
|
|
71
|
+
- Local dev / CI where you don't want real KMS calls or GCP credentials in CI. Use [`@noy-db/at-env`](../at-env) or `MemorySealingKeyProvider` from `@noy-db/hub` instead.
|
|
72
|
+
|
|
73
|
+
## Key rotation
|
|
74
|
+
|
|
75
|
+
Cloud KMS supports automatic key version rotation for symmetric keys. Enable it on the crypto key and KMS handles the rest — your `keyName` stays the same, no app changes needed. Cross-key migration (moving sealed passphrases to a different key) requires manual re-sealing with `unseal` + `seal` under the new key.
|
|
76
|
+
|
|
77
|
+
## API
|
|
78
|
+
|
|
79
|
+
```ts
|
|
80
|
+
function gcpKmsSealingProvider(opts: {
|
|
81
|
+
keyName: string // Full crypto-key resource name
|
|
82
|
+
client?: KmsClientLike // optional pre-built client (useful for tests)
|
|
83
|
+
}): SealingKeyProvider
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Never pass raw GCP credentials in the options — inject a pre-configured `KeyManagementServiceClient` for non-default auth. The default `new KeyManagementServiceClient()` resolves credentials via Application Default Credentials.
|
|
87
|
+
|
|
88
|
+
Returns a [`SealingKeyProvider`](../hub/src/team/managed-passphrase.ts) — the contract `@noy-db/hub`'s managed-passphrase mode consumes.
|
|
89
|
+
|
|
90
|
+
## License
|
|
91
|
+
|
|
92
|
+
MIT
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
gcpKmsSealingProvider: () => gcpKmsSealingProvider
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(index_exports);
|
|
26
|
+
var import_kms = require("@google-cloud/kms");
|
|
27
|
+
function toUint8Array(value) {
|
|
28
|
+
if (value == null) return void 0;
|
|
29
|
+
if (value instanceof Uint8Array) return value;
|
|
30
|
+
return Buffer.from(value, "base64");
|
|
31
|
+
}
|
|
32
|
+
function gcpKmsSealingProvider(opts) {
|
|
33
|
+
const client = opts.client ?? new import_kms.KeyManagementServiceClient();
|
|
34
|
+
return {
|
|
35
|
+
id: `gcp-kms:${opts.keyName}`,
|
|
36
|
+
async seal(passphrase) {
|
|
37
|
+
const [resp] = await client.encrypt({ name: opts.keyName, plaintext: passphrase });
|
|
38
|
+
const blob = toUint8Array(resp?.ciphertext);
|
|
39
|
+
if (!blob) throw new Error("@noy-db/at-gcp-kms: KMS encrypt returned no ciphertext");
|
|
40
|
+
return blob;
|
|
41
|
+
},
|
|
42
|
+
async unseal(sealed) {
|
|
43
|
+
const [resp] = await client.decrypt({ name: opts.keyName, ciphertext: sealed });
|
|
44
|
+
const pt = toUint8Array(resp?.plaintext);
|
|
45
|
+
if (!pt) throw new Error("@noy-db/at-gcp-kms: KMS decrypt returned no plaintext");
|
|
46
|
+
return pt;
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
51
|
+
0 && (module.exports = {
|
|
52
|
+
gcpKmsSealingProvider
|
|
53
|
+
});
|
|
54
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * **@noy-db/at-gcp-kms** — Google Cloud KMS sealing key provider for noy-db\n * managed-passphrase mode (#189).\n *\n * An `at-*` provider that seals and unseals the hub-generated random\n * passphrase via Google Cloud KMS Encrypt / Decrypt. Every seal and unseal is\n * an authenticated KMS API call, giving you a Cloud Audit Logs-backed access\n * log of every time a user's vault is opened — no additional instrumentation\n * required.\n *\n * ## When to use\n *\n * - Compliance regimes requiring auditable key access logs (FedRAMP, HIPAA\n * with managed-encryption requirements, SOC 2 Type II).\n * - Workloads already running on GCP where a Cloud KMS key costs less than\n * engineering an equivalent audit trail.\n * - Any case where you want automatic key rotation without rotating your\n * app's sealing key material manually.\n *\n * ## Setup\n *\n * ```bash\n * # 1. Create a key ring and symmetric crypto key once:\n * gcloud kms keyrings create noy-db-ring --location global\n * gcloud kms keys create noy-db-sealing \\\n * --location global \\\n * --keyring noy-db-ring \\\n * --purpose encryption\n * # Note the full resource name from the output.\n *\n * # 2. Grant your host's service account the Cloud KMS CryptoKey Encrypter/Decrypter role:\n * gcloud kms keys add-iam-policy-binding noy-db-sealing \\\n * --location global \\\n * --keyring noy-db-ring \\\n * --member serviceAccount:HOST_SA@PROJECT.iam.gserviceaccount.com \\\n * --role roles/cloudkms.cryptoKeyEncrypterDecrypter\n * # Credentials are picked up automatically via Application Default Credentials\n * # (ADC): service account attached to GCE/GKE, GOOGLE_APPLICATION_CREDENTIALS\n * # env var, or `gcloud auth application-default login` for local dev.\n * ```\n *\n * ```ts\n * // 3. In your app:\n * import { createNoydb } from '@noy-db/hub'\n * import { gcpKmsSealingProvider } from '@noy-db/at-gcp-kms'\n * import { shamirRecoveryProvider } from '@noy-db/on-shamir'\n *\n * const db = await createNoydb({\n * store,\n * user: 'alice',\n * passphraseMode: 'managed',\n * sealingKey: gcpKmsSealingProvider({\n * keyName: 'projects/my-project/locations/global/keyRings/noy-db-ring/cryptoKeys/noy-db-sealing',\n * }),\n * shamirRecovery: shamirRecoveryProvider(),\n * })\n * ```\n *\n * @packageDocumentation\n */\n\nimport type { SealingKeyProvider } from '@noy-db/hub'\nimport { KeyManagementServiceClient } from '@google-cloud/kms'\nimport type { protos } from '@google-cloud/kms'\n\ntype IEncryptRequest = protos.google.cloud.kms.v1.IEncryptRequest\ntype IDecryptRequest = protos.google.cloud.kms.v1.IDecryptRequest\ntype IEncryptResponse = protos.google.cloud.kms.v1.IEncryptResponse\ntype IDecryptResponse = protos.google.cloud.kms.v1.IDecryptResponse\n\n/** Minimal client surface required by {@link gcpKmsSealingProvider}. */\ninterface KmsClientLike {\n encrypt(request: IEncryptRequest): Promise<[IEncryptResponse, IEncryptRequest | undefined, unknown]>\n decrypt(request: IDecryptRequest): Promise<[IDecryptResponse, IDecryptRequest | undefined, unknown]>\n}\n\n/** Options for {@link gcpKmsSealingProvider}. */\nexport interface GcpKmsSealingProviderOptions {\n /**\n * Full crypto-key resource name, e.g.\n * `projects/PROJECT/locations/LOCATION/keyRings/RING/cryptoKeys/KEY`.\n */\n readonly keyName: string\n /** Optional pre-built client (DI for tests). Default: `new KeyManagementServiceClient()` (ambient ADC). */\n readonly client?: KmsClientLike\n}\n\nfunction toUint8Array(value: Uint8Array | string | null | undefined): Uint8Array | undefined {\n if (value == null) return undefined\n if (value instanceof Uint8Array) return value\n // protobuf may return a base64 string in some environments\n return Buffer.from(value, 'base64')\n}\n\n/**\n * Build a {@link SealingKeyProvider} backed by Google Cloud KMS Encrypt / Decrypt.\n *\n * Credentials are resolved via Application Default Credentials (ADC) — attached\n * service accounts, `GOOGLE_APPLICATION_CREDENTIALS`, or local gcloud login.\n * Never pass raw credentials in the options; inject a pre-configured client for\n * non-default auth instead.\n *\n * @throws Error when KMS returns no ciphertext or no plaintext (guards\n * against unexpected SDK-response shapes).\n * Any KMS API error (PermissionDenied, NotFound, etc.) propagates as-is.\n */\nexport function gcpKmsSealingProvider(opts: GcpKmsSealingProviderOptions): SealingKeyProvider {\n const client: KmsClientLike = opts.client ?? new KeyManagementServiceClient()\n return {\n id: `gcp-kms:${opts.keyName}`,\n\n async seal(passphrase) {\n const [resp] = await client.encrypt({ name: opts.keyName, plaintext: passphrase })\n const blob = toUint8Array(resp?.ciphertext)\n if (!blob) throw new Error('@noy-db/at-gcp-kms: KMS encrypt returned no ciphertext')\n return blob\n },\n\n async unseal(sealed) {\n const [resp] = await client.decrypt({ name: opts.keyName, ciphertext: sealed })\n const pt = toUint8Array(resp?.plaintext)\n if (!pt) throw new Error('@noy-db/at-gcp-kms: KMS decrypt returned no plaintext')\n return pt\n },\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AA8DA,iBAA2C;AAyB3C,SAAS,aAAa,OAAuE;AAC3F,MAAI,SAAS,KAAM,QAAO;AAC1B,MAAI,iBAAiB,WAAY,QAAO;AAExC,SAAO,OAAO,KAAK,OAAO,QAAQ;AACpC;AAcO,SAAS,sBAAsB,MAAwD;AAC5F,QAAM,SAAwB,KAAK,UAAU,IAAI,sCAA2B;AAC5E,SAAO;AAAA,IACL,IAAI,WAAW,KAAK,OAAO;AAAA,IAE3B,MAAM,KAAK,YAAY;AACrB,YAAM,CAAC,IAAI,IAAI,MAAM,OAAO,QAAQ,EAAE,MAAM,KAAK,SAAS,WAAW,WAAW,CAAC;AACjF,YAAM,OAAO,aAAa,MAAM,UAAU;AAC1C,UAAI,CAAC,KAAM,OAAM,IAAI,MAAM,wDAAwD;AACnF,aAAO;AAAA,IACT;AAAA,IAEA,MAAM,OAAO,QAAQ;AACnB,YAAM,CAAC,IAAI,IAAI,MAAM,OAAO,QAAQ,EAAE,MAAM,KAAK,SAAS,YAAY,OAAO,CAAC;AAC9E,YAAM,KAAK,aAAa,MAAM,SAAS;AACvC,UAAI,CAAC,GAAI,OAAM,IAAI,MAAM,uDAAuD;AAChF,aAAO;AAAA,IACT;AAAA,EACF;AACF;","names":[]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { SealingKeyProvider } from '@noy-db/hub';
|
|
2
|
+
import { protos } from '@google-cloud/kms';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* **@noy-db/at-gcp-kms** — Google Cloud KMS sealing key provider for noy-db
|
|
6
|
+
* managed-passphrase mode (#189).
|
|
7
|
+
*
|
|
8
|
+
* An `at-*` provider that seals and unseals the hub-generated random
|
|
9
|
+
* passphrase via Google Cloud KMS Encrypt / Decrypt. Every seal and unseal is
|
|
10
|
+
* an authenticated KMS API call, giving you a Cloud Audit Logs-backed access
|
|
11
|
+
* log of every time a user's vault is opened — no additional instrumentation
|
|
12
|
+
* required.
|
|
13
|
+
*
|
|
14
|
+
* ## When to use
|
|
15
|
+
*
|
|
16
|
+
* - Compliance regimes requiring auditable key access logs (FedRAMP, HIPAA
|
|
17
|
+
* with managed-encryption requirements, SOC 2 Type II).
|
|
18
|
+
* - Workloads already running on GCP where a Cloud KMS key costs less than
|
|
19
|
+
* engineering an equivalent audit trail.
|
|
20
|
+
* - Any case where you want automatic key rotation without rotating your
|
|
21
|
+
* app's sealing key material manually.
|
|
22
|
+
*
|
|
23
|
+
* ## Setup
|
|
24
|
+
*
|
|
25
|
+
* ```bash
|
|
26
|
+
* # 1. Create a key ring and symmetric crypto key once:
|
|
27
|
+
* gcloud kms keyrings create noy-db-ring --location global
|
|
28
|
+
* gcloud kms keys create noy-db-sealing \
|
|
29
|
+
* --location global \
|
|
30
|
+
* --keyring noy-db-ring \
|
|
31
|
+
* --purpose encryption
|
|
32
|
+
* # Note the full resource name from the output.
|
|
33
|
+
*
|
|
34
|
+
* # 2. Grant your host's service account the Cloud KMS CryptoKey Encrypter/Decrypter role:
|
|
35
|
+
* gcloud kms keys add-iam-policy-binding noy-db-sealing \
|
|
36
|
+
* --location global \
|
|
37
|
+
* --keyring noy-db-ring \
|
|
38
|
+
* --member serviceAccount:HOST_SA@PROJECT.iam.gserviceaccount.com \
|
|
39
|
+
* --role roles/cloudkms.cryptoKeyEncrypterDecrypter
|
|
40
|
+
* # Credentials are picked up automatically via Application Default Credentials
|
|
41
|
+
* # (ADC): service account attached to GCE/GKE, GOOGLE_APPLICATION_CREDENTIALS
|
|
42
|
+
* # env var, or `gcloud auth application-default login` for local dev.
|
|
43
|
+
* ```
|
|
44
|
+
*
|
|
45
|
+
* ```ts
|
|
46
|
+
* // 3. In your app:
|
|
47
|
+
* import { createNoydb } from '@noy-db/hub'
|
|
48
|
+
* import { gcpKmsSealingProvider } from '@noy-db/at-gcp-kms'
|
|
49
|
+
* import { shamirRecoveryProvider } from '@noy-db/on-shamir'
|
|
50
|
+
*
|
|
51
|
+
* const db = await createNoydb({
|
|
52
|
+
* store,
|
|
53
|
+
* user: 'alice',
|
|
54
|
+
* passphraseMode: 'managed',
|
|
55
|
+
* sealingKey: gcpKmsSealingProvider({
|
|
56
|
+
* keyName: 'projects/my-project/locations/global/keyRings/noy-db-ring/cryptoKeys/noy-db-sealing',
|
|
57
|
+
* }),
|
|
58
|
+
* shamirRecovery: shamirRecoveryProvider(),
|
|
59
|
+
* })
|
|
60
|
+
* ```
|
|
61
|
+
*
|
|
62
|
+
* @packageDocumentation
|
|
63
|
+
*/
|
|
64
|
+
|
|
65
|
+
type IEncryptRequest = protos.google.cloud.kms.v1.IEncryptRequest;
|
|
66
|
+
type IDecryptRequest = protos.google.cloud.kms.v1.IDecryptRequest;
|
|
67
|
+
type IEncryptResponse = protos.google.cloud.kms.v1.IEncryptResponse;
|
|
68
|
+
type IDecryptResponse = protos.google.cloud.kms.v1.IDecryptResponse;
|
|
69
|
+
/** Minimal client surface required by {@link gcpKmsSealingProvider}. */
|
|
70
|
+
interface KmsClientLike {
|
|
71
|
+
encrypt(request: IEncryptRequest): Promise<[IEncryptResponse, IEncryptRequest | undefined, unknown]>;
|
|
72
|
+
decrypt(request: IDecryptRequest): Promise<[IDecryptResponse, IDecryptRequest | undefined, unknown]>;
|
|
73
|
+
}
|
|
74
|
+
/** Options for {@link gcpKmsSealingProvider}. */
|
|
75
|
+
interface GcpKmsSealingProviderOptions {
|
|
76
|
+
/**
|
|
77
|
+
* Full crypto-key resource name, e.g.
|
|
78
|
+
* `projects/PROJECT/locations/LOCATION/keyRings/RING/cryptoKeys/KEY`.
|
|
79
|
+
*/
|
|
80
|
+
readonly keyName: string;
|
|
81
|
+
/** Optional pre-built client (DI for tests). Default: `new KeyManagementServiceClient()` (ambient ADC). */
|
|
82
|
+
readonly client?: KmsClientLike;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Build a {@link SealingKeyProvider} backed by Google Cloud KMS Encrypt / Decrypt.
|
|
86
|
+
*
|
|
87
|
+
* Credentials are resolved via Application Default Credentials (ADC) — attached
|
|
88
|
+
* service accounts, `GOOGLE_APPLICATION_CREDENTIALS`, or local gcloud login.
|
|
89
|
+
* Never pass raw credentials in the options; inject a pre-configured client for
|
|
90
|
+
* non-default auth instead.
|
|
91
|
+
*
|
|
92
|
+
* @throws Error when KMS returns no ciphertext or no plaintext (guards
|
|
93
|
+
* against unexpected SDK-response shapes).
|
|
94
|
+
* Any KMS API error (PermissionDenied, NotFound, etc.) propagates as-is.
|
|
95
|
+
*/
|
|
96
|
+
declare function gcpKmsSealingProvider(opts: GcpKmsSealingProviderOptions): SealingKeyProvider;
|
|
97
|
+
|
|
98
|
+
export { type GcpKmsSealingProviderOptions, gcpKmsSealingProvider };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { SealingKeyProvider } from '@noy-db/hub';
|
|
2
|
+
import { protos } from '@google-cloud/kms';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* **@noy-db/at-gcp-kms** — Google Cloud KMS sealing key provider for noy-db
|
|
6
|
+
* managed-passphrase mode (#189).
|
|
7
|
+
*
|
|
8
|
+
* An `at-*` provider that seals and unseals the hub-generated random
|
|
9
|
+
* passphrase via Google Cloud KMS Encrypt / Decrypt. Every seal and unseal is
|
|
10
|
+
* an authenticated KMS API call, giving you a Cloud Audit Logs-backed access
|
|
11
|
+
* log of every time a user's vault is opened — no additional instrumentation
|
|
12
|
+
* required.
|
|
13
|
+
*
|
|
14
|
+
* ## When to use
|
|
15
|
+
*
|
|
16
|
+
* - Compliance regimes requiring auditable key access logs (FedRAMP, HIPAA
|
|
17
|
+
* with managed-encryption requirements, SOC 2 Type II).
|
|
18
|
+
* - Workloads already running on GCP where a Cloud KMS key costs less than
|
|
19
|
+
* engineering an equivalent audit trail.
|
|
20
|
+
* - Any case where you want automatic key rotation without rotating your
|
|
21
|
+
* app's sealing key material manually.
|
|
22
|
+
*
|
|
23
|
+
* ## Setup
|
|
24
|
+
*
|
|
25
|
+
* ```bash
|
|
26
|
+
* # 1. Create a key ring and symmetric crypto key once:
|
|
27
|
+
* gcloud kms keyrings create noy-db-ring --location global
|
|
28
|
+
* gcloud kms keys create noy-db-sealing \
|
|
29
|
+
* --location global \
|
|
30
|
+
* --keyring noy-db-ring \
|
|
31
|
+
* --purpose encryption
|
|
32
|
+
* # Note the full resource name from the output.
|
|
33
|
+
*
|
|
34
|
+
* # 2. Grant your host's service account the Cloud KMS CryptoKey Encrypter/Decrypter role:
|
|
35
|
+
* gcloud kms keys add-iam-policy-binding noy-db-sealing \
|
|
36
|
+
* --location global \
|
|
37
|
+
* --keyring noy-db-ring \
|
|
38
|
+
* --member serviceAccount:HOST_SA@PROJECT.iam.gserviceaccount.com \
|
|
39
|
+
* --role roles/cloudkms.cryptoKeyEncrypterDecrypter
|
|
40
|
+
* # Credentials are picked up automatically via Application Default Credentials
|
|
41
|
+
* # (ADC): service account attached to GCE/GKE, GOOGLE_APPLICATION_CREDENTIALS
|
|
42
|
+
* # env var, or `gcloud auth application-default login` for local dev.
|
|
43
|
+
* ```
|
|
44
|
+
*
|
|
45
|
+
* ```ts
|
|
46
|
+
* // 3. In your app:
|
|
47
|
+
* import { createNoydb } from '@noy-db/hub'
|
|
48
|
+
* import { gcpKmsSealingProvider } from '@noy-db/at-gcp-kms'
|
|
49
|
+
* import { shamirRecoveryProvider } from '@noy-db/on-shamir'
|
|
50
|
+
*
|
|
51
|
+
* const db = await createNoydb({
|
|
52
|
+
* store,
|
|
53
|
+
* user: 'alice',
|
|
54
|
+
* passphraseMode: 'managed',
|
|
55
|
+
* sealingKey: gcpKmsSealingProvider({
|
|
56
|
+
* keyName: 'projects/my-project/locations/global/keyRings/noy-db-ring/cryptoKeys/noy-db-sealing',
|
|
57
|
+
* }),
|
|
58
|
+
* shamirRecovery: shamirRecoveryProvider(),
|
|
59
|
+
* })
|
|
60
|
+
* ```
|
|
61
|
+
*
|
|
62
|
+
* @packageDocumentation
|
|
63
|
+
*/
|
|
64
|
+
|
|
65
|
+
type IEncryptRequest = protos.google.cloud.kms.v1.IEncryptRequest;
|
|
66
|
+
type IDecryptRequest = protos.google.cloud.kms.v1.IDecryptRequest;
|
|
67
|
+
type IEncryptResponse = protos.google.cloud.kms.v1.IEncryptResponse;
|
|
68
|
+
type IDecryptResponse = protos.google.cloud.kms.v1.IDecryptResponse;
|
|
69
|
+
/** Minimal client surface required by {@link gcpKmsSealingProvider}. */
|
|
70
|
+
interface KmsClientLike {
|
|
71
|
+
encrypt(request: IEncryptRequest): Promise<[IEncryptResponse, IEncryptRequest | undefined, unknown]>;
|
|
72
|
+
decrypt(request: IDecryptRequest): Promise<[IDecryptResponse, IDecryptRequest | undefined, unknown]>;
|
|
73
|
+
}
|
|
74
|
+
/** Options for {@link gcpKmsSealingProvider}. */
|
|
75
|
+
interface GcpKmsSealingProviderOptions {
|
|
76
|
+
/**
|
|
77
|
+
* Full crypto-key resource name, e.g.
|
|
78
|
+
* `projects/PROJECT/locations/LOCATION/keyRings/RING/cryptoKeys/KEY`.
|
|
79
|
+
*/
|
|
80
|
+
readonly keyName: string;
|
|
81
|
+
/** Optional pre-built client (DI for tests). Default: `new KeyManagementServiceClient()` (ambient ADC). */
|
|
82
|
+
readonly client?: KmsClientLike;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Build a {@link SealingKeyProvider} backed by Google Cloud KMS Encrypt / Decrypt.
|
|
86
|
+
*
|
|
87
|
+
* Credentials are resolved via Application Default Credentials (ADC) — attached
|
|
88
|
+
* service accounts, `GOOGLE_APPLICATION_CREDENTIALS`, or local gcloud login.
|
|
89
|
+
* Never pass raw credentials in the options; inject a pre-configured client for
|
|
90
|
+
* non-default auth instead.
|
|
91
|
+
*
|
|
92
|
+
* @throws Error when KMS returns no ciphertext or no plaintext (guards
|
|
93
|
+
* against unexpected SDK-response shapes).
|
|
94
|
+
* Any KMS API error (PermissionDenied, NotFound, etc.) propagates as-is.
|
|
95
|
+
*/
|
|
96
|
+
declare function gcpKmsSealingProvider(opts: GcpKmsSealingProviderOptions): SealingKeyProvider;
|
|
97
|
+
|
|
98
|
+
export { type GcpKmsSealingProviderOptions, gcpKmsSealingProvider };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import { KeyManagementServiceClient } from "@google-cloud/kms";
|
|
3
|
+
function toUint8Array(value) {
|
|
4
|
+
if (value == null) return void 0;
|
|
5
|
+
if (value instanceof Uint8Array) return value;
|
|
6
|
+
return Buffer.from(value, "base64");
|
|
7
|
+
}
|
|
8
|
+
function gcpKmsSealingProvider(opts) {
|
|
9
|
+
const client = opts.client ?? new KeyManagementServiceClient();
|
|
10
|
+
return {
|
|
11
|
+
id: `gcp-kms:${opts.keyName}`,
|
|
12
|
+
async seal(passphrase) {
|
|
13
|
+
const [resp] = await client.encrypt({ name: opts.keyName, plaintext: passphrase });
|
|
14
|
+
const blob = toUint8Array(resp?.ciphertext);
|
|
15
|
+
if (!blob) throw new Error("@noy-db/at-gcp-kms: KMS encrypt returned no ciphertext");
|
|
16
|
+
return blob;
|
|
17
|
+
},
|
|
18
|
+
async unseal(sealed) {
|
|
19
|
+
const [resp] = await client.decrypt({ name: opts.keyName, ciphertext: sealed });
|
|
20
|
+
const pt = toUint8Array(resp?.plaintext);
|
|
21
|
+
if (!pt) throw new Error("@noy-db/at-gcp-kms: KMS decrypt returned no plaintext");
|
|
22
|
+
return pt;
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
export {
|
|
27
|
+
gcpKmsSealingProvider
|
|
28
|
+
};
|
|
29
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * **@noy-db/at-gcp-kms** — Google Cloud KMS sealing key provider for noy-db\n * managed-passphrase mode (#189).\n *\n * An `at-*` provider that seals and unseals the hub-generated random\n * passphrase via Google Cloud KMS Encrypt / Decrypt. Every seal and unseal is\n * an authenticated KMS API call, giving you a Cloud Audit Logs-backed access\n * log of every time a user's vault is opened — no additional instrumentation\n * required.\n *\n * ## When to use\n *\n * - Compliance regimes requiring auditable key access logs (FedRAMP, HIPAA\n * with managed-encryption requirements, SOC 2 Type II).\n * - Workloads already running on GCP where a Cloud KMS key costs less than\n * engineering an equivalent audit trail.\n * - Any case where you want automatic key rotation without rotating your\n * app's sealing key material manually.\n *\n * ## Setup\n *\n * ```bash\n * # 1. Create a key ring and symmetric crypto key once:\n * gcloud kms keyrings create noy-db-ring --location global\n * gcloud kms keys create noy-db-sealing \\\n * --location global \\\n * --keyring noy-db-ring \\\n * --purpose encryption\n * # Note the full resource name from the output.\n *\n * # 2. Grant your host's service account the Cloud KMS CryptoKey Encrypter/Decrypter role:\n * gcloud kms keys add-iam-policy-binding noy-db-sealing \\\n * --location global \\\n * --keyring noy-db-ring \\\n * --member serviceAccount:HOST_SA@PROJECT.iam.gserviceaccount.com \\\n * --role roles/cloudkms.cryptoKeyEncrypterDecrypter\n * # Credentials are picked up automatically via Application Default Credentials\n * # (ADC): service account attached to GCE/GKE, GOOGLE_APPLICATION_CREDENTIALS\n * # env var, or `gcloud auth application-default login` for local dev.\n * ```\n *\n * ```ts\n * // 3. In your app:\n * import { createNoydb } from '@noy-db/hub'\n * import { gcpKmsSealingProvider } from '@noy-db/at-gcp-kms'\n * import { shamirRecoveryProvider } from '@noy-db/on-shamir'\n *\n * const db = await createNoydb({\n * store,\n * user: 'alice',\n * passphraseMode: 'managed',\n * sealingKey: gcpKmsSealingProvider({\n * keyName: 'projects/my-project/locations/global/keyRings/noy-db-ring/cryptoKeys/noy-db-sealing',\n * }),\n * shamirRecovery: shamirRecoveryProvider(),\n * })\n * ```\n *\n * @packageDocumentation\n */\n\nimport type { SealingKeyProvider } from '@noy-db/hub'\nimport { KeyManagementServiceClient } from '@google-cloud/kms'\nimport type { protos } from '@google-cloud/kms'\n\ntype IEncryptRequest = protos.google.cloud.kms.v1.IEncryptRequest\ntype IDecryptRequest = protos.google.cloud.kms.v1.IDecryptRequest\ntype IEncryptResponse = protos.google.cloud.kms.v1.IEncryptResponse\ntype IDecryptResponse = protos.google.cloud.kms.v1.IDecryptResponse\n\n/** Minimal client surface required by {@link gcpKmsSealingProvider}. */\ninterface KmsClientLike {\n encrypt(request: IEncryptRequest): Promise<[IEncryptResponse, IEncryptRequest | undefined, unknown]>\n decrypt(request: IDecryptRequest): Promise<[IDecryptResponse, IDecryptRequest | undefined, unknown]>\n}\n\n/** Options for {@link gcpKmsSealingProvider}. */\nexport interface GcpKmsSealingProviderOptions {\n /**\n * Full crypto-key resource name, e.g.\n * `projects/PROJECT/locations/LOCATION/keyRings/RING/cryptoKeys/KEY`.\n */\n readonly keyName: string\n /** Optional pre-built client (DI for tests). Default: `new KeyManagementServiceClient()` (ambient ADC). */\n readonly client?: KmsClientLike\n}\n\nfunction toUint8Array(value: Uint8Array | string | null | undefined): Uint8Array | undefined {\n if (value == null) return undefined\n if (value instanceof Uint8Array) return value\n // protobuf may return a base64 string in some environments\n return Buffer.from(value, 'base64')\n}\n\n/**\n * Build a {@link SealingKeyProvider} backed by Google Cloud KMS Encrypt / Decrypt.\n *\n * Credentials are resolved via Application Default Credentials (ADC) — attached\n * service accounts, `GOOGLE_APPLICATION_CREDENTIALS`, or local gcloud login.\n * Never pass raw credentials in the options; inject a pre-configured client for\n * non-default auth instead.\n *\n * @throws Error when KMS returns no ciphertext or no plaintext (guards\n * against unexpected SDK-response shapes).\n * Any KMS API error (PermissionDenied, NotFound, etc.) propagates as-is.\n */\nexport function gcpKmsSealingProvider(opts: GcpKmsSealingProviderOptions): SealingKeyProvider {\n const client: KmsClientLike = opts.client ?? new KeyManagementServiceClient()\n return {\n id: `gcp-kms:${opts.keyName}`,\n\n async seal(passphrase) {\n const [resp] = await client.encrypt({ name: opts.keyName, plaintext: passphrase })\n const blob = toUint8Array(resp?.ciphertext)\n if (!blob) throw new Error('@noy-db/at-gcp-kms: KMS encrypt returned no ciphertext')\n return blob\n },\n\n async unseal(sealed) {\n const [resp] = await client.decrypt({ name: opts.keyName, ciphertext: sealed })\n const pt = toUint8Array(resp?.plaintext)\n if (!pt) throw new Error('@noy-db/at-gcp-kms: KMS decrypt returned no plaintext')\n return pt\n },\n }\n}\n"],"mappings":";AA8DA,SAAS,kCAAkC;AAyB3C,SAAS,aAAa,OAAuE;AAC3F,MAAI,SAAS,KAAM,QAAO;AAC1B,MAAI,iBAAiB,WAAY,QAAO;AAExC,SAAO,OAAO,KAAK,OAAO,QAAQ;AACpC;AAcO,SAAS,sBAAsB,MAAwD;AAC5F,QAAM,SAAwB,KAAK,UAAU,IAAI,2BAA2B;AAC5E,SAAO;AAAA,IACL,IAAI,WAAW,KAAK,OAAO;AAAA,IAE3B,MAAM,KAAK,YAAY;AACrB,YAAM,CAAC,IAAI,IAAI,MAAM,OAAO,QAAQ,EAAE,MAAM,KAAK,SAAS,WAAW,WAAW,CAAC;AACjF,YAAM,OAAO,aAAa,MAAM,UAAU;AAC1C,UAAI,CAAC,KAAM,OAAM,IAAI,MAAM,wDAAwD;AACnF,aAAO;AAAA,IACT;AAAA,IAEA,MAAM,OAAO,QAAQ;AACnB,YAAM,CAAC,IAAI,IAAI,MAAM,OAAO,QAAQ,EAAE,MAAM,KAAK,SAAS,YAAY,OAAO,CAAC;AAC9E,YAAM,KAAK,aAAa,MAAM,SAAS;AACvC,UAAI,CAAC,GAAI,OAAM,IAAI,MAAM,uDAAuD;AAChF,aAAO;AAAA,IACT;AAAA,EACF;AACF;","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@noy-db/at-gcp-kms",
|
|
3
|
+
"version": "0.2.0-pre.1",
|
|
4
|
+
"description": "Google Cloud KMS sealing key provider for noy-db managed-passphrase mode.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "vLannaAi <vicio@lanna.ai>",
|
|
7
|
+
"homepage": "https://github.com/vLannaAi/noy-db/tree/main/packages/at-gcp-kms#readme",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/vLannaAi/noy-db.git",
|
|
11
|
+
"directory": "packages/at-gcp-kms"
|
|
12
|
+
},
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/vLannaAi/noy-db/issues"
|
|
15
|
+
},
|
|
16
|
+
"type": "module",
|
|
17
|
+
"sideEffects": false,
|
|
18
|
+
"exports": {
|
|
19
|
+
".": {
|
|
20
|
+
"import": {
|
|
21
|
+
"types": "./dist/index.d.ts",
|
|
22
|
+
"default": "./dist/index.js"
|
|
23
|
+
},
|
|
24
|
+
"require": {
|
|
25
|
+
"types": "./dist/index.d.cts",
|
|
26
|
+
"default": "./dist/index.cjs"
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
"main": "./dist/index.cjs",
|
|
31
|
+
"module": "./dist/index.js",
|
|
32
|
+
"types": "./dist/index.d.ts",
|
|
33
|
+
"files": [
|
|
34
|
+
"dist",
|
|
35
|
+
"README.md",
|
|
36
|
+
"LICENSE"
|
|
37
|
+
],
|
|
38
|
+
"engines": {
|
|
39
|
+
"node": ">=18.0.0"
|
|
40
|
+
},
|
|
41
|
+
"peerDependencies": {
|
|
42
|
+
"@google-cloud/kms": "^4.0.0",
|
|
43
|
+
"@noy-db/hub": "0.2.0-pre.1"
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"@types/node": "^22.0.0",
|
|
47
|
+
"@google-cloud/kms": "^4.0.0",
|
|
48
|
+
"@noy-db/hub": "0.2.0-pre.1",
|
|
49
|
+
"@noy-db/to-memory": "0.2.0-pre.1",
|
|
50
|
+
"@noy-db/on-shamir": "0.2.0-pre.1"
|
|
51
|
+
},
|
|
52
|
+
"keywords": [
|
|
53
|
+
"noy-db",
|
|
54
|
+
"at-gcp-kms",
|
|
55
|
+
"gcp-kms",
|
|
56
|
+
"kms",
|
|
57
|
+
"sealing-key-provider",
|
|
58
|
+
"managed-passphrase",
|
|
59
|
+
"encryption",
|
|
60
|
+
"zero-knowledge"
|
|
61
|
+
],
|
|
62
|
+
"publishConfig": {
|
|
63
|
+
"access": "public",
|
|
64
|
+
"tag": "latest"
|
|
65
|
+
},
|
|
66
|
+
"scripts": {
|
|
67
|
+
"build": "tsup",
|
|
68
|
+
"test": "vitest run",
|
|
69
|
+
"lint": "eslint src/",
|
|
70
|
+
"typecheck": "tsc --noEmit"
|
|
71
|
+
}
|
|
72
|
+
}
|