@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.
@@ -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: APIGatewayProxyEventV2, requestId: string): Promise<void>;
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;;;;GAIG;AACH,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAazD;;;;;;;GAOG;AACH,wBAAsB,oBAAoB,CACxC,KAAK,EAAE,sBAAsB,EAC7B,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,IAAI,CAAC,CAgCf"}
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
- // 認証チェック: IAMまたはCognito JWT
20
- // Lambda Function URLがNONEの場合、両方の認証方式をサポート
21
- const authHeader = event.headers.authorization || event.headers.Authorization;
22
- // AWS SigV4署名の検出(複数のヘッダーで判定)
23
- const hasAmzDate = event.headers['x-amz-date'] || event.headers['X-Amz-Date'];
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 event - Lambda Function URLイベント
32
+ * @param iam - AWSが検証済みのIAM認証コンテキスト(偽装不可)
51
33
  * @param requestId - リクエストID
52
34
  */
53
- async function handleIAMAuthentication(event, requestId) {
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
- sourceIp: event.requestContext.http.sourceIp,
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":"AAOA,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;AAEH;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACxC,KAA6B,EAC7B,SAAiB;IAEjB,4BAA4B;IAC5B,2CAA2C;IAC3C,MAAM,UAAU,GAAG,KAAK,CAAC,OAAO,CAAC,aAAa,IAAI,KAAK,CAAC,OAAO,CAAC,aAAa,CAAC;IAE9E,6BAA6B;IAC7B,MAAM,UAAU,GAAG,KAAK,CAAC,OAAO,CAAC,YAAY,CAAC,IAAI,KAAK,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC;IAC9E,MAAM,gBAAgB,GACpB,KAAK,CAAC,OAAO,CAAC,sBAAsB,CAAC,IAAI,KAAK,CAAC,OAAO,CAAC,sBAAsB,CAAC,CAAC;IACjF,MAAM,eAAe,GAAG,UAAU,EAAE,UAAU,CAAC,kBAAkB,CAAC,CAAC;IAEnE,kBAAkB;IAClB,MAAM,CAAC,KAAK,CAAC,sBAAsB,EAAE;QACnC,SAAS;QACT,aAAa,EAAE,CAAC,CAAC,UAAU;QAC3B,UAAU,EAAE,CAAC,CAAC,UAAU;QACxB,gBAAgB,EAAE,CAAC,CAAC,gBAAgB;QACpC,eAAe,EAAE,CAAC,CAAC,eAAe;QAClC,gBAAgB,EAAE,UAAU,EAAE,SAAS,CAAC,CAAC,EAAE,EAAE,CAAC;KAC/C,CAAC,CAAC;IAEH,qCAAqC;IACrC,8BAA8B;IAC9B,4CAA4C;IAC5C,6CAA6C;IAC7C,MAAM,SAAS,GAAG,eAAe,IAAI,CAAC,CAAC,CAAC,UAAU,IAAI,gBAAgB,CAAC,CAAC;IAExE,IAAI,SAAS,EAAE,CAAC;QACd,MAAM,uBAAuB,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC;IAClD,CAAC;SAAM,CAAC;QACN,MAAM,2BAA2B,CAAC,UAAU,EAAE,SAAS,CAAC,CAAC;IAC3D,CAAC;AACH,CAAC;AAED;;;;;GAKG;AACH,KAAK,UAAU,uBAAuB,CACpC,KAA6B,EAC7B,SAAiB;IAEjB,sBAAsB;IACtB,4CAA4C;IAC5C,kCAAkC;IAClC,MAAM,CAAC,IAAI,CAAC,2BAA2B,EAAE;QACvC,SAAS;QACT,QAAQ,EAAE,KAAK,CAAC,cAAc,CAAC,IAAI,CAAC,QAAQ;KAC7C,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"}
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.1",
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"
@@ -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
+ }
@@ -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" {