🛠️

minio + aws-sdk-js v3で「MissingContentMD5」エラーが出る場合の対処

に公開

AWS SDK for JavaScript v3 + minioを使ってCI上でテストを実行していたところ、次のようなエラーに遭遇しました。調べても日本語での情報が無さそうだったので、私が実施した解決策を備忘録的に書きます。

MissingContentMD5: Missing required header for this request: Content-Md5.

原因

このエラーは、SDKのバージョンv3.729.0以降で発生する可能性があります。というのも、公式ドキュメントにもあるように、v3.729.0からデフォルトのチェックサム方式が変更になったようです(以前はMD5によるチェックサムだった)。
しかし、MinIOなどの一部S3互換サービスは、この新しいチェックサム方式にまだ対応しておらず、特にDeleteObjectsの呼び出しにおいては Content-MD5 ヘッダーが必須であるため、このエラーが発生します。

解決策

さきほどの公式ドキュメントにある解決策では、middleware経由でDeleteObjects実行時にContent-MD5ヘッダーを設定することでエラーを回避しています。

しかし、

  • 私の検証環境(v3.782.0)では、"flexibleChecksumsMiddleware"がmiddleware stackに存在しないと言われてしまう
  • TypeScript環境だと型が明示されていないのでエラーになる

という問題がありました。
ということで、以下が公式の解決策に手を入れたものです。深追いしてないので他にもっといい方法があるかもですが、私の場合はテストでとりあえず動かしたいだけだったので、これでよしとしました。

import assert from "node:assert";
import { createHash } from "node:crypto";
import {
  S3Client,
  type ServiceInputTypes,
  type ServiceOutputTypes,
} from "@aws-sdk/client-s3";
import { HttpRequest } from "@smithy/protocol-http";
import type { FinalizeRequestMiddleware } from "@smithy/types";

export const s3Client = new S3Client({
  region: "ap-northeast-1",
  endpoint: "http://localhost:9040",
  credentials: {
    accessKeyId: "minio",
    secretAccessKey: "minio123",
  },
  forcePathStyle: true,
  tls: false,
});
const md5Middleware: FinalizeRequestMiddleware<
  ServiceInputTypes,
  ServiceOutputTypes
> = (next, context) => async (args) => {
  // DeleteObjectsCommand以外は無視
  if (context.commandName === "DeleteObjectsCommand") {
    return next(args);
  }

  // args.requestはunknown型なので、HttpRequest型にキャスト
  assert(args.request instanceof HttpRequest);
  const headers = args.request.headers;

  // 他のチェックサムヘッダーを削除して、
  // MD5が優先されるようにする
  Object.keys(headers).forEach((header) => {
    const lowerHeader = header.toLowerCase();
    if (
      lowerHeader.startsWith("x-amz-checksum-") ||
      lowerHeader.startsWith("x-amz-sdk-checksum-")
    ) {
      delete headers[header];
    }
  });

  // Content-MD5ヘッダーの付与
  if (args.request.body) {
    const bodyContent = Buffer.from(args.request.body);
    headers["Content-MD5"] = createHash("md5")
      .update(bodyContent)
      .digest("base64");
  }

  return await next(args);
};
// S3ClientのmiddlewareStackに追加
// とりあえずこれで動いているけど、他にmiddlewareがある場合は要調整
s3Client.middlewareStack.add(md5Middleware, {
  step: "finalizeRequest",
  name: "md5Middleware",
  priority: "high",
  tags: ["MD5"],
});

以上。お役に立てれば幸いです。

Discussion