概要

AWS 上で稼働するアプリケーションでは、認証情報や API キーなどの秘密情報を環境変数で渡すケースが多くあります。
しかし、環境変数を /etc/environment や .env に書き込むと、ファイル経由で漏洩するリスクがあります。
本記事では、EC2 タグに基づいて Parameter Store の値を安全にオンメモリで環境変数として展開する仕組みを紹介します。
SSH ログイン時にも自動で読み込まれるため、運用の手間を減らしつつセキュリティを高められます。

実際の現場で直面した課題

・機密情報を /etc/environment や .env に書くと漏洩リスクがある
・環境ごと(stg / prd)の変数をインスタンスごとに管理したい
・SSH ログイン時にも同じ環境変数を使いたい

解決アプローチ

これらの課題を解決するために、以下の構成を採用します。

・systemd service:
起動時に Parameter Store を問い合わせ、環境変数の export 文を生成

・/etc/profile.d/:
SSH ログイン時に上記スクリプトを実行し、環境変数をオンメモリで展開

・Parameter Store:
環境変数は AWS の Parameter Store に保存し、KMS で暗号化して管理

この方法により、秘密情報はファイルに保存されず、プロセスのメモリ上だけに存在します。

手順

1.Parameter Store に環境変数を作成

まず、AWS Systems Manager の Parameter Store に、環境ごとの機密情報や設定値を作成します。
必要に応じて KMS で暗号化します。

2. systemd unit を作成

・/etc/systemd/system/load-env-params.service

[Unit]
Description=Load environment parameters from SSM Parameter Store at boot based on EC2 tags (no file output)
After=network-online.target

[Service]
Type=oneshot
ExecStart=/usr/local/bin/load-env-params.sh
StandardOutput=null
StandardError=null
RemainAfterExit=yes

User=root
Group=root

[Install]
WantedBy=multi-user.target

ポイント:

・StandardOutput=null / StandardError=null により 機密情報が systemd ジャーナルに残らない
・Type=oneshot で起動時 1 回だけ実行される

3. 環境変数ロードスクリプト

・/usr/local/bin/load-env-params.sh

#!/bin/bash
#
# EC2タグに基づいてSSMパラメータを環境変数として出力する
# ※ ファイルには保存せずオンメモリで展開
#

set -e
set -o pipefail

export PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"

TAG_KEY_EC2="env"         # EC2インスタンスの環境タグのキー
TAG_KEY_SSM="Environment" # Parameter Store の環境タグのキー

JQ_CMD="/bin/jq"
AWS_CMD="/bin/aws"
CURL_CMD="/usr/bin/curl"

# --- IMDSv2 からリージョンとインスタンスIDを取得 ---
TOKEN=$("${CURL_CMD}" -s -X PUT "http://169.254.169.254/latest/api/token" \
  -H "X-aws-ec2-metadata-token-ttl-seconds: 21600")

AWS_REGION=$("${CURL_CMD}" -s -H "X-aws-ec2-metadata-token: ${TOKEN}" \
  http://169.254.169.254/latest/dynamic/instance-identity/document | ${JQ_CMD} -r .region)

INSTANCE_ID=$("${CURL_CMD}" -s -H "X-aws-ec2-metadata-token: ${TOKEN}" \
  http://169.254.169.254/latest/meta-data/instance-id)

# --- EC2 タグから環境判定 (prd / stg のみ) ---
EC2_TAG_VALUE_RAW=$("${AWS_CMD}" ec2 describe-tags \
  --filters "Name=resource-id,Values=${INSTANCE_ID}" "Name=key,Values=${TAG_KEY_EC2}" \
  --query "Tags[0].Value" --region "${AWS_REGION}")

EC2_TAG_VALUE=$("${JQ_CMD}" -r . <<< "${EC2_TAG_VALUE_RAW}")

case "${EC2_TAG_VALUE}" in
  master|api|backend|frontend|batch) TARGET_ENV="prd" ;;
  stg) TARGET_ENV="stg" ;;
  *) exit 1 ;;
esac

# --- タグに一致する Parameter を取得 ---
PARAM_ARNS_JSON=$("${AWS_CMD}" resourcegroupstaggingapi get-resources \
  --resource-type-filters "ssm:parameter" \
  --tag-filters "Key=${TAG_KEY_SSM},Values=${TARGET_ENV}" \
  --query "ResourceTagMappingList[].ResourceARN" --region "${AWS_REGION}")

[ "${PARAM_ARNS_JSON}" != "[]" ] || exit 0

PARAM_NAMES_LIST=$(echo "${PARAM_ARNS_JSON}" | ${JQ_CMD} -r \
  '.[] | sub("arn:aws:ssm:[^:]+:[^:]+:parameter"; "") | @sh' | xargs)

read -r -a PARAM_NAMES_ARRAY <<< "${PARAM_NAMES_LIST}"

# --- 複数パラメータを取得 (10個ずつ) ---
PARAM_VALUES_JSON="[]"
for ((i=0; i<${#PARAM_NAMES_ARRAY[@]}; i+=10)); do
  CHUNK=( "${PARAM_NAMES_ARRAY[@]:i:10}" )
  CHUNK_JSON=$("${AWS_CMD}" ssm get-parameters \
    --names "${CHUNK[@]}" --with-decryption \
    --query "Parameters" --region "${AWS_REGION}")
  PARAM_VALUES_JSON=$(echo "${PARAM_VALUES_JSON}" "${CHUNK_JSON}" | ${JQ_CMD} -s 'add')
done

# --- export KEY=VALUE 形式で出力 ---
${JQ_CMD} -r '
  .[] |
  ($.Name | split("/")[-1]) as $raw_key |
  ($raw_key | split("-")[2:] | join("_") | ascii_upcase) as $clean_key |
  "export " + $clean_key + "=" + (.Value | @sh)
' <<< "${PARAM_VALUES_JSON}"

ポイント:

標準出力でのみ export 文を返す → ログに機密情報を残さない
AWS CLI の制限に合わせ、10 個ずつに分割して複数回呼び出す → 大量パラメータも安全に取得可能
EC2 タグで対象環境を自動判定

4. SSH ログイン時に読み込み

/etc/profile.d/load-env-vars.sh

# SSH ログイン時に EC2 タグに応じた環境変数を自動読み込み
if [ -x "/usr/local/bin/load-env-params.sh" ]; then
  source <("/usr/local/bin/load-env-params.sh" 2>/dev/null)
fi

ポイント:

SSHログイン時に毎回スクリプト実行
標準エラーを /dev/null に流す → 認証情報がログに残らない
環境変数はオンメモリのみ

反映手順

1.load-env-params.sh スクリプトに 実行権限 を追加

sudo chmod +x /usr/local/bin/load-env-params.sh

2.ユニットファイル作成後、デーモンをリロード

sudo systemctl daemon-reload

3.ユニットを有効化(起動時に自動実行)

sudo systemctl enable load-env-params.service

4.ユニットを実行

sudo systemctl start load-env-params.service

5.ステータス確認

sudo systemctl status load-env-params.service

※IAMロールの確認

EC2インスタンスに、SSMパラメータストアへの読み取り権限(例: ssm:GetParameter)を持つIAMロールをアタッチする必要があります。

動作確認

EC2 に SSH ログインして環境変数を確認します。

$ ssh ec2-user@<instance>
$ echo $PROD_TEST_SECRET
prod-hogehoge
$

※ 値はインスタンス内のファイルには保存されず、ログインセッション内の環境変数としてのみ存在します。

セキュリティ上のポイント

・ファイルには一切保存しない
 環境変数は標準出力で export 文として返すだけ。漏洩リスクを低減。

・プロセス/シェルのメモリ内だけに存在
 systemd や SSH ログインで展開された変数は、セッション内でのみ有効。
 セッション終了時には自動的に消えるため、再起動は不要。

・ログに機密情報が残らない
 systemd unit では StandardOutput=null / StandardError=null を指定。
 SSH時も /dev/null に標準エラーを破棄。

・Parameter Store 側で KMS 暗号化を推奨
 AWS 側でも保護され、より安全に運用可能。

まとめ

本記事では、EC2 タグに基づいて Parameter Store の値を安全に環境変数として展開する方法を紹介しました。
systemd と profile.d を組み合わせることで、インスタンス起動時と SSH ログイン時の両方で自動的に環境変数が設定され、ファイルに秘密情報を残さずにオンメモリで扱うことができます。
この仕組みにより、機密情報の漏洩リスクを低く保ちながら運用でき、運用の手間も軽減できます。
「ファイルに書かずに扱う」設計はシンプルですが、セキュアで再現性のある方法としておすすめです。