@noy-db/at-azure-keyvault 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 +102 -0
- package/dist/index.cjs +51 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +104 -0
- package/dist/index.d.ts +104 -0
- package/dist/index.js +28 -0
- package/dist/index.js.map +1 -0
- package/package.json +74 -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,102 @@
|
|
|
1
|
+
# @noy-db/at-azure-keyvault
|
|
2
|
+
|
|
3
|
+
**Azure Key Vault 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 Azure Key Vault Encrypt / Decrypt. Every seal and unseal is an authenticated Key Vault API call — giving you an Azure Monitor / Key Vault audit-log-backed access record 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 Azure RBAC / Key Vault access policy — access is controlled by which managed identities or service principals hold the `encrypt` + `decrypt` key permissions on the Key Vault 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-azure-keyvault @noy-db/on-shamir
|
|
13
|
+
# or: npm install @noy-db/hub @noy-db/at-azure-keyvault @noy-db/on-shamir
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Setup
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
# 1. Create a Key Vault and an RSA key (one-time):
|
|
20
|
+
az keyvault create --name my-noydb-vault --resource-group my-rg --location eastus
|
|
21
|
+
az keyvault key create --vault-name my-noydb-vault --name noydb-sealing --kty RSA --size 2048
|
|
22
|
+
# Note the full key identifier URL from the output (the "id" field).
|
|
23
|
+
|
|
24
|
+
# 2. Grant the host's managed identity or service principal encrypt + decrypt
|
|
25
|
+
# key permissions on the vault:
|
|
26
|
+
az keyvault set-policy --name my-noydb-vault \
|
|
27
|
+
--object-id <MANAGED_IDENTITY_OBJECT_ID> \
|
|
28
|
+
--key-permissions encrypt decrypt
|
|
29
|
+
# Credentials are resolved automatically via DefaultAzureCredential:
|
|
30
|
+
# managed identity attached to the Azure host, AZURE_CLIENT_ID /
|
|
31
|
+
# AZURE_TENANT_ID / AZURE_CLIENT_SECRET env vars, or `az login` for
|
|
32
|
+
# local dev.
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
// 3. In your app:
|
|
37
|
+
import { createNoydb } from '@noy-db/hub'
|
|
38
|
+
import { azureKeyVaultSealingProvider } from '@noy-db/at-azure-keyvault'
|
|
39
|
+
import { shamirRecoveryProvider } from '@noy-db/on-shamir'
|
|
40
|
+
|
|
41
|
+
const db = await createNoydb({
|
|
42
|
+
store,
|
|
43
|
+
user: 'alice',
|
|
44
|
+
passphraseMode: 'managed',
|
|
45
|
+
sealingKey: azureKeyVaultSealingProvider({
|
|
46
|
+
keyId: 'https://my-noydb-vault.vault.azure.net/keys/noydb-sealing/<version>',
|
|
47
|
+
}),
|
|
48
|
+
shamirRecovery: shamirRecoveryProvider(),
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
const vault = await db.openVault('acme')
|
|
52
|
+
// Hub generated a 256-bit random on first open, sealed it via Key Vault Encrypt,
|
|
53
|
+
// and persisted to _meta/sealed-passphrase. The user never sees a passphrase.
|
|
54
|
+
// On reopen, at-azure-keyvault calls Key Vault Decrypt transparently.
|
|
55
|
+
// Azure Key Vault audit logs record every Encrypt/Decrypt call with caller
|
|
56
|
+
// identity + key identifier.
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## When to use this provider
|
|
60
|
+
|
|
61
|
+
- Compliance regimes requiring auditable key access logs (FedRAMP, HIPAA with managed-encryption requirements, SOC 2 Type II).
|
|
62
|
+
- Workloads already running on Azure where a Key Vault RSA key costs less than engineering an equivalent audit trail.
|
|
63
|
+
- Any case where you need an auditable, Azure-native key custody record for every vault open.
|
|
64
|
+
|
|
65
|
+
## When NOT to use this provider
|
|
66
|
+
|
|
67
|
+
- Non-Azure or multi-cloud deployments where adding an Azure dependency is undesirable. Use [`@noy-db/at-env`](../at-env) for a zero-extra-dependency option.
|
|
68
|
+
- Local dev / CI where you don't want real Key Vault calls or Azure credentials in CI. Use [`@noy-db/at-env`](../at-env) or `MemorySealingKeyProvider` from `@noy-db/hub` instead.
|
|
69
|
+
|
|
70
|
+
## Key rotation
|
|
71
|
+
|
|
72
|
+
**Azure RSA decrypt is version-bound.** Unlike AWS/GCP symmetric KMS — where the key version travels inside the ciphertext blob — Azure's `CryptographyClient` resolves the key version at construction time and every decrypt call is pinned to that version. This has a critical consequence:
|
|
73
|
+
|
|
74
|
+
- **Always use a versioned `keyId`** (`https://<vault>.vault.azure.net/keys/<name>/<version>`). The version in the URL is your guarantee that every sealed passphrase can be decrypted by the exact key material used to encrypt it.
|
|
75
|
+
- **Do NOT enable Key Vault auto-rotation on a versionless `keyId`.** If the key rotates while you are using a versionless URL, the `CryptographyClient` will resolve to the new version, and every passphrase sealed under the previous version becomes **permanently undecryptable** — every managed-mode vault is locked out with no recovery path.
|
|
76
|
+
|
|
77
|
+
**To rotate your sealing key** (e.g. for scheduled cryptographic hygiene):
|
|
78
|
+
|
|
79
|
+
1. Create a new key version (or a new key) in Key Vault.
|
|
80
|
+
2. For each vault, call `unseal` with the old versioned `keyId` to recover the plaintext passphrase.
|
|
81
|
+
3. Call `seal` with a provider configured for the **new** versioned `keyId` to produce a new ciphertext.
|
|
82
|
+
4. Persist the new sealed blob and update your app configuration to the new versioned `keyId`.
|
|
83
|
+
|
|
84
|
+
This is a deliberate migration step — not transparent rotation. Treat it the same way you would a secret rotation in any other system.
|
|
85
|
+
|
|
86
|
+
## API
|
|
87
|
+
|
|
88
|
+
```ts
|
|
89
|
+
function azureKeyVaultSealingProvider(opts: {
|
|
90
|
+
keyId: string // Full key identifier URL
|
|
91
|
+
algorithm?: 'RSA-OAEP-256' | 'RSA-OAEP' // default: 'RSA-OAEP-256'
|
|
92
|
+
cryptographyClient?: CryptoLike // optional pre-built client (useful for tests)
|
|
93
|
+
}): SealingKeyProvider
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Never pass raw Azure credentials in the options — inject a pre-configured `CryptographyClient` for non-default auth. The default builds a `CryptographyClient` with `DefaultAzureCredential`.
|
|
97
|
+
|
|
98
|
+
Returns a [`SealingKeyProvider`](../hub/src/team/managed-passphrase.ts) — the contract `@noy-db/hub`'s managed-passphrase mode consumes.
|
|
99
|
+
|
|
100
|
+
## License
|
|
101
|
+
|
|
102
|
+
MIT
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
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
|
+
azureKeyVaultSealingProvider: () => azureKeyVaultSealingProvider
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(index_exports);
|
|
26
|
+
var import_keyvault_keys = require("@azure/keyvault-keys");
|
|
27
|
+
var import_identity = require("@azure/identity");
|
|
28
|
+
function azureKeyVaultSealingProvider(opts) {
|
|
29
|
+
const algorithm = opts.algorithm ?? "RSA-OAEP-256";
|
|
30
|
+
const client = opts.cryptographyClient ?? new import_keyvault_keys.CryptographyClient(opts.keyId, new import_identity.DefaultAzureCredential());
|
|
31
|
+
return {
|
|
32
|
+
id: `azure-kv:${opts.keyId}`,
|
|
33
|
+
async seal(passphrase) {
|
|
34
|
+
const res = await client.encrypt({ algorithm, plaintext: passphrase });
|
|
35
|
+
const c = res?.result;
|
|
36
|
+
if (!c) throw new Error("@noy-db/at-azure-keyvault: Key Vault encrypt returned no result");
|
|
37
|
+
return c instanceof Uint8Array ? c : new Uint8Array(c);
|
|
38
|
+
},
|
|
39
|
+
async unseal(sealed) {
|
|
40
|
+
const res = await client.decrypt({ algorithm, ciphertext: sealed });
|
|
41
|
+
const p = res?.result;
|
|
42
|
+
if (!p) throw new Error("@noy-db/at-azure-keyvault: Key Vault decrypt returned no result");
|
|
43
|
+
return p instanceof Uint8Array ? p : new Uint8Array(p);
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
48
|
+
0 && (module.exports = {
|
|
49
|
+
azureKeyVaultSealingProvider
|
|
50
|
+
});
|
|
51
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * **@noy-db/at-azure-keyvault** — Azure Key Vault sealing key provider for noy-db\n * managed-passphrase mode (#190).\n *\n * An `at-*` provider that seals and unseals the hub-generated random\n * passphrase via Azure Key Vault Encrypt / Decrypt. Every seal and unseal is\n * an authenticated Key Vault API call, giving you an Azure Monitor / Key Vault\n * audit-log-backed access record of every time a user's vault is opened —\n * no additional instrumentation 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 Azure where a Key Vault RSA key costs less\n * than engineering an equivalent audit trail.\n * - Any case where you need an auditable, Azure-native key custody record\n * for every vault open.\n *\n * ## Setup\n *\n * ```bash\n * # 1. Create a Key Vault and an RSA key (one-time):\n * az keyvault create --name my-noydb-vault --resource-group my-rg --location eastus\n * az keyvault key create --vault-name my-noydb-vault --name noydb-sealing --kty RSA --size 2048\n * # Note the full key identifier URL from the output (id field).\n *\n * # 2. Grant the host's managed identity or service principal the\n * # encrypt + decrypt (or wrapKey + unwrapKey) permissions on the key:\n * az keyvault set-policy --name my-noydb-vault \\\n * --object-id <MANAGED_IDENTITY_OBJECT_ID> \\\n * --key-permissions encrypt decrypt\n * # Credentials are resolved automatically via DefaultAzureCredential:\n * # managed identity attached to the Azure host, AZURE_CLIENT_ID /\n * # AZURE_TENANT_ID / AZURE_CLIENT_SECRET env vars, or `az login` for\n * # local dev.\n * ```\n *\n * ```ts\n * // 3. In your app:\n * import { createNoydb } from '@noy-db/hub'\n * import { azureKeyVaultSealingProvider } from '@noy-db/at-azure-keyvault'\n * import { shamirRecoveryProvider } from '@noy-db/on-shamir'\n *\n * const db = await createNoydb({\n * store,\n * user: 'alice',\n * passphraseMode: 'managed',\n * sealingKey: azureKeyVaultSealingProvider({\n * keyId: 'https://my-noydb-vault.vault.azure.net/keys/noydb-sealing/<version>',\n * }),\n * shamirRecovery: shamirRecoveryProvider(),\n * })\n * ```\n *\n * @packageDocumentation\n */\n\nimport type { SealingKeyProvider } from '@noy-db/hub'\nimport {\n CryptographyClient,\n type EncryptResult,\n type DecryptResult,\n type RsaEncryptParameters,\n type RsaDecryptParameters,\n} from '@azure/keyvault-keys'\nimport { DefaultAzureCredential } from '@azure/identity'\n\n/** Minimal client surface required by {@link azureKeyVaultSealingProvider}. */\ninterface CryptoLike {\n encrypt(params: RsaEncryptParameters): Promise<EncryptResult>\n decrypt(params: RsaDecryptParameters): Promise<DecryptResult>\n}\n\n/** Options for {@link azureKeyVaultSealingProvider}. */\nexport interface AzureKeyVaultSealingProviderOptions {\n /**\n * Full **versioned** key identifier URL, e.g.\n * `https://<vault>.vault.azure.net/keys/<name>/<version>`.\n *\n * Azure RSA decrypt is version-bound: the `CryptographyClient` resolves the\n * key version at construction time and every decrypt call is pinned to it.\n * A versionless URL (`.../keys/<name>`) resolves to \"latest\" — if the key\n * auto-rotates, all passphrases sealed under the previous version become\n * **permanently undecryptable**. Always pin to an explicit version.\n */\n readonly keyId: string\n /** RSA encryption algorithm. Defaults to `'RSA-OAEP-256'`. */\n readonly algorithm?: 'RSA-OAEP-256' | 'RSA-OAEP'\n /**\n * Optional pre-built CryptographyClient (DI for tests).\n * Default: builds one with `DefaultAzureCredential`.\n * Never pass raw Azure credentials in these options.\n */\n readonly cryptographyClient?: CryptoLike\n}\n\n/**\n * Build a {@link SealingKeyProvider} backed by Azure Key Vault Encrypt / Decrypt.\n *\n * Credentials are resolved via `DefaultAzureCredential` — managed identities\n * on Azure hosts, `AZURE_CLIENT_ID` / `AZURE_TENANT_ID` / `AZURE_CLIENT_SECRET`\n * env vars, or `az login` for local dev. Never pass raw credentials in the\n * options; inject a pre-configured `CryptographyClient` for non-default auth\n * instead.\n *\n * @throws Error when Key Vault returns no result (guards against unexpected\n * SDK-response shapes). Any Key Vault API error (Forbidden, NotFound, etc.)\n * propagates as-is.\n */\nexport function azureKeyVaultSealingProvider(opts: AzureKeyVaultSealingProviderOptions): SealingKeyProvider {\n const algorithm = opts.algorithm ?? 'RSA-OAEP-256'\n const client: CryptoLike = opts.cryptographyClient\n ?? new CryptographyClient(opts.keyId, new DefaultAzureCredential())\n\n return {\n id: `azure-kv:${opts.keyId}`,\n\n async seal(passphrase) {\n const res = await client.encrypt({ algorithm, plaintext: passphrase })\n const c = res?.result\n if (!c) throw new Error('@noy-db/at-azure-keyvault: Key Vault encrypt returned no result')\n return c instanceof Uint8Array ? c : new Uint8Array(c)\n },\n\n async unseal(sealed) {\n const res = await client.decrypt({ algorithm, ciphertext: sealed })\n const p = res?.result\n if (!p) throw new Error('@noy-db/at-azure-keyvault: Key Vault decrypt returned no result')\n return p instanceof Uint8Array ? p : new Uint8Array(p)\n },\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AA2DA,2BAMO;AACP,sBAAuC;AA4ChC,SAAS,6BAA6B,MAA+D;AAC1G,QAAM,YAAY,KAAK,aAAa;AACpC,QAAM,SAAqB,KAAK,sBAC3B,IAAI,wCAAmB,KAAK,OAAO,IAAI,uCAAuB,CAAC;AAEpE,SAAO;AAAA,IACL,IAAI,YAAY,KAAK,KAAK;AAAA,IAE1B,MAAM,KAAK,YAAY;AACrB,YAAM,MAAM,MAAM,OAAO,QAAQ,EAAE,WAAW,WAAW,WAAW,CAAC;AACrE,YAAM,IAAI,KAAK;AACf,UAAI,CAAC,EAAG,OAAM,IAAI,MAAM,iEAAiE;AACzF,aAAO,aAAa,aAAa,IAAI,IAAI,WAAW,CAAC;AAAA,IACvD;AAAA,IAEA,MAAM,OAAO,QAAQ;AACnB,YAAM,MAAM,MAAM,OAAO,QAAQ,EAAE,WAAW,YAAY,OAAO,CAAC;AAClE,YAAM,IAAI,KAAK;AACf,UAAI,CAAC,EAAG,OAAM,IAAI,MAAM,iEAAiE;AACzF,aAAO,aAAa,aAAa,IAAI,IAAI,WAAW,CAAC;AAAA,IACvD;AAAA,EACF;AACF;","names":[]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { SealingKeyProvider } from '@noy-db/hub';
|
|
2
|
+
import { RsaEncryptParameters, EncryptResult, RsaDecryptParameters, DecryptResult } from '@azure/keyvault-keys';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* **@noy-db/at-azure-keyvault** — Azure Key Vault sealing key provider for noy-db
|
|
6
|
+
* managed-passphrase mode (#190).
|
|
7
|
+
*
|
|
8
|
+
* An `at-*` provider that seals and unseals the hub-generated random
|
|
9
|
+
* passphrase via Azure Key Vault Encrypt / Decrypt. Every seal and unseal is
|
|
10
|
+
* an authenticated Key Vault API call, giving you an Azure Monitor / Key Vault
|
|
11
|
+
* audit-log-backed access record of every time a user's vault is opened —
|
|
12
|
+
* no additional instrumentation 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 Azure where a Key Vault RSA key costs less
|
|
19
|
+
* than engineering an equivalent audit trail.
|
|
20
|
+
* - Any case where you need an auditable, Azure-native key custody record
|
|
21
|
+
* for every vault open.
|
|
22
|
+
*
|
|
23
|
+
* ## Setup
|
|
24
|
+
*
|
|
25
|
+
* ```bash
|
|
26
|
+
* # 1. Create a Key Vault and an RSA key (one-time):
|
|
27
|
+
* az keyvault create --name my-noydb-vault --resource-group my-rg --location eastus
|
|
28
|
+
* az keyvault key create --vault-name my-noydb-vault --name noydb-sealing --kty RSA --size 2048
|
|
29
|
+
* # Note the full key identifier URL from the output (id field).
|
|
30
|
+
*
|
|
31
|
+
* # 2. Grant the host's managed identity or service principal the
|
|
32
|
+
* # encrypt + decrypt (or wrapKey + unwrapKey) permissions on the key:
|
|
33
|
+
* az keyvault set-policy --name my-noydb-vault \
|
|
34
|
+
* --object-id <MANAGED_IDENTITY_OBJECT_ID> \
|
|
35
|
+
* --key-permissions encrypt decrypt
|
|
36
|
+
* # Credentials are resolved automatically via DefaultAzureCredential:
|
|
37
|
+
* # managed identity attached to the Azure host, AZURE_CLIENT_ID /
|
|
38
|
+
* # AZURE_TENANT_ID / AZURE_CLIENT_SECRET env vars, or `az login` for
|
|
39
|
+
* # local dev.
|
|
40
|
+
* ```
|
|
41
|
+
*
|
|
42
|
+
* ```ts
|
|
43
|
+
* // 3. In your app:
|
|
44
|
+
* import { createNoydb } from '@noy-db/hub'
|
|
45
|
+
* import { azureKeyVaultSealingProvider } from '@noy-db/at-azure-keyvault'
|
|
46
|
+
* import { shamirRecoveryProvider } from '@noy-db/on-shamir'
|
|
47
|
+
*
|
|
48
|
+
* const db = await createNoydb({
|
|
49
|
+
* store,
|
|
50
|
+
* user: 'alice',
|
|
51
|
+
* passphraseMode: 'managed',
|
|
52
|
+
* sealingKey: azureKeyVaultSealingProvider({
|
|
53
|
+
* keyId: 'https://my-noydb-vault.vault.azure.net/keys/noydb-sealing/<version>',
|
|
54
|
+
* }),
|
|
55
|
+
* shamirRecovery: shamirRecoveryProvider(),
|
|
56
|
+
* })
|
|
57
|
+
* ```
|
|
58
|
+
*
|
|
59
|
+
* @packageDocumentation
|
|
60
|
+
*/
|
|
61
|
+
|
|
62
|
+
/** Minimal client surface required by {@link azureKeyVaultSealingProvider}. */
|
|
63
|
+
interface CryptoLike {
|
|
64
|
+
encrypt(params: RsaEncryptParameters): Promise<EncryptResult>;
|
|
65
|
+
decrypt(params: RsaDecryptParameters): Promise<DecryptResult>;
|
|
66
|
+
}
|
|
67
|
+
/** Options for {@link azureKeyVaultSealingProvider}. */
|
|
68
|
+
interface AzureKeyVaultSealingProviderOptions {
|
|
69
|
+
/**
|
|
70
|
+
* Full **versioned** key identifier URL, e.g.
|
|
71
|
+
* `https://<vault>.vault.azure.net/keys/<name>/<version>`.
|
|
72
|
+
*
|
|
73
|
+
* Azure RSA decrypt is version-bound: the `CryptographyClient` resolves the
|
|
74
|
+
* key version at construction time and every decrypt call is pinned to it.
|
|
75
|
+
* A versionless URL (`.../keys/<name>`) resolves to "latest" — if the key
|
|
76
|
+
* auto-rotates, all passphrases sealed under the previous version become
|
|
77
|
+
* **permanently undecryptable**. Always pin to an explicit version.
|
|
78
|
+
*/
|
|
79
|
+
readonly keyId: string;
|
|
80
|
+
/** RSA encryption algorithm. Defaults to `'RSA-OAEP-256'`. */
|
|
81
|
+
readonly algorithm?: 'RSA-OAEP-256' | 'RSA-OAEP';
|
|
82
|
+
/**
|
|
83
|
+
* Optional pre-built CryptographyClient (DI for tests).
|
|
84
|
+
* Default: builds one with `DefaultAzureCredential`.
|
|
85
|
+
* Never pass raw Azure credentials in these options.
|
|
86
|
+
*/
|
|
87
|
+
readonly cryptographyClient?: CryptoLike;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Build a {@link SealingKeyProvider} backed by Azure Key Vault Encrypt / Decrypt.
|
|
91
|
+
*
|
|
92
|
+
* Credentials are resolved via `DefaultAzureCredential` — managed identities
|
|
93
|
+
* on Azure hosts, `AZURE_CLIENT_ID` / `AZURE_TENANT_ID` / `AZURE_CLIENT_SECRET`
|
|
94
|
+
* env vars, or `az login` for local dev. Never pass raw credentials in the
|
|
95
|
+
* options; inject a pre-configured `CryptographyClient` for non-default auth
|
|
96
|
+
* instead.
|
|
97
|
+
*
|
|
98
|
+
* @throws Error when Key Vault returns no result (guards against unexpected
|
|
99
|
+
* SDK-response shapes). Any Key Vault API error (Forbidden, NotFound, etc.)
|
|
100
|
+
* propagates as-is.
|
|
101
|
+
*/
|
|
102
|
+
declare function azureKeyVaultSealingProvider(opts: AzureKeyVaultSealingProviderOptions): SealingKeyProvider;
|
|
103
|
+
|
|
104
|
+
export { type AzureKeyVaultSealingProviderOptions, azureKeyVaultSealingProvider };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { SealingKeyProvider } from '@noy-db/hub';
|
|
2
|
+
import { RsaEncryptParameters, EncryptResult, RsaDecryptParameters, DecryptResult } from '@azure/keyvault-keys';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* **@noy-db/at-azure-keyvault** — Azure Key Vault sealing key provider for noy-db
|
|
6
|
+
* managed-passphrase mode (#190).
|
|
7
|
+
*
|
|
8
|
+
* An `at-*` provider that seals and unseals the hub-generated random
|
|
9
|
+
* passphrase via Azure Key Vault Encrypt / Decrypt. Every seal and unseal is
|
|
10
|
+
* an authenticated Key Vault API call, giving you an Azure Monitor / Key Vault
|
|
11
|
+
* audit-log-backed access record of every time a user's vault is opened —
|
|
12
|
+
* no additional instrumentation 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 Azure where a Key Vault RSA key costs less
|
|
19
|
+
* than engineering an equivalent audit trail.
|
|
20
|
+
* - Any case where you need an auditable, Azure-native key custody record
|
|
21
|
+
* for every vault open.
|
|
22
|
+
*
|
|
23
|
+
* ## Setup
|
|
24
|
+
*
|
|
25
|
+
* ```bash
|
|
26
|
+
* # 1. Create a Key Vault and an RSA key (one-time):
|
|
27
|
+
* az keyvault create --name my-noydb-vault --resource-group my-rg --location eastus
|
|
28
|
+
* az keyvault key create --vault-name my-noydb-vault --name noydb-sealing --kty RSA --size 2048
|
|
29
|
+
* # Note the full key identifier URL from the output (id field).
|
|
30
|
+
*
|
|
31
|
+
* # 2. Grant the host's managed identity or service principal the
|
|
32
|
+
* # encrypt + decrypt (or wrapKey + unwrapKey) permissions on the key:
|
|
33
|
+
* az keyvault set-policy --name my-noydb-vault \
|
|
34
|
+
* --object-id <MANAGED_IDENTITY_OBJECT_ID> \
|
|
35
|
+
* --key-permissions encrypt decrypt
|
|
36
|
+
* # Credentials are resolved automatically via DefaultAzureCredential:
|
|
37
|
+
* # managed identity attached to the Azure host, AZURE_CLIENT_ID /
|
|
38
|
+
* # AZURE_TENANT_ID / AZURE_CLIENT_SECRET env vars, or `az login` for
|
|
39
|
+
* # local dev.
|
|
40
|
+
* ```
|
|
41
|
+
*
|
|
42
|
+
* ```ts
|
|
43
|
+
* // 3. In your app:
|
|
44
|
+
* import { createNoydb } from '@noy-db/hub'
|
|
45
|
+
* import { azureKeyVaultSealingProvider } from '@noy-db/at-azure-keyvault'
|
|
46
|
+
* import { shamirRecoveryProvider } from '@noy-db/on-shamir'
|
|
47
|
+
*
|
|
48
|
+
* const db = await createNoydb({
|
|
49
|
+
* store,
|
|
50
|
+
* user: 'alice',
|
|
51
|
+
* passphraseMode: 'managed',
|
|
52
|
+
* sealingKey: azureKeyVaultSealingProvider({
|
|
53
|
+
* keyId: 'https://my-noydb-vault.vault.azure.net/keys/noydb-sealing/<version>',
|
|
54
|
+
* }),
|
|
55
|
+
* shamirRecovery: shamirRecoveryProvider(),
|
|
56
|
+
* })
|
|
57
|
+
* ```
|
|
58
|
+
*
|
|
59
|
+
* @packageDocumentation
|
|
60
|
+
*/
|
|
61
|
+
|
|
62
|
+
/** Minimal client surface required by {@link azureKeyVaultSealingProvider}. */
|
|
63
|
+
interface CryptoLike {
|
|
64
|
+
encrypt(params: RsaEncryptParameters): Promise<EncryptResult>;
|
|
65
|
+
decrypt(params: RsaDecryptParameters): Promise<DecryptResult>;
|
|
66
|
+
}
|
|
67
|
+
/** Options for {@link azureKeyVaultSealingProvider}. */
|
|
68
|
+
interface AzureKeyVaultSealingProviderOptions {
|
|
69
|
+
/**
|
|
70
|
+
* Full **versioned** key identifier URL, e.g.
|
|
71
|
+
* `https://<vault>.vault.azure.net/keys/<name>/<version>`.
|
|
72
|
+
*
|
|
73
|
+
* Azure RSA decrypt is version-bound: the `CryptographyClient` resolves the
|
|
74
|
+
* key version at construction time and every decrypt call is pinned to it.
|
|
75
|
+
* A versionless URL (`.../keys/<name>`) resolves to "latest" — if the key
|
|
76
|
+
* auto-rotates, all passphrases sealed under the previous version become
|
|
77
|
+
* **permanently undecryptable**. Always pin to an explicit version.
|
|
78
|
+
*/
|
|
79
|
+
readonly keyId: string;
|
|
80
|
+
/** RSA encryption algorithm. Defaults to `'RSA-OAEP-256'`. */
|
|
81
|
+
readonly algorithm?: 'RSA-OAEP-256' | 'RSA-OAEP';
|
|
82
|
+
/**
|
|
83
|
+
* Optional pre-built CryptographyClient (DI for tests).
|
|
84
|
+
* Default: builds one with `DefaultAzureCredential`.
|
|
85
|
+
* Never pass raw Azure credentials in these options.
|
|
86
|
+
*/
|
|
87
|
+
readonly cryptographyClient?: CryptoLike;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Build a {@link SealingKeyProvider} backed by Azure Key Vault Encrypt / Decrypt.
|
|
91
|
+
*
|
|
92
|
+
* Credentials are resolved via `DefaultAzureCredential` — managed identities
|
|
93
|
+
* on Azure hosts, `AZURE_CLIENT_ID` / `AZURE_TENANT_ID` / `AZURE_CLIENT_SECRET`
|
|
94
|
+
* env vars, or `az login` for local dev. Never pass raw credentials in the
|
|
95
|
+
* options; inject a pre-configured `CryptographyClient` for non-default auth
|
|
96
|
+
* instead.
|
|
97
|
+
*
|
|
98
|
+
* @throws Error when Key Vault returns no result (guards against unexpected
|
|
99
|
+
* SDK-response shapes). Any Key Vault API error (Forbidden, NotFound, etc.)
|
|
100
|
+
* propagates as-is.
|
|
101
|
+
*/
|
|
102
|
+
declare function azureKeyVaultSealingProvider(opts: AzureKeyVaultSealingProviderOptions): SealingKeyProvider;
|
|
103
|
+
|
|
104
|
+
export { type AzureKeyVaultSealingProviderOptions, azureKeyVaultSealingProvider };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import {
|
|
3
|
+
CryptographyClient
|
|
4
|
+
} from "@azure/keyvault-keys";
|
|
5
|
+
import { DefaultAzureCredential } from "@azure/identity";
|
|
6
|
+
function azureKeyVaultSealingProvider(opts) {
|
|
7
|
+
const algorithm = opts.algorithm ?? "RSA-OAEP-256";
|
|
8
|
+
const client = opts.cryptographyClient ?? new CryptographyClient(opts.keyId, new DefaultAzureCredential());
|
|
9
|
+
return {
|
|
10
|
+
id: `azure-kv:${opts.keyId}`,
|
|
11
|
+
async seal(passphrase) {
|
|
12
|
+
const res = await client.encrypt({ algorithm, plaintext: passphrase });
|
|
13
|
+
const c = res?.result;
|
|
14
|
+
if (!c) throw new Error("@noy-db/at-azure-keyvault: Key Vault encrypt returned no result");
|
|
15
|
+
return c instanceof Uint8Array ? c : new Uint8Array(c);
|
|
16
|
+
},
|
|
17
|
+
async unseal(sealed) {
|
|
18
|
+
const res = await client.decrypt({ algorithm, ciphertext: sealed });
|
|
19
|
+
const p = res?.result;
|
|
20
|
+
if (!p) throw new Error("@noy-db/at-azure-keyvault: Key Vault decrypt returned no result");
|
|
21
|
+
return p instanceof Uint8Array ? p : new Uint8Array(p);
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
export {
|
|
26
|
+
azureKeyVaultSealingProvider
|
|
27
|
+
};
|
|
28
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * **@noy-db/at-azure-keyvault** — Azure Key Vault sealing key provider for noy-db\n * managed-passphrase mode (#190).\n *\n * An `at-*` provider that seals and unseals the hub-generated random\n * passphrase via Azure Key Vault Encrypt / Decrypt. Every seal and unseal is\n * an authenticated Key Vault API call, giving you an Azure Monitor / Key Vault\n * audit-log-backed access record of every time a user's vault is opened —\n * no additional instrumentation 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 Azure where a Key Vault RSA key costs less\n * than engineering an equivalent audit trail.\n * - Any case where you need an auditable, Azure-native key custody record\n * for every vault open.\n *\n * ## Setup\n *\n * ```bash\n * # 1. Create a Key Vault and an RSA key (one-time):\n * az keyvault create --name my-noydb-vault --resource-group my-rg --location eastus\n * az keyvault key create --vault-name my-noydb-vault --name noydb-sealing --kty RSA --size 2048\n * # Note the full key identifier URL from the output (id field).\n *\n * # 2. Grant the host's managed identity or service principal the\n * # encrypt + decrypt (or wrapKey + unwrapKey) permissions on the key:\n * az keyvault set-policy --name my-noydb-vault \\\n * --object-id <MANAGED_IDENTITY_OBJECT_ID> \\\n * --key-permissions encrypt decrypt\n * # Credentials are resolved automatically via DefaultAzureCredential:\n * # managed identity attached to the Azure host, AZURE_CLIENT_ID /\n * # AZURE_TENANT_ID / AZURE_CLIENT_SECRET env vars, or `az login` for\n * # local dev.\n * ```\n *\n * ```ts\n * // 3. In your app:\n * import { createNoydb } from '@noy-db/hub'\n * import { azureKeyVaultSealingProvider } from '@noy-db/at-azure-keyvault'\n * import { shamirRecoveryProvider } from '@noy-db/on-shamir'\n *\n * const db = await createNoydb({\n * store,\n * user: 'alice',\n * passphraseMode: 'managed',\n * sealingKey: azureKeyVaultSealingProvider({\n * keyId: 'https://my-noydb-vault.vault.azure.net/keys/noydb-sealing/<version>',\n * }),\n * shamirRecovery: shamirRecoveryProvider(),\n * })\n * ```\n *\n * @packageDocumentation\n */\n\nimport type { SealingKeyProvider } from '@noy-db/hub'\nimport {\n CryptographyClient,\n type EncryptResult,\n type DecryptResult,\n type RsaEncryptParameters,\n type RsaDecryptParameters,\n} from '@azure/keyvault-keys'\nimport { DefaultAzureCredential } from '@azure/identity'\n\n/** Minimal client surface required by {@link azureKeyVaultSealingProvider}. */\ninterface CryptoLike {\n encrypt(params: RsaEncryptParameters): Promise<EncryptResult>\n decrypt(params: RsaDecryptParameters): Promise<DecryptResult>\n}\n\n/** Options for {@link azureKeyVaultSealingProvider}. */\nexport interface AzureKeyVaultSealingProviderOptions {\n /**\n * Full **versioned** key identifier URL, e.g.\n * `https://<vault>.vault.azure.net/keys/<name>/<version>`.\n *\n * Azure RSA decrypt is version-bound: the `CryptographyClient` resolves the\n * key version at construction time and every decrypt call is pinned to it.\n * A versionless URL (`.../keys/<name>`) resolves to \"latest\" — if the key\n * auto-rotates, all passphrases sealed under the previous version become\n * **permanently undecryptable**. Always pin to an explicit version.\n */\n readonly keyId: string\n /** RSA encryption algorithm. Defaults to `'RSA-OAEP-256'`. */\n readonly algorithm?: 'RSA-OAEP-256' | 'RSA-OAEP'\n /**\n * Optional pre-built CryptographyClient (DI for tests).\n * Default: builds one with `DefaultAzureCredential`.\n * Never pass raw Azure credentials in these options.\n */\n readonly cryptographyClient?: CryptoLike\n}\n\n/**\n * Build a {@link SealingKeyProvider} backed by Azure Key Vault Encrypt / Decrypt.\n *\n * Credentials are resolved via `DefaultAzureCredential` — managed identities\n * on Azure hosts, `AZURE_CLIENT_ID` / `AZURE_TENANT_ID` / `AZURE_CLIENT_SECRET`\n * env vars, or `az login` for local dev. Never pass raw credentials in the\n * options; inject a pre-configured `CryptographyClient` for non-default auth\n * instead.\n *\n * @throws Error when Key Vault returns no result (guards against unexpected\n * SDK-response shapes). Any Key Vault API error (Forbidden, NotFound, etc.)\n * propagates as-is.\n */\nexport function azureKeyVaultSealingProvider(opts: AzureKeyVaultSealingProviderOptions): SealingKeyProvider {\n const algorithm = opts.algorithm ?? 'RSA-OAEP-256'\n const client: CryptoLike = opts.cryptographyClient\n ?? new CryptographyClient(opts.keyId, new DefaultAzureCredential())\n\n return {\n id: `azure-kv:${opts.keyId}`,\n\n async seal(passphrase) {\n const res = await client.encrypt({ algorithm, plaintext: passphrase })\n const c = res?.result\n if (!c) throw new Error('@noy-db/at-azure-keyvault: Key Vault encrypt returned no result')\n return c instanceof Uint8Array ? c : new Uint8Array(c)\n },\n\n async unseal(sealed) {\n const res = await client.decrypt({ algorithm, ciphertext: sealed })\n const p = res?.result\n if (!p) throw new Error('@noy-db/at-azure-keyvault: Key Vault decrypt returned no result')\n return p instanceof Uint8Array ? p : new Uint8Array(p)\n },\n }\n}\n"],"mappings":";AA2DA;AAAA,EACE;AAAA,OAKK;AACP,SAAS,8BAA8B;AA4ChC,SAAS,6BAA6B,MAA+D;AAC1G,QAAM,YAAY,KAAK,aAAa;AACpC,QAAM,SAAqB,KAAK,sBAC3B,IAAI,mBAAmB,KAAK,OAAO,IAAI,uBAAuB,CAAC;AAEpE,SAAO;AAAA,IACL,IAAI,YAAY,KAAK,KAAK;AAAA,IAE1B,MAAM,KAAK,YAAY;AACrB,YAAM,MAAM,MAAM,OAAO,QAAQ,EAAE,WAAW,WAAW,WAAW,CAAC;AACrE,YAAM,IAAI,KAAK;AACf,UAAI,CAAC,EAAG,OAAM,IAAI,MAAM,iEAAiE;AACzF,aAAO,aAAa,aAAa,IAAI,IAAI,WAAW,CAAC;AAAA,IACvD;AAAA,IAEA,MAAM,OAAO,QAAQ;AACnB,YAAM,MAAM,MAAM,OAAO,QAAQ,EAAE,WAAW,YAAY,OAAO,CAAC;AAClE,YAAM,IAAI,KAAK;AACf,UAAI,CAAC,EAAG,OAAM,IAAI,MAAM,iEAAiE;AACzF,aAAO,aAAa,aAAa,IAAI,IAAI,WAAW,CAAC;AAAA,IACvD;AAAA,EACF;AACF;","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@noy-db/at-azure-keyvault",
|
|
3
|
+
"version": "0.2.0-pre.1",
|
|
4
|
+
"description": "Azure Key Vault 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-azure-keyvault#readme",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/vLannaAi/noy-db.git",
|
|
11
|
+
"directory": "packages/at-azure-keyvault"
|
|
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
|
+
"@azure/keyvault-keys": "^4.0.0",
|
|
43
|
+
"@azure/identity": "^4.0.0",
|
|
44
|
+
"@noy-db/hub": "0.2.0-pre.1"
|
|
45
|
+
},
|
|
46
|
+
"devDependencies": {
|
|
47
|
+
"@types/node": "^22.0.0",
|
|
48
|
+
"@azure/keyvault-keys": "^4.0.0",
|
|
49
|
+
"@azure/identity": "^4.0.0",
|
|
50
|
+
"@noy-db/hub": "0.2.0-pre.1",
|
|
51
|
+
"@noy-db/on-shamir": "0.2.0-pre.1",
|
|
52
|
+
"@noy-db/to-memory": "0.2.0-pre.1"
|
|
53
|
+
},
|
|
54
|
+
"keywords": [
|
|
55
|
+
"noy-db",
|
|
56
|
+
"at-azure-keyvault",
|
|
57
|
+
"azure-keyvault",
|
|
58
|
+
"kms",
|
|
59
|
+
"sealing-key-provider",
|
|
60
|
+
"managed-passphrase",
|
|
61
|
+
"encryption",
|
|
62
|
+
"zero-knowledge"
|
|
63
|
+
],
|
|
64
|
+
"publishConfig": {
|
|
65
|
+
"access": "public",
|
|
66
|
+
"tag": "latest"
|
|
67
|
+
},
|
|
68
|
+
"scripts": {
|
|
69
|
+
"build": "tsup",
|
|
70
|
+
"test": "vitest run",
|
|
71
|
+
"lint": "eslint src/",
|
|
72
|
+
"typecheck": "tsc --noEmit"
|
|
73
|
+
}
|
|
74
|
+
}
|