🛠️
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