nui-lambda-shared-utils 1.1.4__tar.gz → 1.2.0__tar.gz
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.
- {nui_lambda_shared_utils-1.1.4 → nui_lambda_shared_utils-1.2.0}/CLAUDE.md +2 -0
- {nui_lambda_shared_utils-1.1.4 → nui_lambda_shared_utils-1.2.0}/PKG-INFO +28 -1
- {nui_lambda_shared_utils-1.1.4 → nui_lambda_shared_utils-1.2.0}/README.md +20 -0
- {nui_lambda_shared_utils-1.1.4 → nui_lambda_shared_utils-1.2.0}/docs/README.md +4 -1
- {nui_lambda_shared_utils-1.1.4 → nui_lambda_shared_utils-1.2.0}/docs/getting-started/configuration.md +11 -0
- {nui_lambda_shared_utils-1.1.4 → nui_lambda_shared_utils-1.2.0}/docs/getting-started/installation.md +7 -0
- {nui_lambda_shared_utils-1.1.4 → nui_lambda_shared_utils-1.2.0}/docs/getting-started/quickstart.md +36 -1
- nui_lambda_shared_utils-1.2.0/docs/guides/jwt-authentication.md +148 -0
- nui_lambda_shared_utils-1.2.0/docs/guides/log-processing.md +156 -0
- {nui_lambda_shared_utils-1.1.4 → nui_lambda_shared_utils-1.2.0}/nui_lambda_shared_utils/__init__.py +35 -0
- nui_lambda_shared_utils-1.2.0/nui_lambda_shared_utils/jwt_auth.py +218 -0
- nui_lambda_shared_utils-1.2.0/nui_lambda_shared_utils/log_processors.py +172 -0
- {nui_lambda_shared_utils-1.1.4 → nui_lambda_shared_utils-1.2.0}/nui_lambda_shared_utils/powertools_helpers.py +5 -1
- {nui_lambda_shared_utils-1.1.4 → nui_lambda_shared_utils-1.2.0}/nui_lambda_shared_utils/slack_client.py +45 -28
- {nui_lambda_shared_utils-1.1.4 → nui_lambda_shared_utils-1.2.0}/nui_lambda_shared_utils.egg-info/SOURCES.txt +6 -0
- {nui_lambda_shared_utils-1.1.4 → nui_lambda_shared_utils-1.2.0}/pyproject.toml +6 -0
- {nui_lambda_shared_utils-1.1.4 → nui_lambda_shared_utils-1.2.0}/requirements-test.txt +3 -1
- {nui_lambda_shared_utils-1.1.4 → nui_lambda_shared_utils-1.2.0}/setup.py +10 -0
- nui_lambda_shared_utils-1.2.0/tests/test_jwt_auth.py +276 -0
- {nui_lambda_shared_utils-1.1.4 → nui_lambda_shared_utils-1.2.0}/tests/test_lambda_helpers.py +6 -0
- nui_lambda_shared_utils-1.2.0/tests/test_log_processors.py +236 -0
- {nui_lambda_shared_utils-1.1.4 → nui_lambda_shared_utils-1.2.0}/tests/test_slack_client.py +127 -105
- {nui_lambda_shared_utils-1.1.4 → nui_lambda_shared_utils-1.2.0}/.editorconfig +0 -0
- {nui_lambda_shared_utils-1.1.4 → nui_lambda_shared_utils-1.2.0}/.github/workflows/ci.yml +0 -0
- {nui_lambda_shared_utils-1.1.4 → nui_lambda_shared_utils-1.2.0}/.github/workflows/publish.yml +0 -0
- {nui_lambda_shared_utils-1.1.4 → nui_lambda_shared_utils-1.2.0}/.github/workflows/test.yml +0 -0
- {nui_lambda_shared_utils-1.1.4 → nui_lambda_shared_utils-1.2.0}/.markdownlint-cli2.yaml +0 -0
- {nui_lambda_shared_utils-1.1.4 → nui_lambda_shared_utils-1.2.0}/CONTRIBUTING.md +0 -0
- {nui_lambda_shared_utils-1.1.4 → nui_lambda_shared_utils-1.2.0}/LICENSE +0 -0
- {nui_lambda_shared_utils-1.1.4 → nui_lambda_shared_utils-1.2.0}/MANIFEST.in +0 -0
- {nui_lambda_shared_utils-1.1.4 → nui_lambda_shared_utils-1.2.0}/docs/development/testing.md +0 -0
- {nui_lambda_shared_utils-1.1.4 → nui_lambda_shared_utils-1.2.0}/docs/guides/cli-tools.md +0 -0
- {nui_lambda_shared_utils-1.1.4 → nui_lambda_shared_utils-1.2.0}/docs/guides/elasticsearch-integration.md +0 -0
- {nui_lambda_shared_utils-1.1.4 → nui_lambda_shared_utils-1.2.0}/docs/guides/lambda-utilities.md +0 -0
- {nui_lambda_shared_utils-1.1.4 → nui_lambda_shared_utils-1.2.0}/docs/guides/powertools-integration.md +0 -0
- {nui_lambda_shared_utils-1.1.4 → nui_lambda_shared_utils-1.2.0}/docs/guides/shared-types.md +0 -0
- {nui_lambda_shared_utils-1.1.4 → nui_lambda_shared_utils-1.2.0}/docs/guides/slack-integration.md +0 -0
- {nui_lambda_shared_utils-1.1.4 → nui_lambda_shared_utils-1.2.0}/mypy.ini +0 -0
- {nui_lambda_shared_utils-1.1.4 → nui_lambda_shared_utils-1.2.0}/nui_lambda_shared_utils/base_client.py +0 -0
- {nui_lambda_shared_utils-1.1.4 → nui_lambda_shared_utils-1.2.0}/nui_lambda_shared_utils/cli.py +0 -0
- {nui_lambda_shared_utils-1.1.4 → nui_lambda_shared_utils-1.2.0}/nui_lambda_shared_utils/cloudwatch_metrics.py +0 -0
- {nui_lambda_shared_utils-1.1.4 → nui_lambda_shared_utils-1.2.0}/nui_lambda_shared_utils/config.py +0 -0
- {nui_lambda_shared_utils-1.1.4 → nui_lambda_shared_utils-1.2.0}/nui_lambda_shared_utils/db_client.py +0 -0
- {nui_lambda_shared_utils-1.1.4 → nui_lambda_shared_utils-1.2.0}/nui_lambda_shared_utils/error_handler.py +0 -0
- {nui_lambda_shared_utils-1.1.4 → nui_lambda_shared_utils-1.2.0}/nui_lambda_shared_utils/es_client.py +0 -0
- {nui_lambda_shared_utils-1.1.4 → nui_lambda_shared_utils-1.2.0}/nui_lambda_shared_utils/es_query_builder.py +0 -0
- {nui_lambda_shared_utils-1.1.4 → nui_lambda_shared_utils-1.2.0}/nui_lambda_shared_utils/lambda_helpers.py +0 -0
- {nui_lambda_shared_utils-1.1.4 → nui_lambda_shared_utils-1.2.0}/nui_lambda_shared_utils/secrets_helper.py +0 -0
- {nui_lambda_shared_utils-1.1.4 → nui_lambda_shared_utils-1.2.0}/nui_lambda_shared_utils/slack_formatter.py +0 -0
- {nui_lambda_shared_utils-1.1.4 → nui_lambda_shared_utils-1.2.0}/nui_lambda_shared_utils/slack_setup/__init__.py +0 -0
- {nui_lambda_shared_utils-1.1.4 → nui_lambda_shared_utils-1.2.0}/nui_lambda_shared_utils/slack_setup/channel_creator.py +0 -0
- {nui_lambda_shared_utils-1.1.4 → nui_lambda_shared_utils-1.2.0}/nui_lambda_shared_utils/slack_setup/channel_definitions.py +0 -0
- {nui_lambda_shared_utils-1.1.4 → nui_lambda_shared_utils-1.2.0}/nui_lambda_shared_utils/slack_setup/setup_helpers.py +0 -0
- {nui_lambda_shared_utils-1.1.4 → nui_lambda_shared_utils-1.2.0}/nui_lambda_shared_utils/timezone.py +0 -0
- {nui_lambda_shared_utils-1.1.4 → nui_lambda_shared_utils-1.2.0}/nui_lambda_shared_utils/utils.py +0 -0
- {nui_lambda_shared_utils-1.1.4 → nui_lambda_shared_utils-1.2.0}/pytest.ini +0 -0
- {nui_lambda_shared_utils-1.1.4 → nui_lambda_shared_utils-1.2.0}/setup.cfg +0 -0
- {nui_lambda_shared_utils-1.1.4 → nui_lambda_shared_utils-1.2.0}/tests/__init__.py +0 -0
- {nui_lambda_shared_utils-1.1.4 → nui_lambda_shared_utils-1.2.0}/tests/test_aws_utils.py +0 -0
- {nui_lambda_shared_utils-1.1.4 → nui_lambda_shared_utils-1.2.0}/tests/test_base_client.py +0 -0
- {nui_lambda_shared_utils-1.1.4 → nui_lambda_shared_utils-1.2.0}/tests/test_cloudwatch_metrics.py +0 -0
- {nui_lambda_shared_utils-1.1.4 → nui_lambda_shared_utils-1.2.0}/tests/test_config.py +0 -0
- {nui_lambda_shared_utils-1.1.4 → nui_lambda_shared_utils-1.2.0}/tests/test_db_client.py +0 -0
- {nui_lambda_shared_utils-1.1.4 → nui_lambda_shared_utils-1.2.0}/tests/test_error_handler.py +0 -0
- {nui_lambda_shared_utils-1.1.4 → nui_lambda_shared_utils-1.2.0}/tests/test_es_client.py +0 -0
- {nui_lambda_shared_utils-1.1.4 → nui_lambda_shared_utils-1.2.0}/tests/test_es_query_builder.py +0 -0
- {nui_lambda_shared_utils-1.1.4 → nui_lambda_shared_utils-1.2.0}/tests/test_powertools_helpers.py +0 -0
- {nui_lambda_shared_utils-1.1.4 → nui_lambda_shared_utils-1.2.0}/tests/test_secrets_helper.py +0 -0
- {nui_lambda_shared_utils-1.1.4 → nui_lambda_shared_utils-1.2.0}/tests/test_slack_formatter.py +0 -0
- {nui_lambda_shared_utils-1.1.4 → nui_lambda_shared_utils-1.2.0}/tests/test_timezone.py +0 -0
- {nui_lambda_shared_utils-1.1.4 → nui_lambda_shared_utils-1.2.0}/tests/test_utils.py +0 -0
|
@@ -37,6 +37,7 @@ This is `nui-lambda-shared-utils`, a Python package providing production-ready u
|
|
|
37
37
|
- **Elasticsearch** (`es_client.py`, `es_query_builder.py`) - Query builders and health monitoring
|
|
38
38
|
- **Database** (`db_client.py`) - Connection pooling with retry logic
|
|
39
39
|
- **Metrics** (`cloudwatch_metrics.py`) - Batched CloudWatch publishing with decorators
|
|
40
|
+
- **JWT Authentication** (`jwt_auth.py`) - RS256 token validation for API Gateway Lambdas (uses `rsa` package)
|
|
40
41
|
- **Error Handling** (`error_handler.py`) - Retry patterns with exponential backoff
|
|
41
42
|
- **Timezone Utils** (`timezone.py`) - Timezone conversion and formatting utilities
|
|
42
43
|
|
|
@@ -47,6 +48,7 @@ The package uses optional extras to minimize Lambda bundle size:
|
|
|
47
48
|
- `elasticsearch` - Elasticsearch client and query builders
|
|
48
49
|
- `database` - MySQL/PostgreSQL drivers
|
|
49
50
|
- `slack` - Slack SDK
|
|
51
|
+
- `jwt` - RS256 JWT validation (`rsa` package)
|
|
50
52
|
- `all` - All integrations
|
|
51
53
|
- `dev` - Development and testing tools
|
|
52
54
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: nui-lambda-shared-utils
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.2.0
|
|
4
4
|
Summary: Enterprise-grade utilities for AWS Lambda functions with Slack, Elasticsearch, and monitoring integrations
|
|
5
5
|
Home-page: https://github.com/nuimarkets/nui-lambda-shared-utils
|
|
6
6
|
Author: NUI Markets
|
|
@@ -41,6 +41,8 @@ Requires-Dist: slack-sdk>=3.19.0; extra == "slack"
|
|
|
41
41
|
Provides-Extra: powertools
|
|
42
42
|
Requires-Dist: aws-lambda-powertools<4.0.0,>=3.6.0; extra == "powertools"
|
|
43
43
|
Requires-Dist: coloredlogs>=15.0; extra == "powertools"
|
|
44
|
+
Provides-Extra: jwt
|
|
45
|
+
Requires-Dist: rsa>=4.9; extra == "jwt"
|
|
44
46
|
Provides-Extra: all
|
|
45
47
|
Requires-Dist: elasticsearch<8.0.0,>=7.17.0; extra == "all"
|
|
46
48
|
Requires-Dist: pymysql>=1.0.0; extra == "all"
|
|
@@ -48,6 +50,7 @@ Requires-Dist: psycopg2-binary>=2.9.0; extra == "all"
|
|
|
48
50
|
Requires-Dist: slack-sdk>=3.19.0; extra == "all"
|
|
49
51
|
Requires-Dist: aws-lambda-powertools<4.0.0,>=3.6.0; extra == "all"
|
|
50
52
|
Requires-Dist: coloredlogs>=15.0; extra == "all"
|
|
53
|
+
Requires-Dist: rsa>=4.9; extra == "all"
|
|
51
54
|
Provides-Extra: dev
|
|
52
55
|
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
53
56
|
Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
|
|
@@ -56,8 +59,12 @@ Requires-Dist: moto>=4.0.0; extra == "dev"
|
|
|
56
59
|
Requires-Dist: black>=22.0.0; extra == "dev"
|
|
57
60
|
Requires-Dist: mypy>=0.990; extra == "dev"
|
|
58
61
|
Requires-Dist: boto3-stubs[essential]>=1.20.0; extra == "dev"
|
|
62
|
+
Requires-Dist: types-PyYAML>=6.0.0; extra == "dev"
|
|
63
|
+
Requires-Dist: types-pytz>=2021.3.0; extra == "dev"
|
|
59
64
|
Requires-Dist: twine>=4.0.0; extra == "dev"
|
|
60
65
|
Requires-Dist: build>=0.8.0; extra == "dev"
|
|
66
|
+
Requires-Dist: rsa>=4.9; extra == "dev"
|
|
67
|
+
Requires-Dist: cryptography>=41.0.0; extra == "dev"
|
|
61
68
|
Dynamic: author
|
|
62
69
|
Dynamic: home-page
|
|
63
70
|
Dynamic: license-file
|
|
@@ -99,6 +106,7 @@ Production-ready utilities for AWS Lambda functions with Slack, Elasticsearch, d
|
|
|
99
106
|
- **Elasticsearch Operations** - Query builders, index management, and health monitoring
|
|
100
107
|
- **Database Connections** - Connection pooling, automatic retries, and transaction management
|
|
101
108
|
- **CloudWatch Metrics** - Batched publishing with custom dimensions
|
|
109
|
+
- **JWT Authentication** - RS256 token validation for API Gateway Lambdas (lightweight, no PyJWT needed)
|
|
102
110
|
- **Error Handling** - Intelligent retry patterns with exponential backoff
|
|
103
111
|
- **Timezone Utilities** - Timezone handling and formatting
|
|
104
112
|
- **Configurable Defaults** - Environment-aware configuration system
|
|
@@ -134,6 +142,7 @@ pip install nui-lambda-shared-utils[powertools] # AWS Powertools only
|
|
|
134
142
|
pip install nui-lambda-shared-utils[slack] # Slack only
|
|
135
143
|
pip install nui-lambda-shared-utils[elasticsearch] # Elasticsearch only
|
|
136
144
|
pip install nui-lambda-shared-utils[database] # Database only
|
|
145
|
+
pip install nui-lambda-shared-utils[jwt] # JWT authentication only
|
|
137
146
|
```
|
|
138
147
|
|
|
139
148
|
### Basic Configuration
|
|
@@ -276,6 +285,23 @@ def lambda_handler(event, context):
|
|
|
276
285
|
|
|
277
286
|
**[→ See full metrics guide](docs/getting-started/quickstart.md#cloudwatch-metrics)**
|
|
278
287
|
|
|
288
|
+
### JWT Authentication
|
|
289
|
+
|
|
290
|
+
```python
|
|
291
|
+
from nui_lambda_shared_utils import require_auth, AuthenticationError
|
|
292
|
+
|
|
293
|
+
def lambda_handler(event, context):
|
|
294
|
+
try:
|
|
295
|
+
claims = require_auth(event) # Validates Bearer token from Authorization header
|
|
296
|
+
except AuthenticationError as e:
|
|
297
|
+
return {"statusCode": 401, "body": "Unauthorized"}
|
|
298
|
+
|
|
299
|
+
user_id = claims["sub"]
|
|
300
|
+
return {"statusCode": 200, "body": f"Hello {user_id}"}
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
**[→ See JWT authentication guide](docs/guides/jwt-authentication.md)**
|
|
304
|
+
|
|
279
305
|
### Error Handling
|
|
280
306
|
|
|
281
307
|
```python
|
|
@@ -330,6 +356,7 @@ This package requires AWS Secrets Manager for credential storage and IAM permiss
|
|
|
330
356
|
- Elasticsearch: `{"host": "...", "username": "...", "password": "..."}`
|
|
331
357
|
- Database: `{"host": "...", "port": 3306, "username": "...", "password": "...", "database": "..."}`
|
|
332
358
|
- Slack: `{"bot_token": "xoxb-...", "webhook_url": "..."}`
|
|
359
|
+
- JWT Public Key: `{"TOKEN_PUBLIC_KEY": "-----BEGIN PUBLIC KEY-----\n..."}`
|
|
333
360
|
|
|
334
361
|
**IAM Permissions** - Lambda execution role needs:
|
|
335
362
|
|
|
@@ -34,6 +34,7 @@ Production-ready utilities for AWS Lambda functions with Slack, Elasticsearch, d
|
|
|
34
34
|
- **Elasticsearch Operations** - Query builders, index management, and health monitoring
|
|
35
35
|
- **Database Connections** - Connection pooling, automatic retries, and transaction management
|
|
36
36
|
- **CloudWatch Metrics** - Batched publishing with custom dimensions
|
|
37
|
+
- **JWT Authentication** - RS256 token validation for API Gateway Lambdas (lightweight, no PyJWT needed)
|
|
37
38
|
- **Error Handling** - Intelligent retry patterns with exponential backoff
|
|
38
39
|
- **Timezone Utilities** - Timezone handling and formatting
|
|
39
40
|
- **Configurable Defaults** - Environment-aware configuration system
|
|
@@ -69,6 +70,7 @@ pip install nui-lambda-shared-utils[powertools] # AWS Powertools only
|
|
|
69
70
|
pip install nui-lambda-shared-utils[slack] # Slack only
|
|
70
71
|
pip install nui-lambda-shared-utils[elasticsearch] # Elasticsearch only
|
|
71
72
|
pip install nui-lambda-shared-utils[database] # Database only
|
|
73
|
+
pip install nui-lambda-shared-utils[jwt] # JWT authentication only
|
|
72
74
|
```
|
|
73
75
|
|
|
74
76
|
### Basic Configuration
|
|
@@ -211,6 +213,23 @@ def lambda_handler(event, context):
|
|
|
211
213
|
|
|
212
214
|
**[→ See full metrics guide](docs/getting-started/quickstart.md#cloudwatch-metrics)**
|
|
213
215
|
|
|
216
|
+
### JWT Authentication
|
|
217
|
+
|
|
218
|
+
```python
|
|
219
|
+
from nui_lambda_shared_utils import require_auth, AuthenticationError
|
|
220
|
+
|
|
221
|
+
def lambda_handler(event, context):
|
|
222
|
+
try:
|
|
223
|
+
claims = require_auth(event) # Validates Bearer token from Authorization header
|
|
224
|
+
except AuthenticationError as e:
|
|
225
|
+
return {"statusCode": 401, "body": "Unauthorized"}
|
|
226
|
+
|
|
227
|
+
user_id = claims["sub"]
|
|
228
|
+
return {"statusCode": 200, "body": f"Hello {user_id}"}
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
**[→ See JWT authentication guide](docs/guides/jwt-authentication.md)**
|
|
232
|
+
|
|
214
233
|
### Error Handling
|
|
215
234
|
|
|
216
235
|
```python
|
|
@@ -265,6 +284,7 @@ This package requires AWS Secrets Manager for credential storage and IAM permiss
|
|
|
265
284
|
- Elasticsearch: `{"host": "...", "username": "...", "password": "..."}`
|
|
266
285
|
- Database: `{"host": "...", "port": 3306, "username": "...", "password": "...", "database": "..."}`
|
|
267
286
|
- Slack: `{"bot_token": "xoxb-...", "webhook_url": "..."}`
|
|
287
|
+
- JWT Public Key: `{"TOKEN_PUBLIC_KEY": "-----BEGIN PUBLIC KEY-----\n..."}`
|
|
268
288
|
|
|
269
289
|
**IAM Permissions** - Lambda execution role needs:
|
|
270
290
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Welcome to the comprehensive documentation for `nui-lambda-shared-utils`.
|
|
4
4
|
|
|
5
|
-
**Last Updated**:
|
|
5
|
+
**Last Updated**: 2026-02-13
|
|
6
6
|
|
|
7
7
|
## Quick Navigation
|
|
8
8
|
|
|
@@ -20,6 +20,8 @@ Component-specific guides for major features:
|
|
|
20
20
|
- **[Lambda Context Helpers](guides/lambda-utilities.md)** - Environment info extraction for logging and metrics
|
|
21
21
|
- **[Slack Integration](guides/slack-integration.md)** - Messaging, formatting, and file uploads
|
|
22
22
|
- **[Elasticsearch Integration](guides/elasticsearch-integration.md)** - Search, bulk indexing, health checks
|
|
23
|
+
- **[JWT Authentication](guides/jwt-authentication.md)** - RS256 token validation for API Gateway Lambdas
|
|
24
|
+
- **[Log Processing](guides/log-processing.md)** - Kinesis log extraction and ES index naming
|
|
23
25
|
- Database Connections (planned)
|
|
24
26
|
- Error Handling Patterns (planned)
|
|
25
27
|
- CloudWatch Metrics (planned)
|
|
@@ -122,6 +124,7 @@ When contributing to documentation:
|
|
|
122
124
|
- Lambda context helpers guide (guides/lambda-utilities.md)
|
|
123
125
|
- Slack integration guide (guides/slack-integration.md)
|
|
124
126
|
- Elasticsearch integration guide (guides/elasticsearch-integration.md)
|
|
127
|
+
- JWT authentication guide (guides/jwt-authentication.md)
|
|
125
128
|
- Shared types reference (guides/shared-types.md)
|
|
126
129
|
- CLI tools guide (guides/cli-tools.md)
|
|
127
130
|
- Testing guide (development/testing.md)
|
|
@@ -20,6 +20,7 @@ The package uses a hierarchical configuration system with the following priority
|
|
|
20
20
|
| `ES_CREDENTIALS_SECRET` | AWS secret name for Elasticsearch credentials | `elasticsearch-credentials` |
|
|
21
21
|
| `DB_CREDENTIALS_SECRET` | AWS secret name for database credentials | `database-credentials` |
|
|
22
22
|
| `SLACK_CREDENTIALS_SECRET` | AWS secret name for Slack credentials | `slack-credentials` |
|
|
23
|
+
| `JWT_PUBLIC_KEY_SECRET` | AWS secret name for JWT public key | *(none)* |
|
|
23
24
|
| `AWS_REGION` | AWS region for services | `us-east-1` |
|
|
24
25
|
|
|
25
26
|
### Alternative Variable Names
|
|
@@ -143,6 +144,16 @@ The package expects AWS secrets in specific JSON formats:
|
|
|
143
144
|
|
|
144
145
|
- `webhook_url` - Alternative to bot token for simple messaging
|
|
145
146
|
|
|
147
|
+
#### JWT Public Key
|
|
148
|
+
|
|
149
|
+
```json
|
|
150
|
+
{
|
|
151
|
+
"TOKEN_PUBLIC_KEY": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqh...\n-----END PUBLIC KEY-----"
|
|
152
|
+
}
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
The key must be in PKCS#8 PEM format (`BEGIN PUBLIC KEY`). The field name is configurable via the `key_field` parameter on `get_jwt_public_key()`.
|
|
156
|
+
|
|
146
157
|
### Creating Secrets
|
|
147
158
|
|
|
148
159
|
#### Using AWS CLI
|
{nui_lambda_shared_utils-1.1.4 → nui_lambda_shared_utils-1.2.0}/docs/getting-started/installation.md
RENAMED
|
@@ -31,6 +31,9 @@ pip install nui-lambda-shared-utils[elasticsearch]
|
|
|
31
31
|
# Database integration only
|
|
32
32
|
pip install nui-lambda-shared-utils[database]
|
|
33
33
|
|
|
34
|
+
# JWT authentication only
|
|
35
|
+
pip install nui-lambda-shared-utils[jwt]
|
|
36
|
+
|
|
34
37
|
# All integrations
|
|
35
38
|
pip install nui-lambda-shared-utils[all]
|
|
36
39
|
|
|
@@ -75,6 +78,10 @@ pip install -e .[dev]
|
|
|
75
78
|
|
|
76
79
|
- `slack-sdk>=3.19.0` - Official Slack SDK
|
|
77
80
|
|
|
81
|
+
#### jwt
|
|
82
|
+
|
|
83
|
+
- `rsa>=4.9` - Pure Python RSA implementation (~100KB, no C extensions)
|
|
84
|
+
|
|
78
85
|
#### dev
|
|
79
86
|
|
|
80
87
|
- `pytest>=7.0.0` - Testing framework
|
{nui_lambda_shared_utils-1.1.4 → nui_lambda_shared_utils-1.2.0}/docs/getting-started/quickstart.md
RENAMED
|
@@ -223,7 +223,42 @@ def lambda_handler(event, context):
|
|
|
223
223
|
raise
|
|
224
224
|
```
|
|
225
225
|
|
|
226
|
-
###
|
|
226
|
+
### 7. JWT Authentication for API Gateway
|
|
227
|
+
|
|
228
|
+
```python
|
|
229
|
+
import nui_lambda_shared_utils as nui
|
|
230
|
+
|
|
231
|
+
def lambda_handler(event, context):
|
|
232
|
+
"""API Gateway Lambda with JWT auth."""
|
|
233
|
+
# Validate Bearer token from Authorization header
|
|
234
|
+
# Fetches public key from Secrets Manager (cached automatically)
|
|
235
|
+
try:
|
|
236
|
+
claims = nui.require_auth(event)
|
|
237
|
+
except nui.AuthenticationError:
|
|
238
|
+
return {"statusCode": 401, "body": "Unauthorized"}
|
|
239
|
+
|
|
240
|
+
# Access token claims
|
|
241
|
+
user_id = claims["sub"]
|
|
242
|
+
role = claims.get("role", "user")
|
|
243
|
+
|
|
244
|
+
return {"statusCode": 200, "body": f"Hello {user_id} ({role})"}
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
**Installation:**
|
|
248
|
+
|
|
249
|
+
```bash
|
|
250
|
+
pip install nui-lambda-shared-utils[jwt]
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
**Requirements:**
|
|
254
|
+
|
|
255
|
+
- `JWT_PUBLIC_KEY_SECRET` env var pointing to Secrets Manager secret
|
|
256
|
+
- Secret contains `{"TOKEN_PUBLIC_KEY": "-----BEGIN PUBLIC KEY-----\n..."}` (PKCS#8 PEM)
|
|
257
|
+
- IAM permission: `secretsmanager:GetSecretValue`
|
|
258
|
+
|
|
259
|
+
**See:** [JWT Authentication Guide](../guides/jwt-authentication.md) for key management and advanced usage.
|
|
260
|
+
|
|
261
|
+
### 8. AWS Powertools Integration
|
|
227
262
|
|
|
228
263
|
For production Lambda functions, use AWS Powertools for standardized logging, metrics, and error handling:
|
|
229
264
|
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# JWT Authentication Guide
|
|
2
|
+
|
|
3
|
+
RS256 JWT token validation for AWS Lambda functions behind API Gateway.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
The `jwt_auth` module validates RS256-signed JWTs using public keys stored in AWS Secrets Manager. It uses the pure-Python `rsa` package (~100KB) instead of PyJWT or `cryptography` (~35MB), keeping Lambda bundles small without needing a Lambda Layer.
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
pip install nui-lambda-shared-utils[jwt]
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Quick Start
|
|
16
|
+
|
|
17
|
+
```python
|
|
18
|
+
from nui_lambda_shared_utils import require_auth, AuthenticationError
|
|
19
|
+
|
|
20
|
+
def lambda_handler(event, context):
|
|
21
|
+
try:
|
|
22
|
+
claims = require_auth(event)
|
|
23
|
+
except AuthenticationError:
|
|
24
|
+
return {"statusCode": 401, "body": "Unauthorized"}
|
|
25
|
+
|
|
26
|
+
return {"statusCode": 200, "body": f"Hello {claims['sub']}"}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## API Reference
|
|
30
|
+
|
|
31
|
+
### `require_auth(event, secret_name=None) -> dict`
|
|
32
|
+
|
|
33
|
+
One-call authentication for API Gateway Lambda handlers. Extracts the Bearer token from the Authorization header, fetches the public key from Secrets Manager, and validates the token.
|
|
34
|
+
|
|
35
|
+
- **event** — API Gateway Lambda proxy event (v1 REST or v2 HTTP API)
|
|
36
|
+
- **secret_name** — Secrets Manager secret name (falls back to `JWT_PUBLIC_KEY_SECRET` env var)
|
|
37
|
+
- **Returns** — Decoded claims dict
|
|
38
|
+
- **Raises** — `AuthenticationError` on any failure (missing header, bad token, expired, bad signature, missing key)
|
|
39
|
+
|
|
40
|
+
### `validate_jwt(token, public_key) -> dict`
|
|
41
|
+
|
|
42
|
+
Lower-level validation when you already have the public key. Verifies token structure, RS256 signature, and expiration.
|
|
43
|
+
|
|
44
|
+
- **token** — Raw JWT string (no `Bearer` prefix)
|
|
45
|
+
- **public_key** — `rsa.PublicKey` instance
|
|
46
|
+
- **Returns** — Decoded claims dict
|
|
47
|
+
- **Raises** — `JWTValidationError` on failure
|
|
48
|
+
|
|
49
|
+
### `get_jwt_public_key(secret_name=None, key_field="TOKEN_PUBLIC_KEY") -> rsa.PublicKey`
|
|
50
|
+
|
|
51
|
+
Fetches and parses the PEM public key from Secrets Manager. Uses `secrets_helper` cache — repeated calls don't make additional API calls.
|
|
52
|
+
|
|
53
|
+
- **secret_name** — Falls back to `JWT_PUBLIC_KEY_SECRET` env var
|
|
54
|
+
- **key_field** — JSON field in the secret containing the PEM string
|
|
55
|
+
- **Returns** — `rsa.PublicKey`
|
|
56
|
+
- **Raises** — `JWTValidationError` if secret or field is missing/invalid
|
|
57
|
+
|
|
58
|
+
### Exceptions
|
|
59
|
+
|
|
60
|
+
- **`JWTValidationError`** — Base exception for token validation failures
|
|
61
|
+
- **`AuthenticationError(JWTValidationError)`** — Authentication-level failures (what `require_auth` raises)
|
|
62
|
+
|
|
63
|
+
## AWS Setup
|
|
64
|
+
|
|
65
|
+
### 1. Store the Public Key
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
aws secretsmanager create-secret \
|
|
69
|
+
--name "prod/jwt-public-key" \
|
|
70
|
+
--secret-string '{"TOKEN_PUBLIC_KEY": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqh...\n-----END PUBLIC KEY-----"}'
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
The key must be PKCS#8 PEM format (`BEGIN PUBLIC KEY`, not `BEGIN RSA PUBLIC KEY`).
|
|
74
|
+
|
|
75
|
+
### 2. Lambda Environment
|
|
76
|
+
|
|
77
|
+
```yaml
|
|
78
|
+
# serverless.yml
|
|
79
|
+
environment:
|
|
80
|
+
JWT_PUBLIC_KEY_SECRET: prod/jwt-public-key
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### 3. IAM Permissions
|
|
84
|
+
|
|
85
|
+
```yaml
|
|
86
|
+
# serverless.yml
|
|
87
|
+
iamRoleStatements:
|
|
88
|
+
- Effect: Allow
|
|
89
|
+
Action: secretsmanager:GetSecretValue
|
|
90
|
+
Resource: arn:aws:secretsmanager:*:*:secret:prod/jwt-public-key-*
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Usage Patterns
|
|
94
|
+
|
|
95
|
+
### Skip Auth for Health Endpoints
|
|
96
|
+
|
|
97
|
+
```python
|
|
98
|
+
def lambda_handler(event, context):
|
|
99
|
+
path = event.get("path") or event.get("rawPath", "")
|
|
100
|
+
if path == "/health":
|
|
101
|
+
return {"statusCode": 200, "body": "ok"}
|
|
102
|
+
|
|
103
|
+
try:
|
|
104
|
+
claims = require_auth(event)
|
|
105
|
+
except AuthenticationError:
|
|
106
|
+
return {"statusCode": 401, "body": "Unauthorized"}
|
|
107
|
+
|
|
108
|
+
# Authenticated route handling
|
|
109
|
+
return handle_request(event, claims)
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Direct Validation (Pre-fetched Key)
|
|
113
|
+
|
|
114
|
+
```python
|
|
115
|
+
from nui_lambda_shared_utils import get_jwt_public_key, validate_jwt, JWTValidationError
|
|
116
|
+
|
|
117
|
+
# Fetch key once at module level (cached by secrets_helper)
|
|
118
|
+
public_key = get_jwt_public_key(secret_name="prod/jwt-public-key")
|
|
119
|
+
|
|
120
|
+
def validate_token(token: str) -> dict:
|
|
121
|
+
try:
|
|
122
|
+
return validate_jwt(token, public_key)
|
|
123
|
+
except JWTValidationError as e:
|
|
124
|
+
raise ValueError(f"Bad token: {e}")
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### Custom Key Field Name
|
|
128
|
+
|
|
129
|
+
```python
|
|
130
|
+
# If your secret uses a different field name
|
|
131
|
+
key = get_jwt_public_key(
|
|
132
|
+
secret_name="prod/auth-keys",
|
|
133
|
+
key_field="RSA_PUBLIC_KEY"
|
|
134
|
+
)
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## What Gets Validated
|
|
138
|
+
|
|
139
|
+
| Check | Behavior |
|
|
140
|
+
|-------|----------|
|
|
141
|
+
| Token structure | Must be 3 dot-separated base64url segments |
|
|
142
|
+
| Algorithm | Header `alg` must be `RS256` |
|
|
143
|
+
| Signature | RSA PKCS#1 v1.5 with SHA-256 |
|
|
144
|
+
| Expiration | `exp` claim checked against current time (optional — tokens without `exp` are accepted) |
|
|
145
|
+
|
|
146
|
+
## Dependency Details
|
|
147
|
+
|
|
148
|
+
The module uses `rsa` (pure Python, ~100KB) for signature verification at runtime. This avoids the ~35MB `cryptography` C extension that PyJWT requires for RS256. The `cryptography` package is only needed in dev for generating test key pairs and signing test tokens.
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
# Log Processing Guide
|
|
2
|
+
|
|
3
|
+
This guide covers the log processing utilities for Lambda functions that stream
|
|
4
|
+
CloudWatch logs to Elasticsearch via Kinesis.
|
|
5
|
+
|
|
6
|
+
## Overview
|
|
7
|
+
|
|
8
|
+
The `log_processors` module provides utilities for:
|
|
9
|
+
|
|
10
|
+
- Extracting CloudWatch logs from Kinesis stream records
|
|
11
|
+
- Deriving Elasticsearch index names from log metadata
|
|
12
|
+
- Type definitions for CloudWatch log structures
|
|
13
|
+
|
|
14
|
+
## Quick Start
|
|
15
|
+
|
|
16
|
+
### Basic Usage
|
|
17
|
+
|
|
18
|
+
```python
|
|
19
|
+
from elasticsearch.helpers import streaming_bulk
|
|
20
|
+
from nui_lambda_shared_utils.log_processors import (
|
|
21
|
+
extract_cloudwatch_logs_from_kinesis,
|
|
22
|
+
derive_index_name,
|
|
23
|
+
)
|
|
24
|
+
from datetime import datetime, timezone
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def process_log_events(log_group: str, log_stream: str, log_events: list):
|
|
28
|
+
"""Process log events and yield ES documents."""
|
|
29
|
+
for event in log_events:
|
|
30
|
+
ts = datetime.fromtimestamp(event["timestamp"] / 1000.0, tz=timezone.utc)
|
|
31
|
+
|
|
32
|
+
yield {
|
|
33
|
+
"_index": derive_index_name(log_group, ts),
|
|
34
|
+
"_id": event["id"],
|
|
35
|
+
"_source": {
|
|
36
|
+
"message": event["message"],
|
|
37
|
+
"@timestamp": ts.isoformat(),
|
|
38
|
+
"log": {"group": log_group, "stream": log_stream},
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def handler(event, context):
|
|
44
|
+
"""Lambda handler for Kinesis -> ES streaming."""
|
|
45
|
+
es = get_elasticsearch_client()
|
|
46
|
+
|
|
47
|
+
for ok, response in streaming_bulk(
|
|
48
|
+
client=es,
|
|
49
|
+
actions=extract_cloudwatch_logs_from_kinesis(
|
|
50
|
+
event["Records"],
|
|
51
|
+
process_fn=process_log_events
|
|
52
|
+
),
|
|
53
|
+
chunk_size=100,
|
|
54
|
+
raise_on_error=True,
|
|
55
|
+
):
|
|
56
|
+
if not ok:
|
|
57
|
+
logger.error(f"Document indexing failed: {response}")
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Error Handling
|
|
61
|
+
|
|
62
|
+
Provide an `on_error` callback to handle failures without stopping the entire batch:
|
|
63
|
+
|
|
64
|
+
```python
|
|
65
|
+
def handle_processing_error(exception: Exception, record_data: dict):
|
|
66
|
+
"""Log errors but continue processing."""
|
|
67
|
+
logger.error(f"Failed to process record: {exception}")
|
|
68
|
+
# Optionally send to dead letter queue, metrics, etc.
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
for doc in extract_cloudwatch_logs_from_kinesis(
|
|
72
|
+
event["Records"],
|
|
73
|
+
process_fn=process_log_events,
|
|
74
|
+
on_error=handle_processing_error
|
|
75
|
+
):
|
|
76
|
+
# Documents from successfully processed records
|
|
77
|
+
pass
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Custom Index Naming
|
|
81
|
+
|
|
82
|
+
Override the default index naming pattern:
|
|
83
|
+
|
|
84
|
+
```python
|
|
85
|
+
from nui_lambda_shared_utils.log_processors import derive_index_name
|
|
86
|
+
|
|
87
|
+
# Default: log-{service}-{YYYY}-m{MM}
|
|
88
|
+
derive_index_name("/aws/lambda/orders", ts)
|
|
89
|
+
# -> "log-orders-2025-m01"
|
|
90
|
+
|
|
91
|
+
# Custom target
|
|
92
|
+
derive_index_name("/aws/lambda/orders", ts, target_override="order-service")
|
|
93
|
+
# -> "log-order-service-2025-m01"
|
|
94
|
+
|
|
95
|
+
# Custom prefix and date format
|
|
96
|
+
derive_index_name("/aws/lambda/orders", ts, prefix="logs", date_format="%Y-%m-%d")
|
|
97
|
+
# -> "logs-orders-2025-01-15"
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Migration Guide
|
|
101
|
+
|
|
102
|
+
### From inline Kinesis extraction
|
|
103
|
+
|
|
104
|
+
Replace:
|
|
105
|
+
|
|
106
|
+
```python
|
|
107
|
+
# Before
|
|
108
|
+
def extract_logs(records):
|
|
109
|
+
for row in records:
|
|
110
|
+
raw_data = row["kinesis"]["data"]
|
|
111
|
+
data = json.loads(
|
|
112
|
+
zlib.decompress(base64.b64decode(raw_data), 16 + zlib.MAX_WBITS).decode("utf-8")
|
|
113
|
+
)
|
|
114
|
+
if data["messageType"] == "CONTROL_MESSAGE":
|
|
115
|
+
continue
|
|
116
|
+
for item in process_log_events(...):
|
|
117
|
+
yield item
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
With:
|
|
121
|
+
|
|
122
|
+
```python
|
|
123
|
+
# After
|
|
124
|
+
from nui_lambda_shared_utils.log_processors import extract_cloudwatch_logs_from_kinesis
|
|
125
|
+
|
|
126
|
+
for doc in extract_cloudwatch_logs_from_kinesis(event["Records"], process_log_events):
|
|
127
|
+
yield doc
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## API Reference
|
|
131
|
+
|
|
132
|
+
### `extract_cloudwatch_logs_from_kinesis()`
|
|
133
|
+
|
|
134
|
+
Extract CloudWatch logs from Kinesis stream records.
|
|
135
|
+
|
|
136
|
+
**Parameters:**
|
|
137
|
+
|
|
138
|
+
- `records`: List of Kinesis event records (`event["Records"]`)
|
|
139
|
+
- `process_fn`: Callback to process log events
|
|
140
|
+
- `on_error`: Optional error handler (if None, exceptions are raised)
|
|
141
|
+
|
|
142
|
+
**Yields:** Dict documents ready for `streaming_bulk()`
|
|
143
|
+
|
|
144
|
+
### `derive_index_name()`
|
|
145
|
+
|
|
146
|
+
Derive Elasticsearch index name from log metadata.
|
|
147
|
+
|
|
148
|
+
**Parameters:**
|
|
149
|
+
|
|
150
|
+
- `log_group`: CloudWatch log group name
|
|
151
|
+
- `timestamp`: Event timestamp for date suffix
|
|
152
|
+
- `prefix`: Index name prefix (default: "log")
|
|
153
|
+
- `date_format`: strftime format (default: "%Y-m%m")
|
|
154
|
+
- `target_override`: Custom service name (optional)
|
|
155
|
+
|
|
156
|
+
**Returns:** Index name string
|
{nui_lambda_shared_utils-1.1.4 → nui_lambda_shared_utils-1.2.0}/nui_lambda_shared_utils/__init__.py
RENAMED
|
@@ -121,9 +121,33 @@ except ImportError:
|
|
|
121
121
|
get_powertools_logger = None # type: ignore
|
|
122
122
|
powertools_handler = None # type: ignore
|
|
123
123
|
|
|
124
|
+
# Log processing utilities (no external dependencies)
|
|
125
|
+
from .log_processors import (
|
|
126
|
+
CloudWatchLogEvent,
|
|
127
|
+
CloudWatchLogsData,
|
|
128
|
+
derive_index_name,
|
|
129
|
+
extract_cloudwatch_logs_from_kinesis,
|
|
130
|
+
)
|
|
131
|
+
|
|
124
132
|
# Lambda context helpers (no external dependencies)
|
|
125
133
|
from .lambda_helpers import get_lambda_environment_info
|
|
126
134
|
|
|
135
|
+
# JWT authentication - optional import
|
|
136
|
+
try:
|
|
137
|
+
from .jwt_auth import (
|
|
138
|
+
validate_jwt,
|
|
139
|
+
require_auth,
|
|
140
|
+
get_jwt_public_key,
|
|
141
|
+
JWTValidationError,
|
|
142
|
+
AuthenticationError,
|
|
143
|
+
)
|
|
144
|
+
except ImportError:
|
|
145
|
+
validate_jwt = None # type: ignore
|
|
146
|
+
require_auth = None # type: ignore
|
|
147
|
+
get_jwt_public_key = None # type: ignore
|
|
148
|
+
JWTValidationError = None # type: ignore
|
|
149
|
+
AuthenticationError = None # type: ignore
|
|
150
|
+
|
|
127
151
|
# Slack setup utilities (for CLI usage) - optional import
|
|
128
152
|
try:
|
|
129
153
|
from . import slack_setup
|
|
@@ -209,6 +233,17 @@ __all__ = [
|
|
|
209
233
|
# AWS Powertools integration
|
|
210
234
|
"get_powertools_logger",
|
|
211
235
|
"powertools_handler",
|
|
236
|
+
# Log processing
|
|
237
|
+
"extract_cloudwatch_logs_from_kinesis",
|
|
238
|
+
"derive_index_name",
|
|
239
|
+
"CloudWatchLogEvent",
|
|
240
|
+
"CloudWatchLogsData",
|
|
212
241
|
# Lambda context helpers
|
|
213
242
|
"get_lambda_environment_info",
|
|
243
|
+
# JWT authentication
|
|
244
|
+
"validate_jwt",
|
|
245
|
+
"require_auth",
|
|
246
|
+
"get_jwt_public_key",
|
|
247
|
+
"JWTValidationError",
|
|
248
|
+
"AuthenticationError",
|
|
214
249
|
]
|