見出し画像

【IT】個人開発DjangoをHerokuで本番運用へ(第2回(AWS編②):アップロード専用IAM/疎通テスト/Heroku連携

皆さま、こんにちは

前回に引き続き、
個人開発したDjangoアプリをHerokuで本番運用に向けて環境構築していきます。

ゴール
S3 に書き込みだけできる 最小権限の IAM ユーザーを用意(削除権限なし
Heroku の環境変数に アクセスキーを安全に登録
・boto3
単発アップロードマルチパートアップロードの疎通を確認
運用の鍵ローテーショントラブル時の切り分け手順を持つ

前提(第1回の設計)
バケット:汎用 / ACL 無効 / バージョニング無効 / SSE-S3(AES256)
ライフサイクル:1 ルール(`<prefix>/` フィルタ)で 5日削除 + 未完了MPU 7日中止
“落穂拾い”Lambda:7日超の完成済みオブジェクトを削除(削除権限は Lambda のみ


連載ナビ(5回構成+序章)

※連載回数や内容を変更することがございます。予めご了承ください。




Step 1:アップロード専用 IAM ユーザーを作る(削除なし・最小権限)

1-1. 設計方針(再確認)

  • アップロード担当(Heroku側)Put だけ。Delete 不可

  • 削除担当(Lambda):第1回で作成済み。Delete 権限は Lambda ロールのみに付与

  • これで誤削除リスクを IAM レベルで遮断します。

1-2. 付与するアクション(最小限)

アップロード(特に マルチパートアップロード)には Put 以外にいくつかの補助アクションが必要です。

  • `s3:PutObject`(必須)

  • `s3:AbortMultipartUpload`(MPU途中の中止権限)

  • `s3:ListBucketMultipartUploads`(バケット側の“進行中MPUの一覧”参照)

  • `s3:ListMultipartUploadParts`(オブジェクト側の“MPUの部品一覧”参照)

  • `s3:ListBucket`(prefix 限定で直近アップロードの確認に使用/任意)
    Get は不要(読み出しは不要なので付けない)

1-3. ポリシー(バケット+特定プレフィックスに限定)

置換:`<YOUR_S3_BUCKET>`, `<YOUR_PREFIX>`(例:`heroku/<APP_NAME>`)

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "ListBucketLimitedToPrefix",
      "Effect": "Allow",
      "Action": ["s3:ListBucket", "s3:ListBucketMultipartUploads"],
      "Resource": "arn:aws:s3:::<YOUR_S3_BUCKET>",
      "Condition": {
        "StringLike": { "s3:prefix": ["<YOUR_PREFIX>/*"] }
      }
    },
    {
      "Sid": "PutAndMultipartOnlyWithinPrefix",
      "Effect": "Allow",
      "Action": [
        "s3:PutObject",
        "s3:AbortMultipartUpload",
        "s3:ListMultipartUploadParts"
      ],
      "Resource": "arn:aws:s3:::<YOUR_S3_BUCKET>/<YOUR_PREFIX>/*"
    }
  ]
}

🔒 付けないもの:`s3:DeleteObject` / `s3:GetObject` / `s3:PutObjectAcl` など。
※ バケットポリシーは不要(非公開のまま。IAM のみで制御)。

1-4. 作成手順(コンソール派)

  1. IAM > ポリシー > 作成 → 上記 JSON を貼付&保存(例:`S3PutOnly-<APP_NAME>`)。

  2. IAM > ユーザー > ユーザーの作成

    • ユーザー名:`heroku-uploader-<APP_NAME>`

    • アクセスタイプ:アクセスキー - プログラムによるアクセス

    • 次へ → 「ポリシーを直接アタッチ」にて 1 のポリシーを選択 → 作成。

  3. ユーザー詳細画面で アクセスキーを作成CSV を安全な場所に保管

1-5. 作成手順(CLI派|任意)

# 事前変数
BUCKET="<YOUR_S3_BUCKET>"
PREFIX="<YOUR_PREFIX>"
APP="yourapp"

# ポリシーJSONを policy.json として保存後:
aws iam create-policy --policy-name S3PutOnly-$APP --policy-document file://policy.json

aws iam create-user --user-name heroku-uploader-$APP
aws iam attach-user-policy \
  --user-name heroku-uploader-$APP \
  --policy-arn arn:aws:iam::<ACCOUNT_ID>:policy/S3PutOnly-$APP

aws iam create-access-key --user-name heroku-uploader-$APP
# => AccessKeyId / SecretAccessKey を控える(CSV推奨)

Step 2:Heroku へ環境変数を登録(Config Vars)

Heroku Dashboard → Settings → Reveal Config Vars で以下をセット。

引用符は不要。`AES256` などはそのまま入力。
✅ 値の変更はデプロイなしで即反映(dyno が再起動されることはあります)。


Step 3:疎通テスト(単発アップロード → マルチパートアップロード)

第1回のバックアップスクリプト(`scripts/s3_backup.sh`)を動かす前に、まずは最小確認を行います。

3-1. 単発アップロード(`PutObject` のみを検証)

Heroku の one-off dyno で1行テスト

heroku run --app <yourapp> -- python - <<'PY'
import os, boto3, datetime
s3 = boto3.client("s3", region_name=os.getenv("AWS_DEFAULT_REGION"))
bucket = os.getenv("S3_BUCKET"); prefix = os.getenv("S3_PREFIX")
key = f"{prefix}/probes/putobject-{datetime.datetime.utcnow().isoformat()}.txt"
s3.put_object(Bucket=bucket, Key=key, Body=b"hello", ServerSideEncryption=os.getenv("S3_SSE","AES256"))
print("OK:", key)
exit()
PY
  • 成功 → S3 に `.../probes/putobject-*.txt` が出来る。

  • 失敗 → 環境変数/権限/リージョンのいずれかが原因。

3-2. マルチパートアップロード(MPU 系権限を検証)

5MB 超なら boto3 の `S3Transfer` が MPU を使用します。強制するテスト:

heroku run --app <yourapp> -- python - <<'PY'
import os, io, boto3, datetime
from boto3.s3.transfer import TransferConfig
size = 8 * 1024 * 1024  # 8MB
body = io.BytesIO(b"x" * size)
cfg = TransferConfig(multipart_threshold=5*1024*1024, multipart_chunksize=5*1024*1024)
s3 = boto3.client("s3", region_name=os.getenv("AWS_DEFAULT_REGION"))
bucket = os.getenv("S3_BUCKET"); prefix=os.getenv("S3_PREFIX")
key = f"{prefix}/probes/mpu-{datetime.datetime.utcnow().isoformat()}.bin"
s3.upload_fileobj(body, bucket, key, ExtraArgs={"ServerSideEncryption": os.getenv("S3_SSE","AES256")}, Config=cfg)
print("OK (multipart):", key)
exit()
PY
  • 成功 → `PutObject` / `ListMultipartUploadParts` / `ListBucketMultipartUploads` が機能。

  • 途中で中断しても、ライフサイクルの“未完了MPU 7日中止”が後始末。

🧹 テストファイルの扱い
削除は Lambda の役割なので、アップロード用 IAM には権限がありません。probes/ 配下は少量のまま放置でOK**(5日で自動削除)**。気になる場合は S3 コンソールで手動削除


Step 4:バックアップスクリプトの“本番一歩手前”実行

ここで `scripts/s3_backup.sh` を新規作成し、Heroku の One-off dyno から動作確認します。

4-1. ファイル作成:`scripts/s3_backup.sh`

# scripts/s3_backup.sh
#!/usr/bin/env bash
set -euo pipefail

export PATH="/app/.heroku/python/bin:/app/.apt/usr/bin:$PATH"
for d in /app/.apt/usr/lib/postgresql/*/bin; do
  [ -d "$d" ] && export PATH="$d:$PATH"
done


APP_NAME="mailsession"                      # ログ表示用
BUCKET="${S3_BUCKET:?S3_BUCKET missing}"
PREFIX="${S3_PREFIX:-heroku/${APP_NAME}}"
SSE="${S3_SSE:-AES256}"
KMS_KEY_ID="${S3_KMS_KEY_ID:-}"

STAMP="$(date -u +%Y%m%d-%H%M%S)"
OUT="/tmp/${APP_NAME}-${STAMP}.dump"

export PGSSLMODE=require  # Heroku PG は TLS 前提

echo "[backup] Start pg_dump to ${OUT}"
pg_dump -Fc --no-acl --no-owner "$DATABASE_URL" -f "$OUT"

S3_URI="s3://${BUCKET}/${PREFIX}/daily-${APP_NAME}-${STAMP}.dump"
echo "[backup] Upload to ${S3_URI}"

if [[ "$SSE" == "aws:kms" && -n "$KMS_KEY_ID" ]]; then
  aws s3 cp "$OUT" "$S3_URI" --sse aws:kms --sse-kms-key-id "$KMS_KEY_ID"
else
  aws s3 cp "$OUT" "$S3_URI" --sse AES256
fi

rm -f "$OUT"
echo "[backup] Done."

4-2. `requirements.txt` に追記

boto3

4-3. One-off dyno で実行

# まず push(または GitHub 連携で自動デプロイ後)
git add scripts/s3_backup.sh requirements.txt
git commit -m "Add pg_dump_to_s3 script"
git push heroku main

# 実行
heroku run --app <yourapp> -- python scripts/s3_backup.sh

4-4. うまくいかない時の主な原因

  • `Unable to locate credentials` → `AWS_*` が未設定/スペルミス。

  • `AccessDenied` → IAM ポリシーの範囲(バケット/プレフィックス)がズレている。

  • `Invalid SSE` → `S3_SSE` が `AES256` 以外(本連載は KMS なし前提)。

  • `pg_dump` 失敗 → `DATABASE_URL` / Postgres アドオンの到達性。

  • S3 キー場所違い → `S3_PREFIX` の末尾スラッシュ抜け等(想定は `.../<APP_NAME>/daily-...`)。


Step 5:鍵ローテーション & 運用の小ワザ

5-1. ローテーション

  1. 新しいアクセスキーを発行(IAM → 当該ユーザー → アクセスキーを作成)。

  2. Heroku の Config Vars を新値に更新(旧値は保持したままでもよい)。

  3. One-off で疎通確認(Step 3 の 1&2 を再実施)。

  4. 問題なければ 旧アクセスキーを無効化 → 削除

5-2. 事故防止の工夫

  • 権限分離:Delete は Lambda のみ(本稿の前提)。

  • Prefix の固定:`S3_PREFIX=heroku/<APP_NAME>` のようにアプリ毎に分離

  • 不要な Get を付与しない:読み出し権限は付けない。

  • ログの確認:アップロード試験・本番実行はCloudWatch(Lambda)とS3 のオブジェクト一覧で確認。

  • Heroku 変数の“見える化”:`/admin/` を持つなら管理者ページに「現在の S3 先」を read-only 表示するのも有効。


よくある質問(Q&A)

Q. Heroku から AWS に “IP 制限” を掛けたい
A. Heroku の送信元 IP は安定しません。IAM ポリシーの `aws:SourceIp` 条件は実務上つらいです。最小権限化 + プレフィックス限定 + 短期鍵ローテーションでリスクを抑えるのが現実解です。

Q. `awscli` を入れて `aws s3 cp` を使いたい
A. Heroku で apt による `awscli` 導入は失敗しやすい不要boto3 で完結しましょう。

Q. KMS(SSE-KMS)を使いたい
A. 本連載の範囲外(将来回)。KMS を使う場合は `SSEKMSKeyId``kms:Encrypt` 等の権限・キー ポリシー設計が必要です。

Q. Heroku Scheduler での“毎日バックアップ”は?
A. 第4回で詳述します。現時点は One-off での疎通が通ることがゴール。


この回の成果物(テンプレまとめ)

  • IAM ポリシー:`S3PutOnly-<APP_NAME>`(上記 JSON)

  • IAM ユーザー:`heroku-uploader-<APP_NAME>`(アクセスキー発行)

  • Heroku Config Vars:`AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY / AWS_DEFAULT_REGION / S3_BUCKET / S3_PREFIX / S3_SSE`

  • 疎通テスト:単発(`put_object`)+ MPU(`upload_fileobj` + `TransferConfig`)


次回予告(第3回:Heroku 編①)

  • Heroku アプリの Config Vars 整理/Release/静的ファイル(Whitenoise) の最小構成を再点検

  • `DEBUG=false` / `ALLOWED_HOSTS` / `CSRF_TRUSTED_ORIGINS` の本番向け落とし穴

  • 管理者の最初の `migrate` / `createsuperuser` を“正しいフラグ順”で


では
次回に!


前回の記事

過去の記事



いいなと思ったら応援しよう!