@exabugs/dynamodb-client 1.5.1 → 1.5.2
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/CHANGELOG.md +17 -0
- package/dist/server/handler.cjs +13 -22
- package/dist/server/handler.cjs.map +2 -2
- package/dist/server/utils/authHandler.d.ts +40 -1
- package/dist/server/utils/authHandler.d.ts.map +1 -1
- package/dist/server/utils/authHandler.js +14 -33
- package/dist/server/utils/authHandler.js.map +1 -1
- package/package.json +1 -1
- package/terraform/main.tf +33 -0
- package/terraform/outputs.tf +10 -0
- package/terraform/variables.tf +6 -0
|
@@ -2,8 +2,46 @@
|
|
|
2
2
|
* 認証ハンドラー
|
|
3
3
|
*
|
|
4
4
|
* IAM認証とCognito JWT認証を処理する
|
|
5
|
+
*
|
|
6
|
+
* このLambdaは2本のFunction URLから呼ばれる:
|
|
7
|
+
* - NONE認証のURL(ブラウザ・Admin UI向け): Cognito JWTのみを受け付ける
|
|
8
|
+
* - AWS_IAM認証のURL(サーバー間呼び出し向け): AWSが署名検証済みのリクエストのみ到達する
|
|
9
|
+
*
|
|
10
|
+
* 旧実装はAuthorization/x-amz-date等のヘッダーの「見た目」でIAM認証とみなしていたが、
|
|
11
|
+
* これらはクライアントが自由に送れる値であり、NONE認証のURL上では一切検証されない
|
|
12
|
+
* (署名検証はAWS_IAM認証のURL上でAWS自身が行うものであり、NONEのURLには存在しない)。
|
|
13
|
+
* そのため誰でも `x-amz-date` 等のヘッダーを送るだけでIAM認証を騙り、全リソースへの
|
|
14
|
+
* 認証なしアクセスが可能になっていた。
|
|
15
|
+
*
|
|
16
|
+
* 修正後は `event.requestContext.authorizer.iam` の有無で判定する。この値はAWS_IAM認証の
|
|
17
|
+
* Function URLが実際に署名検証に成功した場合にのみAWS側が付与するものであり、
|
|
18
|
+
* クライアントが送るヘッダーからは偽装できない。
|
|
5
19
|
*/
|
|
6
20
|
import type { APIGatewayProxyEventV2 } from 'aws-lambda';
|
|
21
|
+
/**
|
|
22
|
+
* AWS_IAM認証のFunction URLがリクエストに付与するコンテキスト
|
|
23
|
+
* (AWS Lambda公式の型定義 APIGatewayEventRequestContextIAMAuthorizer と同じ形)
|
|
24
|
+
*/
|
|
25
|
+
interface IAMAuthorizerContext {
|
|
26
|
+
accessKey: string;
|
|
27
|
+
accountId: string;
|
|
28
|
+
callerId: string;
|
|
29
|
+
cognitoIdentity: null;
|
|
30
|
+
principalOrgId: string;
|
|
31
|
+
userArn: string;
|
|
32
|
+
userId: string;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* NONE/AWS_IAM両方のFunction URLから届き得るイベント
|
|
36
|
+
* (同一のLambdaコードが両方のURLから呼ばれるため、authorizerはオプショナル)
|
|
37
|
+
*/
|
|
38
|
+
type RecordsEvent = APIGatewayProxyEventV2 & {
|
|
39
|
+
requestContext: {
|
|
40
|
+
authorizer?: {
|
|
41
|
+
iam?: IAMAuthorizerContext;
|
|
42
|
+
};
|
|
43
|
+
};
|
|
44
|
+
};
|
|
7
45
|
/**
|
|
8
46
|
* 認証を処理する
|
|
9
47
|
*
|
|
@@ -12,5 +50,6 @@ import type { APIGatewayProxyEventV2 } from 'aws-lambda';
|
|
|
12
50
|
* @returns 認証が成功した場合はvoid、失敗した場合は例外をスロー
|
|
13
51
|
* @throws {Error} 認証に失敗した場合
|
|
14
52
|
*/
|
|
15
|
-
export declare function handleAuthentication(event:
|
|
53
|
+
export declare function handleAuthentication(event: RecordsEvent, requestId: string): Promise<void>;
|
|
54
|
+
export {};
|
|
16
55
|
//# sourceMappingURL=authHandler.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"authHandler.d.ts","sourceRoot":"","sources":["../../../src/server/utils/authHandler.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"authHandler.d.ts","sourceRoot":"","sources":["../../../src/server/utils/authHandler.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAazD;;;GAGG;AACH,UAAU,oBAAoB;IAC5B,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,eAAe,EAAE,IAAI,CAAC;IACtB,cAAc,EAAE,MAAM,CAAC;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;GAGG;AACH,KAAK,YAAY,GAAG,sBAAsB,GAAG;IAC3C,cAAc,EAAE;QACd,UAAU,CAAC,EAAE;YACX,GAAG,CAAC,EAAE,oBAAoB,CAAC;SAC5B,CAAC;KACH,CAAC;CACH,CAAC;AAEF;;;;;;;GAOG;AACH,wBAAsB,oBAAoB,CACxC,KAAK,EAAE,YAAY,EACnB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,IAAI,CAAC,CAYf"}
|
|
@@ -16,47 +16,28 @@ const logger = createLogger({
|
|
|
16
16
|
* @throws {Error} 認証に失敗した場合
|
|
17
17
|
*/
|
|
18
18
|
export async function handleAuthentication(event, requestId) {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
const hasAmzContentSha = event.headers['x-amz-content-sha256'] || event.headers['X-Amz-Content-Sha256'];
|
|
25
|
-
const hasAwsSigV4Auth = authHeader?.startsWith('AWS4-HMAC-SHA256');
|
|
26
|
-
// デバッグ: ヘッダーをログ出力
|
|
27
|
-
logger.debug('Authentication check', {
|
|
28
|
-
requestId,
|
|
29
|
-
hasAuthHeader: !!authHeader,
|
|
30
|
-
hasAmzDate: !!hasAmzDate,
|
|
31
|
-
hasAmzContentSha: !!hasAmzContentSha,
|
|
32
|
-
hasAwsSigV4Auth: !!hasAwsSigV4Auth,
|
|
33
|
-
authHeaderPrefix: authHeader?.substring(0, 20),
|
|
34
|
-
});
|
|
35
|
-
// IAM認証チェック(AWS SigV4署名の特徴的なヘッダーで判定)
|
|
36
|
-
// 以下のいずれかの条件を満たす場合、IAM認証とみなす:
|
|
37
|
-
// 1. AuthorizationヘッダーがAWS4-HMAC-SHA256で始まる
|
|
38
|
-
// 2. x-amz-dateとx-amz-content-sha256の両方が存在する
|
|
39
|
-
const isIAMAuth = hasAwsSigV4Auth || !!(hasAmzDate && hasAmzContentSha);
|
|
40
|
-
if (isIAMAuth) {
|
|
41
|
-
await handleIAMAuthentication(event, requestId);
|
|
42
|
-
}
|
|
43
|
-
else {
|
|
44
|
-
await handleCognitoAuthentication(authHeader, requestId);
|
|
19
|
+
const iam = event.requestContext.authorizer?.iam;
|
|
20
|
+
if (iam) {
|
|
21
|
+
// AWS_IAM認証のFunction URL経由。AWSが署名検証済みであり、この値はクライアントから偽装できない
|
|
22
|
+
handleIAMAuthentication(iam, requestId);
|
|
23
|
+
return;
|
|
45
24
|
}
|
|
25
|
+
// NONE認証のFunction URL経由。Cognito JWTのみを受け付ける
|
|
26
|
+
const authHeader = event.headers.authorization || event.headers.Authorization;
|
|
27
|
+
await handleCognitoAuthentication(authHeader, requestId);
|
|
46
28
|
}
|
|
47
29
|
/**
|
|
48
|
-
* IAM
|
|
30
|
+
* IAM認証済みリクエストを処理する
|
|
49
31
|
*
|
|
50
|
-
* @param
|
|
32
|
+
* @param iam - AWSが検証済みのIAM認証コンテキスト(偽装不可)
|
|
51
33
|
* @param requestId - リクエストID
|
|
52
34
|
*/
|
|
53
|
-
|
|
54
|
-
// IAM認証(スクリプトからのアクセス)
|
|
55
|
-
// Lambda Function URLがNONEの場合、署名検証は行われないため、
|
|
56
|
-
// ここではIAM認証として扱うが、実際の検証はAWS側で行われる
|
|
35
|
+
function handleIAMAuthentication(iam, requestId) {
|
|
57
36
|
logger.info('IAM authenticated request', {
|
|
58
37
|
requestId,
|
|
59
|
-
|
|
38
|
+
userArn: iam.userArn,
|
|
39
|
+
accountId: iam.accountId,
|
|
40
|
+
callerId: iam.callerId,
|
|
60
41
|
});
|
|
61
42
|
}
|
|
62
43
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"authHandler.js","sourceRoot":"","sources":["../../../src/server/utils/authHandler.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"authHandler.js","sourceRoot":"","sources":["../../../src/server/utils/authHandler.ts"],"names":[],"mappings":"AAqBA,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AACrD,OAAO,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAE7C;;GAEG;AACH,MAAM,MAAM,GAAG,YAAY,CAAC;IAC1B,OAAO,EAAE,cAAc;IACvB,KAAK,EAAG,OAAO,CAAC,GAAG,CAAC,SAAiD,IAAI,MAAM;CAChF,CAAC,CAAC;AA4BH;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACxC,KAAmB,EACnB,SAAiB;IAEjB,MAAM,GAAG,GAAG,KAAK,CAAC,cAAc,CAAC,UAAU,EAAE,GAAG,CAAC;IAEjD,IAAI,GAAG,EAAE,CAAC;QACR,4DAA4D;QAC5D,uBAAuB,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;QACxC,OAAO;IACT,CAAC;IAED,4CAA4C;IAC5C,MAAM,UAAU,GAAG,KAAK,CAAC,OAAO,CAAC,aAAa,IAAI,KAAK,CAAC,OAAO,CAAC,aAAa,CAAC;IAC9E,MAAM,2BAA2B,CAAC,UAAU,EAAE,SAAS,CAAC,CAAC;AAC3D,CAAC;AAED;;;;;GAKG;AACH,SAAS,uBAAuB,CAAC,GAAyB,EAAE,SAAiB;IAC3E,MAAM,CAAC,IAAI,CAAC,2BAA2B,EAAE;QACvC,SAAS;QACT,OAAO,EAAE,GAAG,CAAC,OAAO;QACpB,SAAS,EAAE,GAAG,CAAC,SAAS;QACxB,QAAQ,EAAE,GAAG,CAAC,QAAQ;KACvB,CAAC,CAAC;AACL,CAAC;AAED;;;;;;GAMG;AACH,KAAK,UAAU,2BAA2B,CACxC,UAA8B,EAC9B,SAAiB;IAEjB,6BAA6B;IAC7B,MAAM,UAAU,GAAG,OAAO,CAAC,GAAG,CAAC,oBAAoB,CAAC;IACpD,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC,QAAQ;IAExD,IAAI,CAAC,UAAU,EAAE,CAAC;QAChB,MAAM,IAAI,KAAK,CAAC,uDAAuD,CAAC,CAAC;IAC3E,CAAC;IAED,MAAM,UAAU,GAAG,MAAM,gBAAgB,CAAC,UAAU,EAAE,UAAU,EAAE,QAAQ,CAAC,CAAC;IAE5E,MAAM,CAAC,KAAK,CAAC,sBAAsB,EAAE;QACnC,SAAS;QACT,GAAG,EAAE,UAAU,CAAC,GAAG;QACnB,KAAK,EAAE,UAAU,CAAC,KAAK;KACxB,CAAC,CAAC;IAEH,kBAAkB;IAClB,uCAAuC;AACzC,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@exabugs/dynamodb-client",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.2",
|
|
4
4
|
"description": "DynamoDB Single-Table Client SDK with MongoDB-like API, Shadow Records, and Lambda implementation for serverless applications",
|
|
5
5
|
"author": "exabugs",
|
|
6
6
|
"license": "MIT",
|
package/terraform/main.tf
CHANGED
|
@@ -116,6 +116,7 @@ resource "aws_lambda_function" "records" {
|
|
|
116
116
|
REGION = var.region
|
|
117
117
|
TABLE_NAME = var.dynamodb_table_name
|
|
118
118
|
COGNITO_USER_POOL_ID = var.cognito_user_pool_id
|
|
119
|
+
COGNITO_CLIENT_ID = var.cognito_client_id
|
|
119
120
|
LOG_LEVEL = var.log_level
|
|
120
121
|
# シャドウ設定(環境変数ベース)
|
|
121
122
|
SHADOW_CREATED_AT_FIELD = var.shadow_created_at_field
|
|
@@ -168,6 +169,38 @@ resource "aws_lambda_permission" "function_url" {
|
|
|
168
169
|
principal = "*"
|
|
169
170
|
function_url_auth_type = "NONE"
|
|
170
171
|
}
|
|
172
|
+
|
|
173
|
+
# サーバー間(Lambda-to-Lambda)呼び出し専用のエイリアス
|
|
174
|
+
# NONE認証のFunction URL(上記)とは別に、AWS_IAM認証のFunction URLをこのエイリアス経由で公開する。
|
|
175
|
+
# 同一のLambda関数コード($LATEST)を指すが、Function URLは修飾子(エイリアス)ごとに1本しか
|
|
176
|
+
# 設定できないため、2本目のURLを持つにはエイリアスが必要。
|
|
177
|
+
resource "aws_lambda_alias" "iam" {
|
|
178
|
+
name = "iam"
|
|
179
|
+
description = "サーバー間呼び出し用(AWS_IAM認証のFunction URL向け)"
|
|
180
|
+
function_name = aws_lambda_function.records.function_name
|
|
181
|
+
function_version = "$LATEST"
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
# AWS_IAM認証のLambda Function URL
|
|
185
|
+
# サーバー間呼び出し元(モバイルBFF・weather Lambda・運用スクリプト等)はこちらを使う。
|
|
186
|
+
# NONE認証URLと異なり、呼び出し元のIAMロールにlambda:InvokeFunctionUrl権限が必要で、
|
|
187
|
+
# AWSが実際にSigV4署名を検証する(NONE側のような「なんちゃってIAM認証」ではない)。
|
|
188
|
+
resource "aws_lambda_function_url" "records_iam" {
|
|
189
|
+
function_name = aws_lambda_function.records.function_name
|
|
190
|
+
qualifier = aws_lambda_alias.iam.name
|
|
191
|
+
authorization_type = "AWS_IAM"
|
|
192
|
+
|
|
193
|
+
cors {
|
|
194
|
+
allow_origins = ["*"]
|
|
195
|
+
allow_methods = ["GET", "POST", "PUT", "DELETE"]
|
|
196
|
+
allow_headers = ["content-type", "authorization", "x-amz-date", "x-api-key", "x-amz-security-token"]
|
|
197
|
+
expose_headers = ["content-type", "x-amzn-requestid"]
|
|
198
|
+
allow_credentials = false
|
|
199
|
+
max_age = 86400 # 24時間
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
depends_on = [aws_lambda_alias.iam]
|
|
203
|
+
}
|
|
171
204
|
# Parameter Store モジュール
|
|
172
205
|
module "parameter_store" {
|
|
173
206
|
source = "./modules/parameter-store"
|
package/terraform/outputs.tf
CHANGED
|
@@ -49,3 +49,13 @@ output "function_url_id" {
|
|
|
49
49
|
description = "Lambda Function URL ID"
|
|
50
50
|
value = aws_lambda_function_url.records.url_id
|
|
51
51
|
}
|
|
52
|
+
|
|
53
|
+
output "iam_function_url" {
|
|
54
|
+
description = "AWS_IAM認証のLambda Function URL(サーバー間呼び出し専用)"
|
|
55
|
+
value = aws_lambda_function_url.records_iam.function_url
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
output "iam_alias_arn" {
|
|
59
|
+
description = "AWS_IAM認証用エイリアスのARN。呼び出し元IAMロールのlambda:InvokeFunctionUrl権限のResourceに指定する"
|
|
60
|
+
value = aws_lambda_alias.iam.arn
|
|
61
|
+
}
|
package/terraform/variables.tf
CHANGED
|
@@ -30,6 +30,12 @@ variable "cognito_user_pool_id" {
|
|
|
30
30
|
type = string
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
variable "cognito_client_id" {
|
|
34
|
+
description = "Cognito App Client ID(JWTのaud検証に使用。未指定の場合はaud検証をスキップする)"
|
|
35
|
+
type = string
|
|
36
|
+
default = null
|
|
37
|
+
}
|
|
38
|
+
|
|
33
39
|
|
|
34
40
|
|
|
35
41
|
variable "log_retention_days" {
|