メインコンテンツへ移動
AnyStorage
無料ダウンロード

S3 エラーコード

MinIO の SignatureDoesNotMatch:原因と対処

MinIO の SignatureDoesNotMatch には現実的な原因が七つあります。シークレット、時刻のずれ、リージョン、アドレッシング、リバースプロキシ、キーのエンコード、署名済み URL の改変。

MinIO が SignatureDoesNotMatch で何を言っているのか、混同されやすい隣のエラーとの見分け方、そして七つの原因それぞれの確認手順。

signaturedoesnotmatch minio、MinIO 署名 エラー、MinIO リージョン 不一致

返ってくる本文をそのまま

xml

<Error>
  <Code>SignatureDoesNotMatch</Code>
  <Message>The request signature we calculated does not match the signature you provided. Check your key and signing method.</Message>
</Error>

この Message は要約ではなく、MinIO のサーバーがこのコードに対して持っている文字列そのままです。HTTP ステータスは 403 で返ります。SDK ごとに botocore.exceptions.ClientErrorS3Error、ログ上の素の 403 と姿は変わりますが、サーバーが言っている内容は全部同じです。

意味は狭く、そのぶん役に立ちます。アクセスキー ID は見つかっていて、リクエストの解析も済んでいて、そのうえで MinIO が受け取った内容から Signature Version 4 を再計算したら、送られてきた署名と一致しなかった。つまり問うべきは「パスワードが違うのか」ではなく「自分が送っていない何をサーバーは見たのか」です。それを変えてしまう要因が七つあります。

症状・原因・対処
観察できること原因対処
どのクライアントからも即座に失敗するシークレットキーが違う、末尾に空白や改行が混ざっている改行のないファイルから再設定する
ある端末では通り、別の端末では落ちる時刻のずれが 15 分を超えている時刻同期。運が良ければ RequestTimeTooSkewed が出る
us-east-1 なら通るが自分のリージョンだと落ちる認証スコープのリージョンがサーバーと違うMINIO_SITE_REGION に合わせる
GET は通るが PUT が落ちる/ホスト名にバケットを入れると落ちるパススタイルのサーバーに仮想ホスト形式で接続しているpath を強制する
9000 番へ直結なら通り、nginx 経由で落ちるプロキシが Host などの署名対象ヘッダーを書き換えているHost をそのまま通す
空白や +、非 ASCII を含むキーだけ落ちる経路でパスが再エンコードされているプロキシ側でパスを触らせない
署名済み URL を編集した直後から落ちる署名後の変更はすべて無効作り直す。クエリを足さない

1. キー ID ではなくシークレットを疑う

キー ID が違うときには別のエラーがあります。InvalidAccessKeyId、説明文は「The Access Key Id you provided does not exist in our records.」です。SignatureDoesNotMatch が出ているなら ID は一致しています。壊れているのは残り半分か、署名の作り方です。

たいていはコピー&ペースト事故です。改行、末尾の空白、シェルに食われた $。エイリアスを明示的に張って一回だけ叩きます。

sh

mc alias set local http://127.0.0.1:9000 "$MINIO_ROOT_USER" "$MINIO_ROOT_PASSWORD"
mc ls local/

MINIO_ROOT_USERMINIO_ROOT_PASSWORD が MinIO の初期管理者資格情報を読む環境変数名で、ファイルから読む MINIO_ROOT_USER_FILE / MINIO_ROOT_PASSWORD_FILE もあります。シェルのクォート問題が消えるので、後者の方が習慣としては安全です。

2. ほかを触る前に時計を見る

SigV4 は署名対象にタイムスタンプを含むので、時計が狂えば署名も狂います。MinIO が許す幅は固定で、ソースに globalMaxSkewTime = 15 * time.Minute // 15 minutes skew allowed. と書かれています。ソフトウェア要件のチェックリストでも「All servers in a distributed deployment must have clocks synchronized to within 15 minutes of each other」とされ、手段として ntptimedatectltimesyncd が挙げられています。

sh

date -u                          # クライアント側
timedatectl status | head -5     # サーバー側

MinIO 自身がずれを検出できたときは RequestTimeTooSkewed(「The difference between the request time and the server's time is too large.」)が返ります。こちらが出れば話は早いのですが、時計の止まったコンテナやスリープ復帰直後の端末は 15 分の内側に収まったまま署名だけ落ちることもあります。

3. リージョンを合わせる、もしくは送らない

リージョンは SigV4 の認証スコープの一部なので、us-east-1ap-northeast-1 では同じリクエストでも署名が変わります。現行の設定は MINIO_SITE_REGIONmc admin config set ALIAS site region="us-east-1")で、MINIO_REGIONMINIO_REGION_NAME はソース上も legacy 扱いです。

MinIO が判定できる場合は専用のエラーが返ります。AuthorizationHeaderMalformed、「The authorization header is malformed; the region is wrong; expecting 'us-east-1'.」。期待値がメッセージに書いてあるので、そのままクライアントへ設定します。

4. パススタイルと仮想ホスト形式

素の MinIO は http://host:9000/bucket/key を返します。バケットをホスト名の一部として扱うのは MINIO_DOMAIN を設定したときだけで、この変数はソース上で仮想ホストのドメイン一覧として解析されています。設定のないサーバーに仮想ホスト形式のクライアントを向けると、クライアントが署名した host と MinIO が期待する host が食い違います。

AWS CLI 側は pathvirtualauto の三択で、ドキュメントに「The default value in the CLI is to use auto, which will attempt to use virtual where possible, but will fall back to path style if necessary.」とあります。

sh

aws configure set default.s3.addressing_style path
aws --endpoint-url http://127.0.0.1:9000 s3 ls s3://my-bucket/

5. 犯人はだいたいリバースプロキシ

host は署名対象のヘッダーです。プロキシがこれを localhost や上流名、別ポートに書き換えると、クライアントが署名し終えた後に署名対象が変わるので、MinIO の計算結果は当然ずれます。nginx なら proxy_set_header Host $http_host; が要点で、上流名で置き換えるのではなく元のヘッダーを転送します。

MinIO のロードバランシング指針からはもう二行。アップロードサイズ無制限のための client_max_body_size 0; と、proxy_request_buffering off; / proxy_buffering off; です。バッファリング自体は署名を壊しませんが、大きい転送を同じ日に壊して署名のせいにされます。

空白や + を含むキーだけ落ちるのも同類です。経路でパスが正規化・再デコードされれば canonical request が一致しません。ASCII だけのキーで試して、そちらだけ通るならパスは途中で書き換えられています。

6. 署名済み URL は「凍結」されている

署名済み URL には X-Amz-SignedHeaders が含まれ、メソッド・パス・クエリ・期限に対して署名されています。パラメータの追加、http から https への変更、ポート変更、パスの再エンコードはすべて無効化です。署名した資格情報をローテーションした場合も同じです。

期限切れは別のメッセージで、コードは AccessDenied、説明は「Request has expired」です。つまり署名済み URL で SignatureDoesNotMatch が出たら、URL 自体が書き換えられています。短縮サービス、メールクライアント、計測パラメータを足す CDN が定番です。

隣のエラーを読み分ける

混同されやすいエラーと MinIO が送る説明文
コードMinIO が返す説明文実際に分かること
InvalidAccessKeyIdThe Access Key Id you provided does not exist in our records.キー ID か接続先が違う
RequestTimeTooSkewedThe difference between the request time and the server's time is too large.資格情報ではなく時計
AuthorizationHeaderMalformedThe authorization header is malformed; the region is wrong; expecting 'us-east-1'.リージョン不一致。答えつき
InvalidRegionRegion does not match.同系、より簡潔
XAmzContentSHA256MismatchThe provided 'x-amz-content-sha256' header does not match what was computed.本文が経路で変わった
AccessDeniedRequest has expired署名済み URL の期限切れ

署名計算を自分でやらない選択

SigV4 の計算はクライアントに任せてよい作業です。AnyStorage(v0.2.24、macOS 11 以降 / Windows 10 以降 / Ubuntu 20.04 以降)は MinIO を含む S3 互換エンドポイントに接続でき、エンドポイント URL、アクセスキー、シークレットキーに加えて、パススタイル指定とカスタムリージョンを明示的な設定として持ちます。上の原因 3 と 4 はまさにこの二つのスイッチの話です。無料プランの接続数は 2 つ。マウントは内蔵のローカル WebDAV サーバー(http://127.0.0.1:3211)経由なので、FUSE も WinFsp も macFUSE も介在しません。

curl だと通るのに SDK だと落ちるのはなぜ?

署名している対象が違うからです。SDK は x-amz-content-sha256、チェックサム、コンテンツタイプなどを足し、それらは SignedHeaders に載りえます。SDK のデバッグログを出して、手で署名した canonical request と比べてください。

MinIO のログに何も出ません。どこを見ますか?

レスポンス本文がログです。SDK の例外文ではなく生の XML を残してください。そこにあるコードが、例外文では一つに潰れている六種類の失敗を区別してくれます。

バケットポリシーの間違いでもこれが出ますか?

出ません。認可の失敗は AccessDenied(説明文は「Access Denied.」)で返ります。署名検証はポリシー評価より前です。

us-east-1 は特別な値ですか?

慣習にすぎません。MinIO のメッセージがこの値を挙げるのは既定として多いからで、Cloudflare R2 も空値と us-east-1auto の別名として文書化しています。両側で揃っていれば他のリージョンでも構いません。

次に読む