@63klabs/cache-data 1.2.3 → 1.2.6

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/README.md CHANGED
@@ -1,6 +1,6 @@
1
- # Cache Data
1
+ # 63Klabs Cache-Data
2
2
 
3
- A package for node.js applications to access and cache data from remote API endpoints or other sources using AWS S3 and DynamoDb.
3
+ A package for AWS Lambda Node.js applications to access and cache data from remote API endpoints (or other sources) utilizing AWS S3 and DynamoDb for the cache storage. Also provides a simple request handling, routing, and response logging framework for running a web service with minimal dependencies.
4
4
 
5
5
  > Note: This repository and package has moved from chadkluck to 63Klabs but is still managed by the same developer.
6
6
 
@@ -8,1258 +8,81 @@ A package for node.js applications to access and cache data from remote API endp
8
8
 
9
9
  ## Description
10
10
 
11
- For Lambda functions written in Node.js that require caching of data either of an internal process or external data sources such as APIs. It is written specifically to be used in AWS Lambda functions using the Node runtime. However, it can be used in EC2 or other environments to access S3 and DynamoDb. While out of the box it can fetch data from remote endpoint APIs, custom Data Access Objects can be written to provide caching of data from all sorts of sources including resource expensive database calls.
11
+ For AWS Lambda functions written in Node.js that require caching of data either of an internal process or external data source such as remote API endpoints. While out of the box it can fetch data from remote endpoint APIs, custom Data Access Objects can be written to provide caching of data from all sorts of sources including resource expensive database calls.
12
12
 
13
- It also has several utility functions such as one that can load sensitive data from AWS SSM Parameter Store at load time.
13
+ It has several utility functions such `DebugAndLog`, `Timer`, and SSM Parameter Store loaders.
14
14
 
15
- This package has been used in production for applications receiving over 1 million requests per week with a 75% cache-hit rate lowering latency to less than 100ms in most cases. This is a considerable improvement when faced with resource intense processes, connection pools, API rate limits, and slow endpoints.
15
+ It can be used in place of Express.js for simple web service applications as it also includes functions for handling and validating requests, routing, and client request logging.
16
+
17
+ This package has been used in production for web service applications receiving over 1 million requests per week with a 75% cache-hit rate lowering latency to less than 100ms in most cases. This is a considerable improvement when faced with resource intense processes, connection pools, API rate limits, and slow endpoints.
16
18
 
17
19
  ## Getting Started
18
20
 
19
21
  ### Requirements
20
22
 
21
- * Current supported versions of Node.js on Lambda
22
- * AWS Lambda, S3 bucket, DynamoDb table, and SSM Parameter Store
23
- * A basic understanding of CloudFormation, Lambda, S3, DynamoDb, and SSM Parameters
24
- * A basic understanding of IAM policies, especially the Lambda Execution Role, that will allow Lambda to access S3, DynamoDb, and SSM Parameter Store
25
- * Lambda function should have between 512MB and 1024MB of memory allocated. (256MB minimum). See section regarding Lambda Memory under install.
23
+ - Node >18 runtime on Lambda
24
+ - AWS Lambda, S3 bucket, DynamoDb table, and SSM Parameter Store
25
+ - A basic understanding of CloudFormation, Lambda, S3, DynamoDb, and SSM Parameters
26
+ - A basic understanding of IAM policies, especially the Lambda Execution Role, that will allow Lambda to access S3, DynamoDb, and SSM Parameter Store
27
+ - Lambda function should have between 512MB and 1024MB of memory allocated. (256MB minimum). See [Lambda Optimization: Memory Allocation](./docs/lambda-optimization/README.md#lambda-memory-allocation).
26
28
 
27
29
  ### Installing
28
30
 
29
- 1. Make sure your function is using an AWS Lambda supported version of Node and has at least 256MB allocated (512-1024MB recommended).
30
- 2. Add the cache-data environment variables to your Lambda function. Also update your Lambda's execution role to access your S3 and DynamoDb.
31
- 3. Add an S3 bucket and DynamoDb table to store your cache either in the application CloudFormation template or as separate infrastructure.
32
- 4. Install the @63klabs/cache-data package `npm install @63klabs/cache-data`
33
- 5. Add the cache code to your Lambda function
34
-
35
- #### Lambda Memory Allocation
36
-
37
- As pointed out in many online resources, including [AWS's own documentation](https://docs.aws.amazon.com/lambda/latest/operatorguide/computing-power.html), Lambda applications should be given more than the default 128MB when using network resources and processing data. I recommend trying 512MB and adjusting depending on your workload and execution experiences. See [Lower AWS Lambda bill by increasing memory by Taavi Rehemägi](https://dashbird.io/blog/lower-aws-lambda-bill-increasing-memory/).
38
-
39
- Optimal performance is somewhere between 256MB and 1024MB. 1024MB is recommended. (I have seen additional improvements by using the AWS Graviton ARM architecture in Lambda.)
40
-
41
- Example: The charts below reflect 1 million requests over a seven-day period. As you can see, the invocations remained at a high level throughout the seven-day period. There was a dramatic drop in execution time once the memory was increased from 128 to 512MB. Latency was also improved. This also reduced the number of concurrent executions taking place. (The spike in errors was due to a 3rd party endpoint being down.)
42
-
43
- ![Metrics before and after upgrade to 512MB with 1M invocations over a 7 day period](https://github.com/chadkluck/npm-chadkluck-cache-data/assets/17443749/0ec98af5-edcf-4e2a-8017-dd17b9c7a11c)
44
-
45
- If you are worried about cost, the Lambda function demonstrated above handles approximately 4.6 million requests a month, each averaging 46ms in Lambda run time. This means that the Lambda function executes a total of 211,000 seconds a month which is still within the 400,000 seconds provided by the Free Tier. If there was no free tier, the cost would have been around USD $2.00.
46
-
47
- #### Lambda Environment Variables and Execution Role
48
-
49
- Below are some typical settings for use with Cache-Data
50
-
51
- ```yaml
52
- Resources:
53
-
54
- # API Gateway
55
-
56
- WebApi:
57
- Type: AWS::Serverless::Api
58
- Properties:
59
- Name: !Sub '${APPLICATION-API}-WebApi'
60
- StageName: !Ref ApiPathBase
61
- PropagateTags: True
62
- TracingEnabled: True
63
- OpenApiVersion: 3.0.0
64
-
65
- # Lambda Function
66
-
67
- AppFunction:
68
- Type: AWS::Serverless::Function
69
- Properties:
70
- # ...
71
- Runtime: nodejs22.x
72
- MemorySize: 1028
73
- Role: !GetAtt LambdaExecutionRole.Arn
74
-
75
- # Lambda Insights and X-Ray
76
- Tracing: Active # X-Ray
77
- # Required layers for 1) XRay and Lambda Insights, and 2) AWS Secrets Manager and Parameter Store Extension
78
- Layers:
79
- - !Sub "arn:aws:lambda:${AWS::Region}:${ACCT_ID_FOR_AWS_INSIGHTS_EXT}:layer:LambdaInsightsExtension:52" # Check for latest version: https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/Lambda-Insights-extension-versionsx86-64.html
80
- - !Sub "arn:aws:lambda:${AWS::Region}:${ACCT_ID_FOR_AWS_PARAM_AND_SECRETS_EXT}:layer:AWS-Parameters-and-Secrets-Lambda-Extension:11" # https://docs.aws.amazon.com/systems-manager/latest/userguide/ps-integration-lambda-extensions.html#ps-integration-lambda-extensions-add
81
-
82
- Environment:
83
- Variables:
84
- detailedLogs: "5" # Use "0" for production
85
- deployEnvironment: "TEST" # "PROD"
86
- paramStore: "/" # SSM Parameter store can use a hierarchy to organize your apps parameters
87
-
88
- # Cache-Data settings (from: https://www.npmjs.com/package/@63klabs/cache-data)
89
- CacheData_DynamoDbTable: !Ref CacheDataDynamoDbTable
90
- CacheData_S3Bucket: !Ref CacheDataS3Bucket
91
- CacheData_CryptSecureDataAlgorithm: !Ref CacheDataCryptSecureDataAlg
92
- CacheData_CryptIdHashAlgorithm: !Ref CacheDataCryptIdHashAlgorithm
93
- CacheData_DynamoDb_maxCacheSize_kb: !Ref CacheDataDbMaxCacheSizeInKB
94
- CacheData_PurgeExpiredCacheEntriesAfterXHours: !Ref CacheDataPurgeExpiredCacheEntriesInHours
95
- CacheData_ErrorExpirationInSeconds: !Ref CacheDataErrorExpirationInSeconds
96
- CacheData_TimeZoneForInterval: !Ref CacheDataTimeZoneForInterval
97
- CacheData_AWSXRayOn: !Ref CacheDataAWSXRayOn
98
-
99
-
100
- # -- LambdaFunction Execution Role --
101
-
102
- LambdaExecutionRole:
103
- Type: AWS::IAM::Role
104
- Properties:
105
- RoleName: !Sub "${LAMBDA_EXECUTION_ROLE_NAME}-ExecutionRole"
106
- Description: "IAM Role that allows the Lambda permission to execute and access resources"
107
- Path: /
108
-
109
- AssumeRolePolicyDocument:
110
- Statement:
111
- - Effect: Allow
112
- Principal:
113
- Service: [lambda.amazonaws.com]
114
- Action: sts:AssumeRole
115
-
116
- # These are for application monitoring via LambdaInsights and X-Ray
117
- ManagedPolicyArns:
118
- - 'arn:aws:iam::aws:policy/CloudWatchLambdaInsightsExecutionRolePolicy'
119
- - 'arn:aws:iam::aws:policy/AWSXRayDaemonWriteAccess'
120
-
121
- # These are the resources your Lambda function needs access to
122
- # Logs, SSM Parameters, DynamoDb, S3, etc.
123
- # Define specific actions such as get/put (read/write)
124
- Policies:
125
- - PolicyName: LambdaResourceAccessPolicies
126
- PolicyDocument:
127
- Statement:
128
-
129
- - Sid: LambdaAccessToWriteLogs
130
- Action:
131
- - logs:CreateLogGroup
132
- - logs:CreateLogStream
133
- - logs:PutLogEvents
134
- Effect: Allow
135
- Resource: !GetAtt AppLogGroup.Arn
136
-
137
- # cache-data Parameter Read Access (from: https://www.npmjs.com/package/@63klabs/cache-data)
138
- - Sid: LambdaAccessToSSMParameters
139
- Action:
140
- - ssm:DescribeParameters
141
- - ssm:GetParameters
142
- - ssm:GetParameter
143
- - ssm:GetParametersByPath
144
- Effect: Allow
145
- Resource:
146
- - !Sub "arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter${ParameterStoreHierarchy}*"
147
-
148
- # cache-data S3 bucket (from: https://www.npmjs.com/package/@63klabs/cache-data)
149
- - Sid: LambdaAccessToS3BucketCacheData
150
- Action:
151
- - s3:PutObject
152
- - s3:GetObject
153
- - s3:GetObjectVersion
154
- Effect: Allow
155
- Resource: !Join [ '', [ !GetAtt CacheDataS3Bucket.Arn, '/cache/*' ] ]
156
-
157
- # cache-data DynamoDb table (from: https://www.npmjs.com/package/@63klabs/cache-data)
158
- - Sid: LambdaAccessToDynamoDBTableCacheData
159
- Action:
160
- - dynamodb:GetItem
161
- - dynamodb:Scan
162
- - dynamodb:Query
163
- - dynamodb:BatchGetItem
164
- - dynamodb:PutItem
165
- - dynamodb:UpdateItem
166
- - dynamodb:BatchWriteItem
167
- Effect: Allow
168
- Resource: !GetAtt CacheDataDynamoDbTable.Arn
169
-
170
- ```
171
-
172
- The example definition above uses the following parameters. You can either add these to your template's `Parameters` section or hard-code values for your environment variables using the specifications outlined.
173
-
174
- ```yaml
175
- Parameters:
176
-
177
-
178
- # ---------------------------------------------------------------------------
179
- # Cache-Data Parameters
180
- # From: https://www.npmjs.com/package/@63klabs/cache-data
181
-
182
- CacheDataDbMaxCacheSizeInKB:
183
- Type: Number
184
- Description: "DynamoDb does better when storing smaller pieces of data. Choose the cut-off in KB that large objects should be stored in S3 instead (10)"
185
- Default: 10
186
- MinValue: 10
187
- MaxValue: 200
188
- ConstraintDescription: "Numeric value between 10 and 200 (inclusive)"
189
- CacheDataCryptIdHashAlgorithm:
190
- Type: String
191
- Description: "Hash algorithm used for generating the URI ID to identify cached requests. This is for generating IDs, not crypto."
192
- Default: "RSA-SHA256"
193
- AllowedValues: ["RSA-SHA256", "RSA-SHA3-224", "RSA-SHA3-256", "RSA-SHA3-384", "RSA-SHA3-512"]
194
- ConstraintDescription: "Use possible hashes available from Node.js in the RSA- category (RSA-SHA256 to RSA-SM3)"
195
- CacheDataCryptSecureDataAlg:
196
- Type: String
197
- Description: "Cryptographic algorithm to use for storing sensitive cached data in S3 and DynamoDb"
198
- Default: "aes-256-cbc"
199
- AllowedValues: ["aes-256-cbc", "aes-256-cfb", "aes-256-cfb1", "aes-256-cfb8", "aes-256-ofb"]
200
- ConstraintDescription: "Use possible cipher algorithms available (crypto.getCiphers()) from Node.js in the aes-256-xxx category"
201
- CacheDataErrorExpirationInSeconds:
202
- Type: Number
203
- Description: "How long should errors be cached? This prevents retrying a service that is currently in error too often (300 is recommended)"
204
- Default: 300
205
- MinValue: 1
206
- ConstraintDescription: "Choose a value of 1 or greater"
207
- CacheDataPurgeExpiredCacheEntriesInHours:
208
- Type: Number
209
- Description: "The number of hours expired cached data should be kept before purging. Expired cache data may be used if the source returns an error."
210
- Default: 24
211
- MinValue: 1
212
- ConstraintDescription: "Choose a value of 1 or greater"
213
- CacheDataPurgeAgeOfCachedBucketObjInDays:
214
- Type: Number
215
- Description: "Similar to CacheData_PurgeExpiredCacheEntriesInHours, but for the S3 Bucket. S3 calculates from time object is created/last modified (not accessed). This should be longer than your longest cache expiration set in custom/policies. Keeping objects in S3 for too long increases storage costs. (30 is recommended)"
216
- Default: 15
217
- MinValue: 3
218
- ConstraintDescription: "Choose a value of 3 days or greater. This should be slightly longer than the longest cache expiration expected"
219
- CacheDataTimeZoneForInterval:
220
- Type: String
221
- Description: "Cache-Data may expire using an interval such as every four, six, twelve, ... hours on the hour starting at midnight. What timezone holds the midnight to calculate from?"
222
- Default: "Etc/UTC"
223
- AllowedValues: ["Etc/UTC", "America/Puerto_Rico", "America/New_York", "America/Indianapolis", "America/Chicago", "America/Denver", "America/Phoenix", "America/Los_Angeles", "America/Anchorage", "Pacific/Honolulu"] # https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
224
- ConstraintDescription: "Common examples for United States of America. Accepted values can be changed in the template for your region."
225
- CacheDataAWSXRayOn:
226
- Type: String
227
- Description: "Turn on AWS XRay tracing for Cache-Data"
228
- Default: "false"
229
- AllowedValues: ["true", "false"]
230
- ConstraintDescription: "Accepted values are true or false"
231
- ```
232
-
233
- #### Cache-Data DynamoDb and S3 CloudFormation Resource Templates
234
-
235
- Use the following as an example for creating your DynamoDb and S3 cache storage locations.
236
-
237
- ```yaml
238
- Resources:
239
-
240
- # ... all your other resources
241
- # ... make sure your Lambda function has between 512MB and 1024MB allocated (256MB minimum)
242
- # ... also make sure you added environment variables to your Lambda function
243
- # ... and make sure your Lambda Execution Role grants access to your DynamoDb and S3 buckets
244
-
245
- # ---------------------------------------------------------------------------
246
- # Cache-Data
247
- # From: https://www.npmjs.com/package/@63klabs/cache-data
248
- # Your Lambda function will need access via the Execution Role
249
-
250
- # -- Cache-Data DynamoDb Table --
251
-
252
- CacheDataDynamoDbTable:
253
- Type: AWS::DynamoDB::Table
254
- Description: Table to store Cache-Data.
255
- Properties:
256
- TableName: !Sub '${YOUR-DYNAMODB-TABLE}-CacheData'
257
- AttributeDefinitions:
258
- - AttributeName: "id_hash"
259
- AttributeType: "S"
260
- KeySchema:
261
- - AttributeName: "id_hash"
262
- KeyType: "HASH"
263
- TimeToLiveSpecification:
264
- AttributeName: "purge_ts"
265
- Enabled: true
266
- BillingMode: "PAY_PER_REQUEST"
267
-
268
-
269
- # -- Cache-Data S3 Bucket --
270
-
271
- CacheDataS3Bucket:
272
- Type: AWS::S3::Bucket
273
- Description: S3 Bucket to store Cache-Data too big for DynamoDb. Cache-Data stores objects in /cache directory. The application may store additional data outside of the cache directory.
274
- Properties:
275
- BucketName: !Sub "${YOUR-BUCKET-NAME}-cachedata"
276
- PublicAccessBlockConfiguration:
277
- BlockPublicAcls: true
278
- BlockPublicPolicy: true
279
- IgnorePublicAcls: true
280
- RestrictPublicBuckets: true
281
- BucketEncryption:
282
- ServerSideEncryptionConfiguration:
283
- - ServerSideEncryptionByDefault:
284
- SSEAlgorithm: AES256
285
- LifecycleConfiguration:
286
- Rules:
287
- - Id: "ExpireObjects"
288
- AbortIncompleteMultipartUpload:
289
- DaysAfterInitiation: 1
290
- ExpirationInDays: !Ref CacheDataPurgeAgeOfCachedBucketObjInDays
291
- Prefix: "cache" # this will limit this policy to YOURBUCKETNAME/cache/*
292
- NoncurrentVersionExpirationInDays: !Ref CacheDataPurgeAgeOfCachedBucketObjInDays
293
- Status: "Enabled" # Enable only if you are going to use this LifecycleConfiguration
294
-
295
- # -- Cache-Data S3 Bucket Policy --
296
-
297
- CacheDataS3BucketPolicy:
298
- Type: AWS::S3::BucketPolicy
299
- Properties:
300
- Bucket: !Ref CacheDataS3Bucket
301
- PolicyDocument:
302
- Version: "2012-10-17"
303
- Id: SecurityPolicy
304
- Statement:
305
- - Sid: "DenyNonSecureTransportAccess"
306
- Effect: Deny
307
- Principal: "*"
308
- Action: "s3:*"
309
- Resource:
310
- - !GetAtt CacheDataS3Bucket.Arn
311
- - !Join [ '', [ !GetAtt CacheDataS3Bucket.Arn, '/*' ] ]
312
- Condition:
313
- Bool:
314
- "aws:SecureTransport": false
315
-
316
- ```
317
-
318
- #### Install npm Package and Add Starter Code
319
-
320
- 1. Go to your application directory
321
- 2. Run the command `npm i @63klabs/cache-data`
322
- 3. Add `const { tools, cache, endpoint } = require('@63klabs/cache-data');` to your script
323
- 4. Add a Config class to your script. This should be placed before the handler so it only is defined during cold starts. This can also be imported from a separate script.
324
- 5. Add `Config.init()` after the Config class but before the handler.
325
- 5. Add `await tools.Config.promise();` in the handler to make sure the Config has completed.
326
-
327
- ```javascript
328
- const { tools, cache, endpoint } = require('@63klabs/cache-data');
329
-
330
- class Config extends tools._ConfigSuperClass {
331
- static async init() {
332
- tools._ConfigSuperClass._promise = new Promise(async (resolve, reject) => {
333
- resolve(true);
334
- };
335
- };
336
-
337
- /* initialize the Config */
338
- obj.Config.init();
339
-
340
- exports.handler = async (event, context, callback) => {
341
- await tools.Config.promise();
342
- let response = {
343
- statusCode: 200,
344
- body: JSON.stringify({message: "Hello from Lambda!"}),
345
- headers: {'content-type': 'application/json'}
346
- };
347
-
348
- callback(null, response);
349
- }
350
- ```
351
-
352
- Note: `deployEnvironment` is only one of the possible runtime environment variables the script checks for. You may also use `env`, `deployEnvironment`, `environment`, or `stage`. Also note the confusion that may be had when we are talking about "environment" as it refers to both Lambda Runtime Environment Variables as well as a variable denoting a Deployment Environment (Production, Development, Testing, etc.).
353
-
354
- (Runtime Environment variables are accessed using `process.env.`_`variableName`_.)
355
-
356
- ### Usage
357
-
358
- Note: There is a sample app and tutorial that works with a CI/CD pipeline available at the repository: [serverless-webservice-template-for-pipeline-atlantis](https://github.com/chadkluck/serverless-webservice-template-for-pipeline-atlantis)
359
-
360
- #### Config
361
-
362
- ##### Parameters and Secrets
363
-
364
- Cache-Data requires an 32 character hexidecimal key to encrypt data when at rest. This can be stored in an SSM Parameter named `crypt_secureDataKey`.
365
-
366
- You have two options for storing and retrieving your SSM Parameters:
367
-
368
- 1. Using the Cache-Data SSM Parameter access function.
369
- 2. Using the AWS Parameter and Secrets Lambda Extension.
370
-
371
- Both are easily accessed using functions in the Cache-Data toolkit.
372
-
373
- ###### Option 1: Cache-Data SSM Parameter access function
374
-
375
- This runs in the Config.init() function and can be used to retrieve all of the parameters needed for your application.
376
-
377
- ```javascript
378
- class Config extends tools._ConfigSuperClass {
379
- static async init() {
380
-
381
- tools._ConfigSuperClass._promise = new Promise(async (resolve, reject) => {
382
-
383
- try {
384
-
385
- let params = await this._initParameters(
386
- [
387
- {
388
- "group": "app", // so we can do params.app.weatherapikey later
389
- "path": process.env.paramStore
390
- }
391
- ]
392
- );
393
-
394
- // You can access within init() using params.app.crypt_secureDataKey
395
-
396
- resolve(true);
397
- } catch(error) {
398
- reject(null);
399
- }
400
- });
401
- }
402
- }
403
- ```
404
-
405
- Accesses the SSM Parameter Store and places any parameters found under `/apps/my_cool_app/` into a `params.app` variable. You'll see that the cache initialization uses `params.app.crypt_secureDataKey` which is the parameter we created under `/apps/my_cool_app/crypt_secureDataKey`.
406
-
407
- New deployments and new concurrent instances will pick up changes to a Parameter value, but long-running instances will not. If you change the value of a Parameter then you need to redeploy the application in order to clear out any use of the old value.
408
-
409
- ###### Option 2: AWS Parameter and Secrets Lambda Extension
410
-
411
- This is a more robust option and works with Secrets Manager as well. It requires the installation of a Lambda layer and then use of the `CachedSecret` and/or `CachedSSMParameter` Class from the Cache-Data tool-kit.
412
-
413
- Another advantage is that unlike the previous method, this method will pick up on any Secret and Parameter value changes and begin using the new values within 5 minutes (unless you set the cache for longer).
414
-
415
- First, make sure you install the Lambda layer:
416
-
417
- ```yaml
418
- Resources:
419
-
420
- AppFunction:
421
- Type: AWS::Serverless::Function
422
- Properties:
423
- # ...
424
- Layers:
425
- - !Sub "arn:aws:lambda:${AWS::Region}:${ACCT_ID_FOR_AWS_PARAM_AND_SECRETS_EXT}:layer:AWS-Parameters-and-Secrets-Lambda-Extension:11" # https://docs.aws.amazon.com/systems-manager/latest/userguide/ps-integration-lambda-extensions.html#ps-integration-lambda-extensions-add
426
- ```
427
-
428
- Next, in your code, create the object to store your Parameter or Secret.
429
-
430
- ```javascript
431
- const myKey = new tools.CachedSSMParameter('appSecretKey', {refreshAfter: 1600});
432
- ```
433
-
434
- Finally, make sure you prime() and await the value.
435
-
436
- ```javascript
437
- myKey.prime(); // request in the background so you can do other things before using it.
438
-
439
- // ... do many things
440
-
441
- let password = await myKey.getValue();
442
- ```
443
-
444
- If you place it within an object you can also stringify that object and it will replace the reference with the secret. (You need to await the .prime() before processing)
445
-
446
- ```javascript
447
- myKey.prime(); // request in the background so you can do other things before using it.
448
-
449
- // ... do many things
450
- const dbconn = { username: myUsername, password: myKey };
451
-
452
- await myKey.prime();
453
- connect(JSON.parse(JSON.stringify(dbconn)));
454
-
455
- // or use toString()
456
- await myKey.prime();
457
- connect( {
458
- username: `${myUsername}`,
459
- password: `${myKey}`
460
- })
461
- ```
462
-
463
- ##### Connections, and Cache
464
-
465
- The cache object acts as an intermediary between your application and your data (whether it be a remote endpoint or other storage/process mechanism).
466
-
467
- Before you can use Parameter Store, S3, and DynamoDb for the cache, they need to be set up with the proper access granted to your Lambda function.
468
-
469
- 1. Set up an S3 bucket (Your application will store cache data in `/cache`)
470
- 2. Create a DynamoDb table
471
- 3. Create a Parameter in SSM Parameter store `/app/my_cool_app/crypt_secureDataKey` and set the secret text to a 64 character length hex value. (64 hex characters because we are using a 256 bit key and cipher (`aes-256-ofb`)in the example below)
472
- 4. Make sure you set up IAM policies to allow you Lambda function access to the S3 bucket, DynamoDb table, and SSM Parameter store.
473
-
474
- Once the S3 bucket, DynamoDb table, and SSM Parameter are set up we can focus on your Lambda function.
475
-
476
- During your application initialization (but not for each request) we need to initialize the Config object.
477
-
478
- The class below will do the following three things:
479
-
480
- 1. Bring in the secret key (and other parameters) from SSM Parameter Store.
481
- 2. Create connections with cache settings for each connection
482
- 3. Initialize the Cache
483
-
484
- This code can be put into a separate file and brought in using a `require` statement. It should be scoped to the highest level of your Lambda function and not in the request handler.
485
-
486
- ```js
487
- /* EXAMPLE USING the this._initParameters method of obtaining parameters during Config.init() */
488
-
489
- // require cache-data
490
- const { tools, cache, endpoint } = require('@63klabs/cache-data');
491
-
492
- /**
493
- * Extends tools._ConfigSuperClass
494
- * Used to create a custom Config interface
495
- * Usage: should be placed near the top of the script file outside
496
- * of the event handler. It should be global and must be initialized.
497
- * @example
498
- * const obj = require("./classes.js");
499
- * obj.Config.init();
500
- */
501
- class Config extends tools._ConfigSuperClass {
502
-
503
- /**
504
- * This is custom inititialization code for the application. Depending
505
- * upon needs, the _init functions from the super class may be used
506
- * as needed. Init is async, and a promise is stored, allowing the
507
- * lambda function to wait until the promise is finished.
508
- */
509
- static async init() {
510
-
511
- tools._ConfigSuperClass._promise = new Promise(async (resolve, reject) => {
512
-
513
- try {
514
-
515
- let params = await this._initParameters(
516
- [
517
- {
518
- "group": "app", // so we can do params.app.weatherapikey later
519
- "path": "/apps/my_cool_app/" // process.env.paramStorePath // or store as a Lambda environment variable
520
- }
521
- ]
522
- );
523
-
524
- // after we have the params, we can set the connections
525
- let connections = new tools.Connections();
526
-
527
- /* NOTE: instead of hard coding connections, you could import
528
- from a connections file and then add in any additional values
529
- such as keys from the Param store
530
- */
531
-
532
- // for games demo from api.chadkluck.net
533
- connections.add( {
534
- name: "demo",
535
- host: "api.chadkluck.net",
536
- path: "/games",
537
- parameters: {},
538
- headers: {
539
- referer: "https://chadkluck.net"
540
- },
541
- cache: [
542
- {
543
- profile: "games",
544
- overrideOriginHeaderExpiration: true, // if the endpoint returns an expiration, do we ignore it for our own?
545
- defaultExpirationInSeconds: (10 * 60),// , // 10 minutes
546
- expiresIsOnInterval: true, // for example, a 10 min cache can expire on the hour, 10, 20, 30... after. 24 hour cache can expire at midnight. 6 hour cache can expire at 6am, noon, 6pm, and midnight
547
- headersToRetain: "", // what headers from the endpoint do we want to keep with the cache data?
548
- hostId: "demo", // log entry friendly (or not)
549
- pathId: "games", // log entry friendly (or not)
550
- encrypt: false // you can set this to true and it will use the key from param store and encrypt data at rest in S3 and DynamoDb
551
- }
552
- ]
553
- } );
554
-
555
- tools._ConfigSuperClass._connections = connections;
556
-
557
- // Cache settings
558
- cache.Cache.init({
559
- dynamoDbTable: "yourDynamoDbTable", // replace with the name of a DynamoDb table to store cached data
560
- s3Bucket: "yourS3Bucket", // replace with a bucket name to store cache data. Data will be stored in /cache in yourS3Bucket
561
- secureDataAlgorithm: "aes-256-ofb", // how do we encrypt data at rest
562
- secureDataKey: Buffer.from(params.app.crypt_secureDataKey, "hex"), // using the parameter from above during Config.init()
563
- //secureDataKey: new tools.CachedSSMParameter('/apps/my_cool_app/CacheData_SecureDataKey', {refreshAfter: 300}), // if using tools.CachedSSMParameter()
564
- idHashAlgorithm: "RSA-SHA3-512", // the alg used to create a unique hash identifier for requests so we can tell them apart in the cache
565
- DynamoDbMaxCacheSize_kb: 20, // data larger than this (in KB) will be stored in S3 to keep DynamoDb running efficently
566
- purgeExpiredCacheEntriesAfterXHours: 24, // expired caches hang around for a while before we purge just in case there is cause to fall back on them
567
- defaultExpirationExtensionOnErrorInSeconds: 300, // so as to not overwhelm a down endpoint, or to not cache an error for too long, how often should we check back?
568
- timeZoneForInterval: "America/Chicago" // if caching on interval, we need a timezone to account for calculating hours, days, and weeks. List: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
569
- });
570
-
571
- resolve(true);
572
- } catch (error) {
573
- tools.DebugAndLog.error("Could not initialize Config", error);
574
- reject(false);
575
- };
576
-
577
- });
578
-
579
- };
580
- };
581
- ```
582
-
583
- The `connection` code above does the following:
584
-
585
- 1. Defines the host and path (and any parameters and headers to send to a remote endpoint)
586
- 2. Defines the cache settings for that remote endpoint (note, these are only cache settings for that remote endpoint and not the overall cache)
587
-
588
- Additional connections may be added using additional `connections.add()` functions.
589
-
590
- The `cache` code does the following:
591
-
592
- 1. Sets the DynamoDb table and S3 bucket to store cached data
593
- 2. Sets the algorithm to securely encrypt data at rest in DynamoDb and S3
594
- 3. Sets the hash algorithm used to create a unique id for each unique request
595
- 4. How big of an object do we save in DynamoDb before storing it in S3? (20K objects are ideal, anything bigger is in S3)
596
- 5. How long to wait before purging expired entries (they aren't purged right away but kept in case of errors)
597
- 6. If there is an error getting fresh data, how long do we extend any existing cache? (so we can back off while endpoint is in error)
598
- 7. Set the time zone for intervals. For example, we can expire on the hour (8am, 12pm, 8pm, etc) but if we expire at the end of the day, when is the "end of the day"? Midnight where? If empty it will be UTC.
599
-
600
- Each of these are described in their own sections below.
601
-
602
- Note that it is probably best to not hard code values but instead bring them in as environment variables from your Lambda function.
603
-
604
- Next, we need to call the initialization in our application, and before the handler can be executed, make sure the promise has resolved.
605
-
606
- ```js
607
- // note that the Config object is defined in the code above
608
-
609
- /* initialize the Config */
610
- Config.init(); // we need to await completion in the async call function
611
-
612
- /**
613
- * Lambda function handler
614
- */
615
- exports.handler = async (event, context, callback) => {
616
-
617
- /* wait for CONFIG to be settled as we need it before continuing. */
618
- await Config.promise();
619
-
620
- /* Process the request and wait for result */
621
- const response = await someFunction(event, context); // some code or function that generates a response
622
-
623
- /* Send the result back to API Gateway */
624
- callback(null, response);
625
-
626
- }
627
- ```
628
-
629
- Note that you will replace `someFunction()` with your own function that will call and process the data from cache as in the example below.
630
-
631
- Once the `Config` object is initialized, the following code can be used to access data through the cache.
632
-
633
- ```js
634
- /*
635
- Note that cache object was already set by the require statement
636
- assuming:
637
- const { tools, cache, endpoint } = require('@63klabs/cache-data');
638
- */
639
-
640
- let connection = Config.getConnection("demo"); // corresponds with the name we gave it during connections.add()
641
- let conn = connection.toObject(); // we'll "extract" the connection data. .toObject() will create a clone of the data so we can modify if need be
642
-
643
- let cacheProfile = connection.getCacheProfile("games"); // corresponds with the cache profile we gave within demo for connections.add()
644
-
645
- const cacheObj = await cache.CacheableDataAccess.getData(
646
- cacheProfile, // this is your cache profile for an endpoint, included from connection object
647
- endpoint.getDataDirectFromURI, // this is the function you want to invoke to get fresh data if the cache is stale. (conn and null will be passed to it)
648
- conn, // connection information which will be passed to endpoint.getDataDirectFromURI() to get fresh data. Also used to identify the object in cache
649
- null // this parameter can be used to pass additional data to endpoint.getDataDirectFromURI (or any other DAO)
650
- );
651
-
652
- let games = cacheObj.getBody(true); // return the data as an object (true) instead of a string (false). You could use false if you want to keep the data as a string (as in xml or html or text)
653
- ```
654
-
655
- In order to do its job it needs to:
656
-
657
- 1. Know how to access the data. We use a Connection object from Config to do this. You can think of a Connection object as all the pieces of an HTTP request. It identifies the protocol, domain, path, query string, headers, etc. (However, it doesn't have to be an HTTP request.)
658
- 2. Know the function to use to access fresh data from the remote endpoint. Using the Connection object, your can either use a built in HTTP request, or define your own method for processing an http request or other data source.
659
- 3. Know the cache policy for the data. We use a Cache object to do this. It is an object that has information on expiration, headers to save with the data, where cache data is stored, stored data encryption protocol,
660
-
661
- ### cache.CacheableDataAccess.getData() without Connection
662
-
663
- Note that you can use `cache.CacheableDataAccess.getData()` without a Connection object. You'll notice that we "extract" the connection data from `connection` using `.toObject()`. We do this not just because it creates an object that isn't a reference (thus allowing us to ad hoc modify things like path or parameters without changing the original) but also because any object with any structure may be passed (as long as your passed function is expecting it).
664
-
665
- The `cacheProfile` variable is also just an object, but must adhere to the structure outlined in the cache declaration previously shown.
666
-
667
- You can create the cache configuration and connection on the fly without the Connection object:
668
-
669
- ```js
670
- const cacheProfile ={
671
- overrideOriginHeaderExpiration: true,
672
- defaultExpirationExtensionOnErrorInSeconds: 3600,
673
- defaultExpirationInSeconds: (10 * 60), // 10 minutes
674
- expiresIsOnInterval: true,
675
- headersToRetain: ['x-data-id', 'x-data-sha1'],
676
- hostId: "example",
677
- pathId: "person",
678
- encrypt: true
679
- };
31
+ 1. Generate Secret Key to Encrypt Cache:
32
+ - Use the [key generation script](./docs/00-example-implementation/generate-put-ssm.py) during [the build](./docs/00-example-implementation/example-buildspec.yml) to establish a key to encrypt your data.
33
+ 2. Lambda CloudFormation Template:
34
+ - See [Lambda template example](./docs/00-example-implementation/example-template-lambda-function.yml)
35
+ - Node: AWS Lambda supported version of Node
36
+ - Memory: Allocate at least 256MB (512-1024MB recommended)
37
+ - Environment Variables: Add the cache-data environment variables to your Lambda function.
38
+ - Execution Role: Include access to S3 and DynamoDb in your Lambda's execution role.
39
+ 3. S3 and DynamoDb CloudFormation Template to store your cache:
40
+ - See [S3 and DynamoDb Cache Store template example](./docs/00-example-implementation/example-template-s3-and-dynamodb-cache-store.yml)
41
+ - Include in your application infrastructure template or as separate infrastructure.
42
+ 4. Install the @63klabs/cache-data package:
43
+ - `npm install @63klabs/cache-data`
44
+ 5. Add code to your Lambda function to utilize caching and other cache-data utilities:
45
+ - See [example code for index and handler](./docs/00-example-implementation/example-handler.js)
46
+ - See [example code for config initialization](./docs/00-example-implementation/example-config.js)
47
+
48
+ It is recommended that you use the quick-start method when implementing for the first time. It comes with default values and requires less CloudFormation yaml and Node code.
49
+
50
+ - [Quick Start Implementation](./docs/00-quick-start-implementation/README.md)
51
+ - [Advanced Implementation for Providing a Web Service](./docs/01-advanced-implementation-for-web-service/README.md)
52
+ - [Additional Documentation](./docs/README.md)
680
53
 
681
- const conn = {
682
- host: "api.example.com",
683
- path: "/person",
684
- parameters: {id: id, event: event },
685
- headers: {}
686
- };
687
-
688
- const cacheObj = await cache.CacheableDataAccess.getData(
689
- cacheProfile,
690
- myCustomDAO_getData,
691
- conn,
692
- null
693
- );
694
- ```
695
-
696
- ### Connections using CachedSSMParameter or CachedSecret
697
-
698
- Creating a connection is similar to above, we can add an authorization property to the conection:
699
-
700
- ```js
701
- authentication: {
702
- parameters: {
703
- apikey: new tools.CachedSSMParameter('/apps/my_cool_app/demoAPIkey', {refreshAfter: 300}),
704
- }
705
- }
706
- ```
707
-
708
- Learn more about Connection Authentication below.
709
-
710
- And when calling Cache.init(), pass a CachedSSMParameter (or CachedSecret) to the secureDataKey property:
711
-
712
- ```js
713
- secureDataKey: new tools.CachedSSMParameter('/apps/my_cool_app/CacheData_SecureDataKey', {refreshAfter: 1600}),
714
- ```
715
-
716
- ```js
717
- // for games demo from api.chadkluck.net
718
- connections.add( {
719
- name: "demo",
720
- host: "api.chadkluck.net",
721
- path: "/games",
722
- parameters: {},
723
- headers: {
724
- referer: "https://chadkluck.net"
725
- },
726
- authentication: {
727
- parameters: {
728
- apikey: new tools.CachedSSMParameter('/apps/my_cool_app/demoAPIkey', {refreshAfter: 300}), // ADDED
729
- }
730
- },
731
- cache: myCacheProfilesArray
732
- } );
733
-
734
- tools._ConfigSuperClass._connections = connections;
735
-
736
- // Cache settings
737
- cache.Cache.init({
738
- dynamoDbTable: "yourDynamoDbTable",
739
- s3Bucket: "yourS3Bucket",
740
- secureDataAlgorithm: "aes-256-ofb",
741
- secureDataKey: new tools.CachedSSMParameter('/apps/my_cool_app/CacheData_SecureDataKey', {refreshAfter: 1600}), // CHANGED FROM params.app
742
- idHashAlgorithm: "RSA-SHA3-512",
743
- DynamoDbMaxCacheSize_kb: 20,
744
- purgeExpiredCacheEntriesAfterXHours: 24,
745
- defaultExpirationExtensionOnErrorInSeconds: 300,
746
- timeZoneForInterval: "America/Chicago"
747
- });
748
-
749
- ```
750
-
751
- ### Connections Authentication
752
-
753
- You can store your authentication methods separate from the headers, parameters, and body properties. You can also use Basic authorization.
754
-
755
- Just add an `authentication` property to your connection.
756
-
757
- ```js
758
- // for games demo from api.chadkluck.net
759
- connections.add( {
760
- name: "demo",
761
- host: "api.chadkluck.net",
762
- path: "/games",
763
- headers: {
764
- referer: "https://chadkluck.net"
765
- },
766
- authentication: {
767
- parameters: {
768
- apikey: new tools.CachedSSMParameter('/apps/my_cool_app/demoAPIkey', {refreshAfter: 1600}), // ADDED
769
- }
770
- },
771
- cache: myCacheProfilesArray
772
- } );
773
-
774
- connections.add( {
775
- name: "demoauthbasic",
776
- host: "api.chadkluck.net",
777
- path: "/games",
778
- headers: {
779
- referer: "https://chadkluck.net"
780
- },
781
- authentication: {
782
- basic: {
783
- username: new tools.CachedSSMParameter('/apps/my_cool_app/demoUsername', {refreshAfter: 300}),
784
- password: new tools.CachedSSMParameter('/apps/my_cool_app/demoPassword', {refreshAfter: 300}),
785
- }
786
- },
787
- cache: myCacheProfilesArray
788
- } );
789
-
790
- connections.add( {
791
- name: "demoauthheaders",
792
- host: "api.chadkluck.net",
793
- path: "/games",
794
- headers: {
795
- referer: "https://chadkluck.net"
796
- },
797
- authentication: {
798
- headers: {
799
- 'x-api-key': new tools.CachedSSMParameter('/apps/my_cool_app/apiKey', {refreshAfter: 300})
800
- }
801
- },
802
- cache: myCacheProfilesArray
803
- } );
804
-
805
- connections.add( {
806
- name: "demoauthbody",
807
- host: "api.chadkluck.net",
808
- path: "/games",
809
- headers: {
810
- referer: "https://chadkluck.net"
811
- },
812
- authentication: {
813
- body: {
814
- 'x-api-key': new tools.CachedSSMParameter('/apps/my_cool_app/apiKey', {refreshAfter: 300}),
815
- 'account': new tools.CachedSSMParameter('/apps/my_cool_app/accountId', {refreshAfter: 3600})
816
- }
817
- },
818
- cache: myCacheProfilesArray
819
- } );
820
- ```
821
-
822
- ### Connections Options
823
-
824
- Specify a `timeout` in the connection to pass to the http_get command. Default is `8000`.
825
-
826
- Specify how duplicate parameters in a query string should be handled. This allows you to craft your query string to match what your endpoint expects when it parses the query string.
827
-
828
- ```javascript
829
- connections.add({
830
- method: "POST",
831
- host: "api.chadkluck.net",
832
- path: "/echo/",
833
- headers: headers,
834
- uri: "",
835
- protocol: "https",
836
- body: null,
837
- parameters: {
838
- greeting: "Hello",
839
- planets: ["Earth", "Mars"]
840
- },
841
- options: {
842
- timeout: 8000,
843
- separateDuplicateParameters: false, // default is false
844
- separateDuplicateParametersAppendToKey: "", // "" "[]", or "0++", "1++"
845
- combinedDuplicateParameterDelimiter: ','
846
- }
847
- })
848
- ```
849
-
850
- By default the query string used for the request will be:
851
-
852
- ```text
853
- ?greeting=Hello&planets=Earth,Mars
854
- ```
855
-
856
- However, by changing `separateDuplicateParameters` to `true` and `separateDuplicateParametersAppendToKey` to `[]`:
857
-
858
- ```text
859
- ?greeting=Hello&planets[]=Earth&planets[]=Mars
860
- ```
861
-
862
- You can also append an index to the end of the parameter:
863
-
864
- ```javascript
865
- options = {
866
- separateDuplicateParameters: true,
867
- separateDuplicateParametersAppendToKey: "0++", // "" "[]", or "0++", "1++"
868
- }
869
- // ?greeting=Hello&planets0=Earth&planets1=Mars
870
- ```
871
-
872
- Similarly, you can start at index 1 instead of 0:
873
-
874
- ```javascript
875
- options = {
876
- separateDuplicateParameters: true,
877
- separateDuplicateParametersAppendToKey: "1++", // "" "[]", or "0++", "1++"
878
- }
879
- // ?greeting=Hello&planets1=Earth&planets2=Mars
880
- ```
881
-
882
- ### tools.Timer
883
-
884
- In its simplist form we can do the following:
885
-
886
- ```js
887
- /*
888
- Assuming:
889
- const { tools, cache, endpoint } = require('@63klabs/cache-data');
890
- */
891
-
892
- const timerTaskGetGames = new tools.Timer("Getting games", true); // We give it a name for logging, and we set to true so the timer starts right away
893
-
894
- /* A block of code we want to execute and get timing for */
895
- // do something
896
- // do something
897
-
898
- timerTaskGetGames.stop(); // if debug level is >= 3 (DebugAndLog.DIAG) it will log the elapsed time in ms
899
- ```
900
-
901
- The above code will create a timer which we can access by the variable name `timerTaskGetGames`. Since we set the second parameter to `true` it will start the timer upon creation.
902
-
903
- Then a block of code will execute.
904
-
905
- Then we stop the timer using `.stop()` and if the logging level is 3 or greater it will send a log entry with the elapsed time to the console.
906
-
907
- You are able to get the current time elapsed in milliseconds from a running Timer by calling `const ms = timerVarName.elapsed()`
908
-
909
- ### tools.DebugAndLog
910
-
911
- ```js
912
- /*
913
- Assuming:
914
- const { tools, cache, endpoint } = require('@63klabs/cache-data');
915
- */
916
-
917
- /* increase the log level - comment out when not needed */
918
- tools.DebugAndLog.setLogLevel(5, "2022-02-28T04:59:59Z"); // we can increase the debug level with an expiration
919
-
920
- tools.DebugAndLog.debug("Hello World");
921
- tools.DebugAndLog.msg("The sky is set to be blue today");
922
- tools.DebugAndLog.diag("Temperature log:", log);
923
-
924
- try {
925
- // some code
926
- } catch (error) {
927
- tools.DebugAndLog.error("We have an error in try/catch 1", error);
928
- }
929
-
930
- try {
931
- // some code
932
- } catch (error) {
933
- tools.DebugAndLog.warn("We have an error but will log it as a warning in try/catch 2", error);
934
- }
935
- ```
936
-
937
- Before calling `Config.init()` you can set the log level using `DebugAndLog.setLogLevel()`. If you set the log level after calling `Config.init()` OR after calling any `DebugAndLog` function, you will get an error. That is because a default log level has already been set and we will not allow the changing of the log level after a script has begun.
938
-
939
- There are six (6) logging functions.
940
-
941
- ```js
942
- DebugAndLog.error(msgStr, obj); // logs at ALL logging levels
943
- DebugAndLog.warn(msgStr, obj); // logs at ALL logging levels
944
- DebugAndLog.log(msgStr, tagStr, obj); // logs at ALL logging levels
945
- DebugAndLog.msg(msgStr, obj); // logs at level 1 and above
946
- DebugAndLog.diag(msgStr, obj); // logs at level 3 and above
947
- DebugAndLog.debug(msgStr, obj); // logs at level 5
948
- ```
949
-
950
- In the above the `obj` parameter is optional and is an object you wish to log. Be careful of logging objects that may contain sensitive information.
951
-
952
- Choose the method based on how verbose you want your logging to be at various script levels.
953
-
954
- Note that `DebugAndLog.log(msgStr, tagStr)` allows you to add a tag. If a tag is not provided `LOG` will be used and your log entry will look like `[LOG] your message`.
955
-
956
- If you provide `TEMP` as a tag ('temperature' for example) then the log entry will look something like this: `[TEMP] your message`.
957
-
958
- ## Advanced
959
-
960
- The examples above should get you started.
961
-
962
- However, there are advanced uses for the Cache object such as caching processed data not from an endpoint and creating your own Data Access Object (DAO) classes.
963
-
964
- ### Caching data not from a remote endpoint
965
-
966
- Cache does not have to be from a remote endpoint.
967
-
968
- Suppose you gather data from six endpoints and process the data in a resource and time intensive process and would like to cache the result for 6 or 24 hours. You can use the Cache object to store any data from any source, either externally or internally.
969
-
970
- The function parameter passed to the Cache object is the method used to obtain data. Remember the `endpoint.getDataDirectFromURI` from the code sample above? That is just a function to return a bare bones response from an api endpoint. (You can actually extend the `endpoint.Endpoint` class and create your own DAOs that can pre and post process data before returning to your application's cache.)
971
-
972
- Instead of passing in the function `endpoint.getDataDirectFromURI` you can create any function that will grab or process data and return an object.
973
-
974
- Remember, when passing functions for another function to execute, do not include the `()` at the end.
975
-
976
- ### Creating your own Data Access Object (DAO)
977
-
978
- You can either extend `endpoint.Endpoint` or create your own.
979
-
980
- ### Sanitize and Obfuscate functions
981
-
982
- These functions attempt to scrub items labled as 'secret', 'key', 'token' and 'Authorization' from objects for logging purposes.
983
-
984
- Sanitization is also performed on objects passed to the DebugAndLog logging functions.
985
-
986
- #### Sanitize
987
-
988
- You can pass an object to sanitize for logging purposes.
989
-
990
- NOTE: This is a tool that attempts to sanitize and may miss sensitive information. Inspect the [regular expression used for performing search](https://regex101.com/r/IJp35p/3) for more information. Care should be taken when logging objects for purposes of debugging.
991
-
992
- What it attempts to do:
993
-
994
- - Finds object keys with 'secret', 'key', and 'token' in the name and obfuscates their values.
995
- - It checks string values for key:value and key=value pairs and obfuscates the value side if the key contains the words 'secret', 'key', or 'token'. For example, parameters in a query string `https://www.example.com?client=435&key=1234EXAMPLE783271234567` would produce `https://www.example.com?client=435&key=******4567`
996
- - It checks for 'Authentication' object keys and sanitizes the value.
997
- - It checks for multi-value (arrays) of object keys named with secret, key, or token such as `"Client-Secrets":[123456789,1234567890,90987654321]`
998
-
999
- ```JavaScript
1000
- // Note: These fake secrets are hard-coded for demo/test purposes only. NEVER hard-code secrets!
1001
- const obj = {
1002
- secret: "98765-EXAMPLE-1234567890efcd",
1003
- apiKey: "123456-EXAMPLE-123456789bcea",
1004
- kbToken: "ABCD-EXAMPLE-12345678901234567890",
1005
- queryString: "?site=456&secret=12345EXAMPLE123456&b=1",
1006
- headers: {
1007
- Authorization: "Basic someBase64EXAMPLE1234567"
1008
- }
1009
- };
1010
-
1011
- console.log("My Sanitized Object", tools.sanitize(obj));
1012
- /* output: My Sanitized Object {
1013
- secret: '******efcd',
1014
- apiKey: '******bcea',
1015
- kbToken: '******7890',
1016
- queryString: '?site=456&secret=******3456&b=1',
1017
- headers: { Authorization: 'Basic ******4567' }
1018
- }
1019
- */
1020
- ```
1021
-
1022
- > It is best to avoid logging ANY data that contains sensitive information. While this function provides an extra layer of protection, it should be used sparingly for debugging purposes (not on-going logging) in non-production environments.
1023
-
1024
- #### Obfuscate
1025
-
1026
- You can pass a string to obfuscate.
1027
-
1028
- For example, `12345EXAMPLE7890` will return `******7890`.
1029
-
1030
- By default, asterisks are used to pad the left-hand side, and only 4 characters are kept on the right. The length of the string returned is not dependent on the length of the string passed in which in turn obfuscates the original length of the string. However, the right side will not reveal more than 25% of the string (it actually rounds up 1 character so a 2 character string would still reveal the final character).
1031
-
1032
- Default options can be changed by passing an options object.
1033
-
1034
- ```JavaScript
1035
- const str = "EXAMPLE1234567890123456789";
1036
-
1037
- console.log( tools.obfuscate(str) );
1038
- // output: ******6789
1039
-
1040
- const opt = { keep: 6, char: 'X', len: 16 };
1041
- console.log( tools.obfuscate(str, opt) );
1042
- // output: XXXXXXXXXX456789
1043
- ```
1044
-
1045
- ### AWS-SDK
1046
-
1047
- The @63klabs/cache-data package will automatically detect and use the correct AWS SDK based on the version of Node.
1048
-
1049
- Node 16 environments will use AWS-SDK version 2.
1050
-
1051
- Node 18+ environments will use AWS-SDK version 3.
1052
-
1053
- Note that `package.json` for @63klabs/cache-data only installs the AWS-SDK on dev environments. This is because AWS Lambda already includes the AWS-SDK without requiring installs. This makes your application lighter and ensures you are always running the most recent SDK release. Given this, that means that AWS SDK v3 is not available in Lambda functions using Node 16, and v2 is not available in Lambda Node >=18 environments.
1054
-
1055
- Because DynamoDb, S3, and SSM Parameter store are used by cache-data, only those SDKs are included. A client is provided for each along with limited number of commands. To make gets and puts easier a get and put command is mapped for DynamoDb and S3. (Uses appropriate commands underneath for V2 and V3 so your code wouldn't need to change.)
1056
-
1057
- #### `tools.AWS` Object
1058
-
1059
- When `tools` is imported, you can use the `tools.AWS` object to perform common read/write operations on S3, DynamoDb, and SSM Parameter Store.
1060
-
1061
- ```javascript
1062
- const { tools } = require('@63klabs/cache-data');
1063
-
1064
- console.log(`NODE VERSION ${tools.AWS.NODE_VER} USING AWS SDK ${tools.AWS.SDK_VER}`);
1065
- console.log(`REGION: ${tools.AWS.REGION}`); // set from Lambda environment variable AWS_REGION
1066
-
1067
- var getParams = {
1068
- Bucket: 'mybucket', // bucket name,
1069
- Key: 'hello.txt' // object to get
1070
- }
1071
-
1072
- const result = await tools.AWS.s3.get(getParams);
1073
-
1074
- let objectData = await s3Body.transformToString(); // V3: Object bodies in V3 are readable streams, so we convert to string
1075
- // let objectData = data.Body.toString('utf-8'); // V2: Object bodies are Buffers, so we convert to string
1076
- console.log(`hello.txt Body: ${objectData}`);
1077
- // outputs "hello.txt Body: Hello, World!"
1078
-
1079
- ```
1080
-
1081
- The `tools.AWS` object provides the following:
1082
-
1083
- ```js
1084
- {
1085
- NODE_VER: '20.6.0',
1086
- NODE_VER_MAJOR: 20,
1087
- NODE_VER_MINOR: 6,
1088
- NODE_VER_PATCH: 0,
1089
- NODE_VER_MAJOR_MINOR: '20.6',
1090
- NODE_VER_ARRAY: [ 20, 6, 0 ],
1091
- REGION: "us-east-1", // Set from Node environment process.env.AWS_REGION
1092
- SDK_VER: "V3",
1093
- SDK_V2: false, // if (tools.AWS.SDK_V2) { console.log('AWS SDK Version 2!'); }
1094
- SDK_V3: true, // if (tools.AWS.SDK_V3) { console.log('AWS SDK Version 3!'); }
1095
- INFO: { /* an object containing all of the properties listed above */ }
1096
- dynamo: {
1097
- client: DynamoDBDocumentClient,
1098
- put: (params) => client.send(new PutCommand(params)), // const result = await tools.AWS.dynamo.put(params);
1099
- get: (params) => client.send(new GetCommand(params)), // const result = await tools.AWS.dynamo.get(params);
1100
- scan: (params) => client.send(new ScanCommand(params)), // const result = await tools.AWS.dynamo.scan(params);
1101
- delete: (params) => client.send(new DeleteCommand(params)), // const result = await tools.AWS.dynamo.delete(params);
1102
- update: (params) => client.send(new UpdateCommand(params)), // const result = await tools.AWS.dynamo.update(params);
1103
- sdk: {
1104
- DynamoDBClient,
1105
- DynamoDBDocumentClient,
1106
- GetCommand,
1107
- PutCommand
1108
- }
1109
- },
1110
- s3: {
1111
- client: S3,
1112
- put: (params) => client.send(new PutObjectCommand(params)), // const result = await tools.AWS.s3.put(params)
1113
- get: (params) => client.send(new GetObjectCommand(params)), // const result = await tools.AWS.s3.get(params)
1114
- sdk: {
1115
- S3,
1116
- GetObjectCommand,
1117
- PutObjectCommand
1118
- }
1119
-
1120
- },
1121
- ssm: {
1122
- client: SSMClient,
1123
- getByName: (params) => client.send(new GetParametersCommand(query)), // const params = await tools.AWS.ssm.getByName(query)
1124
- getByPath: (params) => client.send(new GetParametersByPathCommand(query)), // const params = await tools.AWS.ssm.getByPath(query)
1125
- sdk: {
1126
- SSMClient,
1127
- GetParametersByPathCommand,
1128
- GetParametersCommand
1129
- }
1130
- }
1131
- }
1132
- ```
1133
-
1134
- Because Node 16 and the AWS SDK v2 are being deprecated, this documentation will mainly cover AWS SDK v3. However, `{DynamoDb, S3, SSM}` are still available when your environment is using Node 16 and AWS SDK v2 by importing `tools` from cache-data and accessing the `AWS` class. (See Using AWS SDK V2 through tools.AWS (Deprecated) below.)
1135
-
1136
- ##### Using AWS SDK V3 through tools.AWS
1137
-
1138
- To use the AWS SDK you normally have to import the proper SDKs and libraries, create a client, and then send the commands. The way this is accomplished in version 2 and version 3 of the AWS SDK is slightly different. How to use the AWS SDK is beyond the scope of this package. However, since the package uses reads and writes to S3 objects, DynamoDb tables, and SSM Parameter store, it readily makes these commands available through the `AWS` object from `tools`.
1139
-
1140
- Also, as a shortcut as you move from Node 16 and Node 18 (and above), the methods exposed will not differ as it automatically uses the correct methods for the loaded SDK.
1141
-
1142
- To use the methds you only need to pass the parameter or query object as you normally would.
1143
-
1144
- ```javascript
1145
- // Given the two parameter/query objects:
1146
-
1147
- let paramsForPut = {
1148
- TableName: 'myTable',
1149
- Item: {
1150
- 'hash_id': '8e91cef4a27',
1151
- 'episode_name': "There's No Disgrace Like Home",
1152
- 'air_date': "1990-01-28",
1153
- 'production_code': '7G04'
1154
- }
1155
- }
1156
-
1157
- let paramsForGet = {
1158
- TableName: 'myTable',
1159
- Key: {'hash_id': '8e91cef4a27'}
1160
- };
1161
- ```
1162
-
1163
- ```javascript
1164
- // Using AWS SDK V2
1165
- const { DynamoDb } = require('aws-sdk');
1166
-
1167
- const dbDocClient = new DynamoDB.DocumentClient( {region: 'us-east-1'} );
1168
-
1169
- const dbPutResult = await dbDocClient.put(paramsForNewRecord).promise();
1170
- const dbGetResult = await dbDocClient.get(paramsForGet).promise();
1171
- ```
1172
-
1173
- ```javascript
1174
- // Using AWS SDK V3
1175
- const { DynamoDBClient} = require("@aws-sdk/client-dynamodb");
1176
- const { DynamoDBDocumentClient, GetCommand, PutCommand} = require("@aws-sdk/lib-dynamodb");
1177
-
1178
- const dbClient = new DynamoDBClient({ region: AWS.REGION });
1179
- const dbDocClient = DynamoDBDocumentClient.from(dbClient);
1180
-
1181
- const dbPutResult = await dbDocClient.send(PutCommand(paramsForNewRecord));
1182
- const dbGetResult = await dbDocClient.send(GetCommand(paramsForGetRecord));
1183
- ```
1184
-
1185
- ```javascript
1186
- // Using tools to handle the SDK version and basic calls for you
1187
- const { tools } = require('@63klabs/cache-data');
1188
-
1189
- const dbPutResult = await tools.AWS.dynamodb.put(paramsForNewRecord);
1190
- const dbGetResult = await tools.AWS.dynamodb.get(paramsForGetRecrod);
1191
- ```
1192
-
1193
- Refer to the section about the tools.AWS above for the variables, methods, and SDK objects available.
1194
-
1195
- For more on creating parameter/query objects for S3, DynamoDb, and SSM Parameter Store:
1196
-
1197
- - [Amazon S3 examples using SDK for JavaScript (v3)](https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/javascript_s3_code_examples.html)
1198
- - [Using the DynamoDB Document Client](https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/dynamodb-example-dynamodb-utilities.html)
1199
- - []()
1200
-
1201
- ##### Import Additional Commands
1202
-
1203
- When using AWS SDK version 3, you can import additional commands and use them with the client provided by `tools.AWS`.
1204
-
1205
- ```javascript
1206
- const { tools } = require('@63klabs/cache-data');
1207
- const { DeleteObjectCommand } = require('@aws-sdk/client-s3'); // AWS SDK v3
1208
-
1209
- const command = new DeleteObjectCommand({
1210
- Bucket: "myBucket",
1211
- Key: "good-bye.txt"
1212
- });
1213
-
1214
- const response = await tools.AWS.s3.client.send(command);
1215
- ```
1216
-
1217
- #### Using AWS SDK V2 through tools.AWS (Deprecated)
54
+ ## Help
1218
55
 
1219
- Because Node 16 and the AWS SDK v2 is being deprecated, this documentation will mainly cover AWS SDK v3. However, `{DynamoDb, S3, SSM}` are still available by importing `tools` from cache-data and accessing the `AWS` class:
56
+ Make sure you have your S3 bucket, DynamoDb table, and SSM Parameter store set up. Also make sure that you have IAM policies to allow your Lambda function access to these, and CodeBuild to read and write to SSM Parameter store.
1220
57
 
1221
- ```js
1222
- // NodeJS 16 using AWS SDK v2
1223
- const {tools} = require("@63klabs/cache-data");
58
+ Review the [Documentation](./docs/README.md) which includes implementation guides, example code and templates, cache data features, and lambda optimization best practices.
1224
59
 
1225
- // using the provided S3 client
1226
- const s3result1 = await tools.AWS.s3.client.putObject(params).promise();
60
+ A full implementation example and tutorial is provided as one of the Atlantis Application Starters available through the [Atlantis Tutorials repository](https://github.com/63klabs/atlantis-tutorials). (Atlantis is a collection of templates and deployment scripts to assist in starting and automating serverless deployments using AWS SAM and CloudFormation.)
1227
61
 
1228
- // using your own client
1229
- const s3client = new tools.AWS.s3.sdk.S3();
1230
- const s3result2 = await s3Client.putObject(params).promise();
62
+ ## Security
1231
63
 
1232
- // similarly with DynamoDb
1233
- const dbResult1 = await tools.AWS.dynamo.client.put(params).promise(); // tools.AWS.dynamo.client uses DynamoDB.DocumentClient
64
+ See [SECURITY](./SECURITY.md) for information on reporting concerns.
1234
65
 
1235
- // using your own DynamoDb Document client
1236
- const dbClient = new tools.AWS.dynamo.sdk.DynamoDB.DocumentClient( {region: 'us-east-1'} );
1237
- const dbResult2 = await dbClient.put(params).promise(),
1238
- ```
66
+ ## Change Log
1239
67
 
1240
- ### AWS X-Ray
68
+ See [Change Log](CHANGELOG.md) for version history and changes.
1241
69
 
1242
- X-Ray can be enabled by making sure you have the Lambda layer installed, appropriate permissions granted to your Lambda execution policy, and setting AWS X-Ray to true in the CloudFormation Template parameters or your Lambda Environment variable.
70
+ ## Issues, Features, and Enhancements
1243
71
 
1244
- The templates and code under the installation section already have these implemented.
72
+ Visit the [Issues section of the @63Klabs Cache-Data GitHub repository](https://github.com/63klabs/cache-data) for information on reported issues, upcoming fixes and enhancements, and to submit requests.
1245
73
 
1246
- ## Help
74
+ ## License
1247
75
 
1248
- Make sure you have your S3 bucket, DynamoDb table, and SSM Parameter store set up. Also make sure that you have IAM policies to allow your Lambda function access to these.
76
+ This project is licensed under the MIT License - see the LICENSE.txt file for details
1249
77
 
1250
78
  ## Author
1251
79
 
1252
- ### Chad Kluck
80
+ ### Chad Kluck
1253
81
 
82
+ - Software, DevOps, and Developer Experience Engineer
83
+ - [AWS Certified Developer - Associate](https://www.credly.com/users/chad-kluck/badges)
1254
84
  - [Website](https://chadkluck.me/)
1255
85
  - [GitHub](https://github.com/chadkluck)
1256
- - [Mastodon: @63klabs@universeodon.com](https://universeodon.com/@63klabs)
1257
- - [X: @ChadKluck](https://x.com/chadkluck)
1258
-
1259
- ## Version History
1260
-
1261
- Refer to the [Change Log](CHANGELOG.md)
1262
-
1263
- ## License
1264
-
1265
- This project is licensed under the MIT License - see the LICENSE.txt file for details
86
+ - [GitHub (63Klabs)](https://github.com/63klabs)
87
+ - [Mastodon: @chadkluck@universeodon.com](https://universeodon.com/@chadkluck)
88
+ - [LinkedIn](https://www.linkedin.com/in/chadkluck/)