@faizahmed/secret-keystore 1.1.0
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 +1203 -0
- package/SECURITY.md +505 -0
- package/bin/cli.js +969 -0
- package/package.json +77 -0
- package/src/attestation/attestation-client.js +146 -0
- package/src/attestation/attestation-manager.js +339 -0
- package/src/attestation/cms-unwrap.js +166 -0
- package/src/attestation/index.js +66 -0
- package/src/attestation/key-pair.js +129 -0
- package/src/config.js +130 -0
- package/src/content-operations.js +494 -0
- package/src/errors.js +372 -0
- package/src/index.d.ts +641 -0
- package/src/index.js +438 -0
- package/src/keystore.js +678 -0
- package/src/kms.js +858 -0
- package/src/object-operations.js +232 -0
- package/src/options.js +541 -0
- package/src/path-matcher.js +319 -0
- package/src/rotate.js +92 -0
- package/src/yaml-utils.js +265 -0
package/SECURITY.md
ADDED
|
@@ -0,0 +1,505 @@
|
|
|
1
|
+
# Security
|
|
2
|
+
|
|
3
|
+
This document explains the security model of [`@faizahmed/secret-keystore`](https://www.npmjs.com/package/@faizahmed/secret-keystore) (available on npm) and how it protects your secrets.
|
|
4
|
+
|
|
5
|
+
> **🔐 Key Design Decision:** This package uses **IAM roles by default**. Explicit AWS credentials require opt-in. This ensures production deployments are secure by default.
|
|
6
|
+
|
|
7
|
+
## Table of Contents
|
|
8
|
+
|
|
9
|
+
- [Overview](#overview)
|
|
10
|
+
- [Threat Scenarios](#threat-scenarios)
|
|
11
|
+
- [Security Layers](#security-layers)
|
|
12
|
+
- [Security-First Dependency Policy](#security-first-dependency-policy)
|
|
13
|
+
- [Nitro Enclave Attestation](#nitro-enclave-attestation)
|
|
14
|
+
- [Full Attestation Flow](#full-attestation-flow)
|
|
15
|
+
- [5-Minute Auto-Refresh](#5-minute-auto-refresh)
|
|
16
|
+
- [AttestationManager API](#attestationmanager-api)
|
|
17
|
+
- [KMS Key Policies](#kms-key-policies)
|
|
18
|
+
- [Authentication](#authentication)
|
|
19
|
+
- [Best Practices](#best-practices)
|
|
20
|
+
- [Security Checklist](#security-checklist)
|
|
21
|
+
|
|
22
|
+
## Overview
|
|
23
|
+
|
|
24
|
+
The package provides multiple layers of security:
|
|
25
|
+
|
|
26
|
+
| Layer | Protection |
|
|
27
|
+
|-------|------------|
|
|
28
|
+
| **IAM Role Default** | Uses IAM roles by default — no credentials to manage or leak |
|
|
29
|
+
| **Encryption at Rest** | Secrets in config files are KMS-encrypted ciphertext (symmetric: direct; RSA: envelope with encrypted DEK) |
|
|
30
|
+
| **Access Control** | IAM policies + KMS key policies control decryption |
|
|
31
|
+
| **Runtime Isolation** | Decrypted values never in `process.env` |
|
|
32
|
+
| **Memory Protection** | Additional AES-256-GCM encryption in keystore memory |
|
|
33
|
+
| **Attestation** | Optional Nitro Enclave verification for maximum security |
|
|
34
|
+
| **Minimal Dependencies** | Security-sensitive code uses native Node.js modules only |
|
|
35
|
+
|
|
36
|
+
## Threat Scenarios
|
|
37
|
+
|
|
38
|
+
### Scenario 1: Attacker gets your config file
|
|
39
|
+
|
|
40
|
+
If an attacker obtains your `.env` or `secrets.yaml` file, they see:
|
|
41
|
+
|
|
42
|
+
```env
|
|
43
|
+
KMS_KEY_ID=arn:aws:kms:us-east-1:123456789:key/abc-123
|
|
44
|
+
DB_PASSWORD=ENC[AQICAHh2nZPq7x9K3mJ...long-base64-ciphertext...]
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
**They CANNOT decrypt `DB_PASSWORD` because:**
|
|
48
|
+
|
|
49
|
+
| Barrier | Why It Stops Them |
|
|
50
|
+
|---------|-------------------|
|
|
51
|
+
| **KMS Required** | The ciphertext can ONLY be decrypted by AWS KMS — no offline cracking possible |
|
|
52
|
+
| **IAM Required** | They need valid AWS credentials with `kms:Decrypt` permission |
|
|
53
|
+
| **Key Policy** | The KMS key policy controls exactly who/what can decrypt |
|
|
54
|
+
|
|
55
|
+
### Scenario 2: Attacker gets server access
|
|
56
|
+
|
|
57
|
+
Even with full server access:
|
|
58
|
+
|
|
59
|
+
| Protection Layer | How It Helps |
|
|
60
|
+
|------------------|--------------|
|
|
61
|
+
| **IAM Role Binding** | Only the specific EC2/ECS/Lambda role can decrypt |
|
|
62
|
+
| **VPC Restrictions** | KMS key policy can restrict to specific VPCs |
|
|
63
|
+
| **No Plaintext on Disk** | Decrypted values exist only in memory |
|
|
64
|
+
| **No `process.env`** | Can't dump secrets via `/proc/[pid]/environ` |
|
|
65
|
+
| **Memory Encryption** | Additional AES-256-GCM encryption in keystore memory |
|
|
66
|
+
|
|
67
|
+
### Scenario 3: Attacker gets server access + AWS credentials
|
|
68
|
+
|
|
69
|
+
Even with both:
|
|
70
|
+
|
|
71
|
+
| Protection | How It Helps |
|
|
72
|
+
|------------|--------------|
|
|
73
|
+
| **KMS Key Policy** | Can restrict to specific IAM roles only |
|
|
74
|
+
| **VPC Conditions** | Can require requests from specific VPCs |
|
|
75
|
+
| **Attestation** | Can require valid Nitro Enclave attestation |
|
|
76
|
+
|
|
77
|
+
## Security Layers
|
|
78
|
+
|
|
79
|
+
```mermaid
|
|
80
|
+
flowchart TB
|
|
81
|
+
subgraph CONFIG["Config File"]
|
|
82
|
+
A["DB_PASSWORD=ENC[AQICAHh...encrypted...]"]
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
subgraph KMS["AWS KMS"]
|
|
86
|
+
B["IAM Policy Check"]
|
|
87
|
+
C["Key Policy Check"]
|
|
88
|
+
D["Attestation Check (optional)"]
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
subgraph MEM["Keystore Memory"]
|
|
92
|
+
E["AES-256-GCM Encrypted Storage"]
|
|
93
|
+
F["keyStore.get() API"]
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
CONFIG -->|"Encrypted ciphertext"| KMS
|
|
97
|
+
KMS -->|"Decrypted plaintext"| MEM
|
|
98
|
+
|
|
99
|
+
style CONFIG fill:#ffcccc
|
|
100
|
+
style KMS fill:#ccffcc
|
|
101
|
+
style MEM fill:#ccccff
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Layer 1: Encryption at Rest
|
|
105
|
+
|
|
106
|
+
- Secrets encrypted with AWS KMS before storage
|
|
107
|
+
- Only ciphertext exists in config files
|
|
108
|
+
- No offline cracking possible — requires KMS API
|
|
109
|
+
- Encrypted format: `ENC[...]` (base64). With **symmetric** keys, KMS encrypts the value directly; with **asymmetric (RSA)** keys, the library uses envelope encryption (a random DEK encrypts the secret, KMS encrypts only the DEK; the encrypted DEK is stored with the ciphertext so only KMS can recover it).
|
|
110
|
+
|
|
111
|
+
### Layer 2: Access Control
|
|
112
|
+
|
|
113
|
+
- **IAM Policies**: Control which AWS identities can call KMS
|
|
114
|
+
- **KMS Key Policies**: Additional layer of access control on the key itself
|
|
115
|
+
- **Conditions**: Can restrict by VPC, IP, time, and more
|
|
116
|
+
|
|
117
|
+
### Layer 3: Runtime Protection
|
|
118
|
+
|
|
119
|
+
- Decrypted values **never** stored in `process.env`
|
|
120
|
+
- Cannot be dumped via `/proc/[pid]/environ`
|
|
121
|
+
- Additional AES-256-GCM encryption in keystore memory
|
|
122
|
+
- Accessible only via `keyStore.get()` API
|
|
123
|
+
- Secure memory wipe on `destroy()`
|
|
124
|
+
|
|
125
|
+
## Security-First Dependency Policy
|
|
126
|
+
|
|
127
|
+
> **Critical Decision:** This library intentionally avoids third-party dependencies for all security-sensitive operations.
|
|
128
|
+
|
|
129
|
+
### Rationale
|
|
130
|
+
|
|
131
|
+
When dealing with sensitive information (secrets, encryption keys, attestation documents), we cannot risk vulnerabilities introduced through third-party packages. Supply chain attacks and dependency vulnerabilities have proven to be a significant threat vector.
|
|
132
|
+
|
|
133
|
+
**Example: react2shell (Next.js)**
|
|
134
|
+
|
|
135
|
+
The `react2shell` vulnerability demonstrated how a widely-used framework could be compromised, potentially leaking sensitive data. Such incidents reinforce our decision to minimize external dependencies in security-critical code paths.
|
|
136
|
+
|
|
137
|
+
### What We Use
|
|
138
|
+
|
|
139
|
+
| Operation | Approach |
|
|
140
|
+
|-----------|----------|
|
|
141
|
+
| **AWS KMS calls** | AWS SDK only (official, maintained by AWS) |
|
|
142
|
+
| **Encryption/Decryption** | Node.js native `crypto` module |
|
|
143
|
+
| **In-memory encryption** | Node.js native `crypto` module |
|
|
144
|
+
| **JSON parsing** | Native `JSON.parse()` |
|
|
145
|
+
| **ENV parsing** | Custom implementation (no dotenv) |
|
|
146
|
+
| **Buffer operations** | Node.js native `Buffer` |
|
|
147
|
+
|
|
148
|
+
### Dependencies We Allow
|
|
149
|
+
|
|
150
|
+
| Dependency | Justification |
|
|
151
|
+
|------------|---------------|
|
|
152
|
+
| `@aws-sdk/client-kms` | Official AWS SDK, required for KMS operations |
|
|
153
|
+
| `js-yaml` (optional) | Well-audited, widely used, minimal attack surface |
|
|
154
|
+
| `pkijs` / `asn1js` | Required for CMS/EnvelopedData unwrapping in attestation flow. Well-audited PKI libraries maintained by PeculiarVentures. |
|
|
155
|
+
| `pvtsutils` | Utility dependency for PKIjs buffer operations |
|
|
156
|
+
|
|
157
|
+
### Dependencies We Avoid
|
|
158
|
+
|
|
159
|
+
| Category | Examples | Reason |
|
|
160
|
+
|----------|----------|--------|
|
|
161
|
+
| General utility libraries | lodash, underscore | Unnecessary for our use case |
|
|
162
|
+
| HTTP clients | axios, got, node-fetch | Use native `fetch` or `https` |
|
|
163
|
+
| Crypto wrappers | bcrypt wrappers, crypto-js | Use native `crypto` module |
|
|
164
|
+
| Config parsers | dotenv, config | Custom implementation for control |
|
|
165
|
+
|
|
166
|
+
## Nitro Enclave Attestation
|
|
167
|
+
|
|
168
|
+
For maximum security, use AWS Nitro Enclaves with attestation. **This library manages the entire attestation lifecycle internally.**
|
|
169
|
+
|
|
170
|
+
### Why Attestation is the Strongest Protection
|
|
171
|
+
|
|
172
|
+
**With attestation, even root access to the host cannot decrypt secrets** because:
|
|
173
|
+
|
|
174
|
+
1. KMS key policy requires a valid attestation document
|
|
175
|
+
2. Attestation proves the request comes from verified, untampered enclave code
|
|
176
|
+
3. Host OS and other processes cannot generate valid attestation
|
|
177
|
+
4. PCR values are cryptographically tied to the enclave image
|
|
178
|
+
|
|
179
|
+
### Full Attestation Flow
|
|
180
|
+
|
|
181
|
+
The library implements the complete attestation flow as required by AWS KMS:
|
|
182
|
+
|
|
183
|
+
```mermaid
|
|
184
|
+
sequenceDiagram
|
|
185
|
+
participant App as Your App
|
|
186
|
+
participant AM as AttestationManager
|
|
187
|
+
participant Anjuna as Nitro/Anjuna Endpoint
|
|
188
|
+
participant KMS as AWS KMS
|
|
189
|
+
|
|
190
|
+
App->>AM: createSecretKeyStore() with attestation enabled
|
|
191
|
+
AM->>AM: 1. Generate RSA-4096 key pair
|
|
192
|
+
AM->>Anjuna: 2. GET /attestation?public_key=...
|
|
193
|
+
Anjuna-->>AM: Attestation document (contains public key)
|
|
194
|
+
AM->>AM: Cache {document, privateKey, timestamp}
|
|
195
|
+
|
|
196
|
+
App->>AM: keyStore.get('SECRET')
|
|
197
|
+
AM->>KMS: 3. Decrypt + Recipient{AttestationDocument}
|
|
198
|
+
Note over KMS: KMS verifies attestation + PCR values
|
|
199
|
+
KMS-->>AM: 4. CiphertextForRecipient (CMS EnvelopedData)
|
|
200
|
+
AM->>AM: 5. Unwrap CMS with private key (PKIjs/asn1js)
|
|
201
|
+
AM-->>App: Decrypted plaintext
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
### Key Components
|
|
205
|
+
|
|
206
|
+
| Component | Purpose |
|
|
207
|
+
|-----------|---------|
|
|
208
|
+
| **Ephemeral RSA-4096 Key Pair** | Generated fresh for each attestation cycle. Public key embedded in attestation doc, private key used to unwrap CMS response. |
|
|
209
|
+
| **Attestation Document** | Fetched from Anjuna/Nitro endpoint. Contains public key + PCR measurements. Valid for 5 minutes. |
|
|
210
|
+
| **CiphertextForRecipient** | KMS returns encrypted plaintext wrapped in CMS EnvelopedData format instead of raw plaintext. |
|
|
211
|
+
| **CMS Unwrapping** | Uses PKIjs/asn1js to decrypt the CMS EnvelopedData using the ephemeral private key. |
|
|
212
|
+
|
|
213
|
+
### How to Enable Attestation
|
|
214
|
+
|
|
215
|
+
```javascript
|
|
216
|
+
const { createSecretKeyStore } = require('@faizahmed/secret-keystore');
|
|
217
|
+
|
|
218
|
+
const keyStore = await createSecretKeyStore(
|
|
219
|
+
{ type: 'env', content },
|
|
220
|
+
kmsKeyId,
|
|
221
|
+
{
|
|
222
|
+
attestation: {
|
|
223
|
+
enabled: true, // Enable attestation
|
|
224
|
+
required: true, // Fail if attestation unavailable
|
|
225
|
+
endpoint: 'http://localhost:8080/attestation', // Anjuna/Nitro endpoint
|
|
226
|
+
timeout: 10000, // Request timeout (ms)
|
|
227
|
+
userData: 'app-identifier' // Optional user data in attestation
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
);
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
### 5-Minute Auto-Refresh
|
|
234
|
+
|
|
235
|
+
AWS KMS requires attestation documents to be less than 5 minutes old. The library handles this **automatically**:
|
|
236
|
+
|
|
237
|
+
```mermaid
|
|
238
|
+
sequenceDiagram
|
|
239
|
+
participant App
|
|
240
|
+
participant AM as AttestationManager
|
|
241
|
+
participant KMS as AWS KMS
|
|
242
|
+
participant Anjuna
|
|
243
|
+
|
|
244
|
+
Note over AM: Document cached (age: 4m 55s)
|
|
245
|
+
App->>AM: decrypt()
|
|
246
|
+
AM->>KMS: Decrypt with cached doc
|
|
247
|
+
KMS-->>AM: ❌ ValidationException (doc expired)
|
|
248
|
+
|
|
249
|
+
AM->>AM: Detect 5-min age limit error
|
|
250
|
+
AM->>AM: Generate NEW RSA-4096 key pair
|
|
251
|
+
AM->>Anjuna: Fetch NEW attestation doc
|
|
252
|
+
Anjuna-->>AM: Fresh attestation document
|
|
253
|
+
AM->>AM: Update cache (timestamp reset)
|
|
254
|
+
|
|
255
|
+
AM->>KMS: Retry decrypt with fresh doc
|
|
256
|
+
KMS-->>AM: CiphertextForRecipient
|
|
257
|
+
AM->>AM: Unwrap CMS
|
|
258
|
+
AM-->>App: ✅ Decrypted plaintext
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
| Scenario | Library Behavior |
|
|
262
|
+
|----------|------------------|
|
|
263
|
+
| First request | Initialize (generate key pair, fetch doc, cache) |
|
|
264
|
+
| Document < 5 min old | Use cached document and private key |
|
|
265
|
+
| Document expired (KMS rejects) | Regenerate key pair, fetch new doc, retry once |
|
|
266
|
+
| Retry also fails | Throw `ATTESTATION_INIT_FAILED` error |
|
|
267
|
+
| Anjuna/Nitro unavailable | Throw `ATTESTATION_FETCH_FAILED` error |
|
|
268
|
+
|
|
269
|
+
### AttestationManager API
|
|
270
|
+
|
|
271
|
+
For advanced use cases, use `AttestationManager` directly:
|
|
272
|
+
|
|
273
|
+
```javascript
|
|
274
|
+
const { createAttestationManager } = require('@faizahmed/secret-keystore');
|
|
275
|
+
const { KMSClient } = require('@aws-sdk/client-kms');
|
|
276
|
+
|
|
277
|
+
// Create and auto-initialize
|
|
278
|
+
const manager = await createAttestationManager({
|
|
279
|
+
endpoint: 'http://localhost:8080/attestation',
|
|
280
|
+
timeout: 10000,
|
|
281
|
+
userData: 'my-app',
|
|
282
|
+
logger: console, // Optional logger
|
|
283
|
+
autoInitialize: true // Default: true
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
// Check status
|
|
287
|
+
console.log(manager.getStatus());
|
|
288
|
+
// {
|
|
289
|
+
// initialized: true,
|
|
290
|
+
// hasError: false,
|
|
291
|
+
// hasDocument: true,
|
|
292
|
+
// hasKeyPair: true,
|
|
293
|
+
// documentAge: 45000, // ms since document was fetched
|
|
294
|
+
// endpoint: 'http://...'
|
|
295
|
+
// }
|
|
296
|
+
|
|
297
|
+
// Decrypt with attestation
|
|
298
|
+
const kmsClient = new KMSClient({ region: 'us-east-1' });
|
|
299
|
+
const plaintext = await manager.decryptWithAttestation(
|
|
300
|
+
kmsClient,
|
|
301
|
+
ciphertextBlob,
|
|
302
|
+
kmsKeyId,
|
|
303
|
+
{ encryptionContext: { key: 'value' } }
|
|
304
|
+
);
|
|
305
|
+
|
|
306
|
+
// Force refresh (if needed)
|
|
307
|
+
await manager.reinitialize();
|
|
308
|
+
|
|
309
|
+
// Cleanup
|
|
310
|
+
manager.destroy();
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
### Security Considerations
|
|
314
|
+
|
|
315
|
+
| Aspect | Implementation |
|
|
316
|
+
|--------|----------------|
|
|
317
|
+
| **Key Material** | RSA-4096 private key never leaves process memory |
|
|
318
|
+
| **Key Lifetime** | Private key destroyed when document expires or manager is destroyed |
|
|
319
|
+
| **No External Storage** | Attestation materials exist only in memory |
|
|
320
|
+
| **CMS Unwrapping** | Uses PKIjs (well-audited library) for ASN.1/CMS operations |
|
|
321
|
+
| **Mutex Protection** | Concurrent initialization requests share single init promise |
|
|
322
|
+
|
|
323
|
+
## KMS Key Policies
|
|
324
|
+
|
|
325
|
+
### Basic Policy: Restrict to Specific IAM Role
|
|
326
|
+
|
|
327
|
+
```json
|
|
328
|
+
{
|
|
329
|
+
"Version": "2012-10-17",
|
|
330
|
+
"Statement": [
|
|
331
|
+
{
|
|
332
|
+
"Sid": "AllowDecryptFromSpecificRole",
|
|
333
|
+
"Effect": "Allow",
|
|
334
|
+
"Principal": {
|
|
335
|
+
"AWS": "arn:aws:iam::123456789012:role/MyAppRole"
|
|
336
|
+
},
|
|
337
|
+
"Action": "kms:Decrypt",
|
|
338
|
+
"Resource": "*"
|
|
339
|
+
}
|
|
340
|
+
]
|
|
341
|
+
}
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
### Enhanced Policy: Restrict to VPC
|
|
345
|
+
|
|
346
|
+
```json
|
|
347
|
+
{
|
|
348
|
+
"Version": "2012-10-17",
|
|
349
|
+
"Statement": [
|
|
350
|
+
{
|
|
351
|
+
"Sid": "AllowDecryptFromVPC",
|
|
352
|
+
"Effect": "Allow",
|
|
353
|
+
"Principal": {
|
|
354
|
+
"AWS": "arn:aws:iam::123456789012:role/MyAppRole"
|
|
355
|
+
},
|
|
356
|
+
"Action": "kms:Decrypt",
|
|
357
|
+
"Resource": "*",
|
|
358
|
+
"Condition": {
|
|
359
|
+
"StringEquals": {
|
|
360
|
+
"aws:SourceVpc": "vpc-12345678"
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
]
|
|
365
|
+
}
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
### Maximum Security: Nitro Enclave Attestation
|
|
369
|
+
|
|
370
|
+
```json
|
|
371
|
+
{
|
|
372
|
+
"Version": "2012-10-17",
|
|
373
|
+
"Statement": [
|
|
374
|
+
{
|
|
375
|
+
"Sid": "AllowDecryptFromEnclave",
|
|
376
|
+
"Effect": "Allow",
|
|
377
|
+
"Principal": {
|
|
378
|
+
"AWS": "arn:aws:iam::123456789012:role/EnclaveRole"
|
|
379
|
+
},
|
|
380
|
+
"Action": "kms:Decrypt",
|
|
381
|
+
"Resource": "*",
|
|
382
|
+
"Condition": {
|
|
383
|
+
"StringEqualsIgnoreCase": {
|
|
384
|
+
"kms:RecipientAttestation:PCR0": "EXPECTED_PCR0_VALUE",
|
|
385
|
+
"kms:RecipientAttestation:PCR1": "EXPECTED_PCR1_VALUE",
|
|
386
|
+
"kms:RecipientAttestation:PCR2": "EXPECTED_PCR2_VALUE"
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
]
|
|
391
|
+
}
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
## Authentication
|
|
395
|
+
|
|
396
|
+
### IAM Roles (Default & Recommended)
|
|
397
|
+
|
|
398
|
+
This package uses **IAM roles by default**. This is the recommended and most secure approach:
|
|
399
|
+
|
|
400
|
+
| Environment | IAM Role Type |
|
|
401
|
+
|-------------|---------------|
|
|
402
|
+
| EC2 | Instance Profile |
|
|
403
|
+
| ECS | Task Role |
|
|
404
|
+
| EKS | IAM Roles for Service Accounts (IRSA) |
|
|
405
|
+
| Lambda | Execution Role |
|
|
406
|
+
| Nitro Enclave | Enclave Role + Attestation |
|
|
407
|
+
|
|
408
|
+
**Benefits of IAM roles:**
|
|
409
|
+
- No credentials to manage or rotate
|
|
410
|
+
- No risk of credential leakage
|
|
411
|
+
- Automatic credential rotation by AWS
|
|
412
|
+
- Fine-grained access control via IAM policies
|
|
413
|
+
|
|
414
|
+
### Explicit Credentials (Not Recommended)
|
|
415
|
+
|
|
416
|
+
Only use explicit credentials when IAM roles are not available (e.g., local development):
|
|
417
|
+
|
|
418
|
+
```bash
|
|
419
|
+
# CLI: Explicit opt-in required
|
|
420
|
+
npx @faizahmed/secret-keystore encrypt \
|
|
421
|
+
--kms-key-id="alias/my-key" \
|
|
422
|
+
--use-credentials
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
```javascript
|
|
426
|
+
// Runtime: Explicit opt-in required
|
|
427
|
+
const keyStore = await createSecretKeyStore(source, kmsKeyId, {
|
|
428
|
+
aws: {
|
|
429
|
+
credentials: {
|
|
430
|
+
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
|
|
431
|
+
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
});
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
> ⚠️ **Warning:** Never commit credentials to version control. Use environment variables for local development only.
|
|
438
|
+
|
|
439
|
+
## Best Practices
|
|
440
|
+
|
|
441
|
+
### Infrastructure
|
|
442
|
+
|
|
443
|
+
| Practice | Why |
|
|
444
|
+
|----------|-----|
|
|
445
|
+
| **Use IAM Roles (default)** | This package uses IAM roles by default—no credentials to manage |
|
|
446
|
+
| **Restrict KMS Key Policy** | Limit decrypt to specific roles, VPCs, or enclaves |
|
|
447
|
+
| **Enable CloudTrail** | Log all KMS operations for audit and alerting |
|
|
448
|
+
| **Use Nitro Enclaves** | For sensitive workloads, attestation provides strongest protection |
|
|
449
|
+
| **Separate Keys per Environment** | Use different KMS keys for dev, staging, production |
|
|
450
|
+
|
|
451
|
+
### Application
|
|
452
|
+
|
|
453
|
+
| Practice | Why |
|
|
454
|
+
|----------|-----|
|
|
455
|
+
| **Don't use explicit credentials** | Rely on IAM roles; explicit credentials are for local dev only |
|
|
456
|
+
| **Set `throwOnMissingKey: true`** | Fail fast in production if secrets are missing |
|
|
457
|
+
| **Validate at Startup** | Ensure all required secrets are available before serving traffic |
|
|
458
|
+
| **Don't Log Secrets** | Never log decrypted values, even in debug mode |
|
|
459
|
+
| **Use TTL with Auto-Refresh** | Enable `access.ttl` and `access.autoRefresh` for long-running services |
|
|
460
|
+
| **Call `destroy()` on Shutdown** | Securely wipe secrets from memory |
|
|
461
|
+
|
|
462
|
+
### Operations
|
|
463
|
+
|
|
464
|
+
| Practice | Why |
|
|
465
|
+
|----------|-----|
|
|
466
|
+
| **Monitor KMS Usage** | Alert on unexpected decrypt operations |
|
|
467
|
+
| **Review IAM Policies** | Regularly audit who has access to KMS keys |
|
|
468
|
+
| **Test Recovery** | Ensure you can recover if KMS access is lost |
|
|
469
|
+
| **Document Key Policies** | Keep track of what each key policy allows |
|
|
470
|
+
| **Rotate Secrets Regularly** | Re-encrypt with new values periodically |
|
|
471
|
+
|
|
472
|
+
## Security Checklist
|
|
473
|
+
|
|
474
|
+
### Basic Security
|
|
475
|
+
|
|
476
|
+
- [ ] KMS key created with appropriate key policy
|
|
477
|
+
- [ ] IAM role configured with minimal permissions
|
|
478
|
+
- [ ] Config files encrypted using CLI (`--kms-key-id` is required)
|
|
479
|
+
- [ ] Reserved keys (`KMS_KEY_ID`, `AWS_REGION`) are the only plaintext values
|
|
480
|
+
- [ ] Application uses `throwOnMissingKey: true` in production
|
|
481
|
+
- [ ] Application calls `keyStore.destroy()` on shutdown
|
|
482
|
+
- [ ] CloudTrail logging enabled for KMS operations
|
|
483
|
+
|
|
484
|
+
### Runtime Protection
|
|
485
|
+
|
|
486
|
+
- [ ] TTL with auto-refresh enabled for long-running services
|
|
487
|
+
- [ ] Secure memory wipe enabled (default)
|
|
488
|
+
- [ ] In-memory AES-256-GCM encryption enabled (default)
|
|
489
|
+
- [ ] No secrets logged, even in debug mode
|
|
490
|
+
|
|
491
|
+
### Nitro Enclave Attestation (Maximum Security)
|
|
492
|
+
|
|
493
|
+
- [ ] Anjuna/Nitro attestation endpoint accessible from enclave
|
|
494
|
+
- [ ] KMS key policy includes PCR value conditions
|
|
495
|
+
- [ ] `attestation.enabled: true` and `attestation.required: true` set
|
|
496
|
+
- [ ] `attestation.endpoint` points to correct Anjuna/Nitro URL
|
|
497
|
+
- [ ] VPC restrictions in KMS key policy (optional but recommended)
|
|
498
|
+
- [ ] Fallback disabled in production (`fallbackToStandard: false`)
|
|
499
|
+
|
|
500
|
+
## Further Reading
|
|
501
|
+
|
|
502
|
+
- [AWS KMS Key Policies](https://docs.aws.amazon.com/kms/latest/developerguide/key-policies.html)
|
|
503
|
+
- [AWS Nitro Enclaves](https://docs.aws.amazon.com/enclaves/latest/user/nitro-enclave.html)
|
|
504
|
+
- [KMS Cryptographic Details](https://docs.aws.amazon.com/kms/latest/cryptographic-details/intro.html)
|
|
505
|
+
- [IAM Roles for Amazon EC2](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/iam-roles-for-amazon-ec2.html)
|