[AWS]ACMのエクスポート証明書によるSSLサーバ証明書自動更新
AWS ACMのエクスポートパブリック証明書をAWS以外の外部WEBサーバで自動更新します。
1. SSLサーバ証明書の構成
(1) 仕様
| 対象ドメイン名 |
*.example.jp / example.jp(1枚のSAN証明書として発行) |
| 認証種別 |
DV認証 |
| 認証方式 |
DNS認証 |
(2) 証明書ファイル構成
| ファイル名 |
用途 |
| /etc/httpd/certs/example.jp/acm/example.jp.privkey_nopwd.pem |
秘密鍵(パスフレーズなし) |
| /etc/httpd/certs/example.jp/acm/example.jp.inca.pem |
中間CA証明書 |
| /etc/httpd/certs/example.jp/acm/example.jp.cert.pem |
サーバ証明書 |
| /etc/httpd/certs/example.jp/acm/example.jp.full_cert.pem |
サーバ証明書 + 中間CA証明書 |
2. AWSコンソールでの作業
(1) AWS Certificate Manager (ACM)でのSSLサーバ証明書のリクエスト
- 「Certificate Manager」に移動します。
- 「証明書をリクエスト」をクリックします。
[a] 証明書をリクエスト
- 「パブリック証明書をリクエスト」を選択して「次へ」をクリックします。
[b] パブリック証明書をリクエスト
必ずエクスポートの許可を有効化します。
| ドメイン名 |
*.example.jp |
| example.jp |
| エクスポートを許可 |
エクスポートを有効にする |
| 検証方法 |
DNS 検証 |
| キーアルゴリズム |
RSA 2048 |
[c] ドメインの検証
ドメインの検証を行います。
■ Route 53の場合
- 「Route 53 でレコードを作成」をクリックします。
- 「Amazon Route 53 で DNS レコードを作成」画面で「レコードを作成」をクリックします。
■ その他のDNSの場合
- 「CSV にエクスポート」をクリックします。
- ダウンロードした「DNS_設定.csv」の内容を該当のDNSに設定します。
ステータスが「発行済み」になればOKです。
(2) IAMポリシーの作成
対象証明書のDescribeとExportのみ許可する最小権限のポリシーを作成します。
- 「IAM」に移動します。
- ローカルメニューから「ポリシー」をクリックします。
- 「ポリシーの作成」をクリックします。
- 「ポリシーエディタ」を「JSON」にします。
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "ACMExportSpecificCertificate",
"Effect": "Allow",
"Action": [
"acm:DescribeCertificate",
"acm:ExportCertificate"
],
"Resource": "arn:aws:acm:ap-northeast-1:123456789012:certificate/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
}
]
}
「Resource」にACM証明書の「ARN」を差し替えて下さい。
| ポリシー名 |
ACMExport_example.jp_Policy |
| 説明 – オプション |
ACM Exportable Certificate Policy |
「ポリシー名」のドメイン名は実際のドメインに差し替えて下さい。
(3) IAMユーザーの作成
- ローカルメニューから「IAM ユーザー」をクリックします。
- 「ユーザーの作成」をクリックします。
| ユーザー名 |
acm-export-user_example.jp |
「ユーザー名」のドメイン名は実際のドメインに差し替えて下さい。
- 「次へ」をクリックします。
- 「ポリシーを直接アタッチする」を選択します。
- 「ACMExport_example.jp_Policy」にチェックを付けます。
- 「次へ」をクリックします。
- 「ユーザーの作成」をクリックします。
- 作成したユーザー名をクリックします。
- 「セキュリティ認証情報」-「アクセスキー」で「アクセスキーを作成」をクリックします。
|
コマンドラインインターフェイス (CLI) |
|
上記のレコメンデーションを理解し、アクセスキーを作成します。 |
- 「次へ」をクリックします。
- 「アクセスキーを作成」をクリックします。
- 「.csv ファイルをダウンロード」をクリックして、アクセスキー情報をダウンロードします。
2. AWS CLI v2の導入
(1) インストール
AWS CLI v2のインストールを行います。
# cd /usr/local/src
# curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
# unzip awscliv2.zip
# ./aws/install
# /usr/local/bin/aws --version
(2) プロファイルの作成
ACMのSSLサーバ証明書をエクスポートするプロファイルを作成します。
| プロファイル名 |
acm-export_example.jp |
ドメイン名は実際のドメイン名を差し替えて下さい。
# aws configure --profile acm-export_example.jp
「プロファイル名」のドメイン名は実際のドメインに差し替えて下さい。
ASW Access Key ID: (アクセスキー)
ASW Secret Access Key: (シークレットアクセスキー)
Default region name:ap-northeast-1
Default output format:json
3. 証明書設置の準備作業
ここで記載している「exmaple.jp」は実際のドメイン名に差し替えて下さい。
(1) 証明書ディレクトリの作成
| 証明書ディレクトリ |
/etc/httpd/certs/exampel.jp/acm/ |
# mkdir -p /etc/httpd/certs/example.jp/acm
(2) ACMエクスポート用パスフレーズファイルの作成
| パスフレーズファイル名 |
/root/.acm/example.jp/.acm_export_passphrase |
# mkdir -p /root/.acm/example.jp
# openssl rand -base64 48 > /root/.acm/example.jp/.acm_export_passphrase
# chmod 600 /root/.acm/example.jp/.acm_export_passphrase
(3) jqコマンドのインストール
4. 初回エクスポートスクリプト
初回のACM証明書をエクスポートしてサーバに設置を行うスクリプトです。
# vi /root/.acm/example.jp/acm-export.sh
パス名のドメインは実際のドメイン名に書き換えて下さい。
#!/usr/bin/env bash
set -Eeuo pipefail
umask 077
############################################################
# 基本設定
############################################################
DOMAIN="${DOMAIN:-example.jp}"
AWS_REGION="${AWS_REGION:-ap-northeast-1}"
CERT_ARN="${CERT_ARN:-arn:aws:acm:ap-northeast-1:123456789012:certificate/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}"
# 証明書に含まれていることを確認するホスト名。
# 必要に応じて "www.example.jp" などを追加してください。
EXPECTED_HOSTS=(
"example.jp"
)
############################################################
# 証明書・作業ファイル
############################################################
CERT_DIR="${CERT_DIR:-/etc/httpd/certs/${DOMAIN}/acm}"
PASSPHRASE_FILE="${PASSPHRASE_FILE:-/root/.acm/${DOMAIN}/.acm_export_passphrase}"
WORK_BASE="${WORK_BASE:-/root/acm-export}"
PRIVKEY_FILE="${CERT_DIR}/${DOMAIN}.privkey.pem"
PRIVKEY_NOPWD_FILE="${CERT_DIR}/${DOMAIN}.privkey_nopwd.pem"
INCA_FILE="${CERT_DIR}/${DOMAIN}.inca.pem"
CERT_FILE="${CERT_DIR}/${DOMAIN}.cert.pem"
FULL_CERT_FILE="${CERT_DIR}/${DOMAIN}.full_cert.pem"
LOG_DIR="${LOG_DIR:-/var/log/acm}"
LOG_FILE="${LOG_FILE:-${LOG_DIR}/acm_export.log}"
SAFE_DOMAIN="${DOMAIN//[^[:alnum:]._-]/_}"
LOCK_FILE="${LOCK_FILE:-/run/lock/acm_export_${SAFE_DOMAIN}.lock}"
############################################################
# コマンド
############################################################
AWS_BIN="${AWS_BIN:-/usr/local/bin/aws}"
AWS_PROFILE="${AWS_PROFILE:-acm-export_${DOMAIN}}" # 未使用の場合は空文字にする
JQ="${JQ:-/usr/bin/jq}"
OPENSSL="${OPENSSL:-/usr/bin/openssl}"
APACHECTL="${APACHECTL:-/usr/sbin/apachectl}"
SYSTEMCTL="${SYSTEMCTL:-/usr/bin/systemctl}"
SHA256SUM="${SHA256SUM:-/usr/bin/sha256sum}"
FLOCK="${FLOCK:-/usr/bin/flock}"
STAT="${STAT:-/usr/bin/stat}"
CMP="${CMP:-/usr/bin/cmp}"
CA_BUNDLE="${CA_BUNDLE:-/etc/pki/tls/certs/ca-bundle.crt}"
RESTORECON="${RESTORECON:-/usr/sbin/restorecon}"
# 新しい証明書が24時間以内に期限切れになる場合は配置しない。
MIN_VALID_SECONDS="${MIN_VALID_SECONDS:-86400}"
############################################################
# 実行時変数
############################################################
TMP_DIR=""
BACKUP_DIR=""
ROLLBACK_REQUIRED=0
STAGE_SUFFIX=".new.$$"
TARGET_FILES=(
"${PRIVKEY_FILE}"
"${PRIVKEY_NOPWD_FILE}"
"${INCA_FILE}"
"${CERT_FILE}"
"${FULL_CERT_FILE}"
)
############################################################
# 共通関数
############################################################
log() {
local msg="$1"
local now
now="$(date '+%Y-%m-%d %H:%M:%S')"
if [[ -d "${LOG_DIR}" ]]; then
printf '[%s] %s\n' "${now}" "${msg}" | tee -a "${LOG_FILE}"
else
printf '[%s] %s\n' "${now}" "${msg}" >&2
fi
}
die() {
log "ERROR: $*"
exit 1
}
run_and_log() {
"$@" >>"${LOG_FILE}" 2>&1
}
aws_cmd() {
[[ -x "${AWS_BIN}" ]] || die "AWS CLIを実行できません: ${AWS_BIN}"
if [[ -n "${AWS_PROFILE}" ]]; then
"${AWS_BIN}" --profile "${AWS_PROFILE}" "$@"
else
"${AWS_BIN}" "$@"
fi
}
restore_selinux_contexts() {
if [[ -x "${RESTORECON}" ]]; then
"${RESTORECON}" -RF "${CERT_DIR}" >>"${LOG_FILE}" 2>&1 || \
log "WARNING: restoreconに失敗しました。SELinuxコンテキストを確認してください。"
fi
}
rollback_files() {
local target base
[[ -n "${BACKUP_DIR}" && -d "${BACKUP_DIR}" ]] || return 0
log "旧証明書ファイルへロールバックします..."
for target in "${TARGET_FILES[@]}"; do
base="$(basename -- "${target}")"
if [[ -e "${BACKUP_DIR}/${base}" ]]; then
cp -a -- "${BACKUP_DIR}/${base}" "${target}"
else
rm -f -- "${target}"
fi
done
restore_selinux_contexts
}
cleanup() {
local rc=$?
local target
set +e
if (( ROLLBACK_REQUIRED == 1 )); then
rollback_files
fi
for target in "${TARGET_FILES[@]}"; do
rm -f -- "${target}${STAGE_SUFFIX}"
done
if [[ -n "${TMP_DIR}" && -d "${TMP_DIR}" ]]; then
rm -rf -- "${TMP_DIR}"
fi
if (( rc != 0 )); then
log "ACM証明書エクスポート処理が異常終了しました(終了コード: ${rc})。"
fi
trap - EXIT
exit "${rc}"
}
trap cleanup EXIT
############################################################
# 事前チェック
############################################################
check_executable() {
local command_path="$1"
local command_name="$2"
[[ -x "${command_path}" ]] || die "${command_name}を実行できません: ${command_path}"
}
check_passphrase_file() {
local owner_uid mode_text mode_value size line_count
[[ -f "${PASSPHRASE_FILE}" ]] || \
die "パスフレーズファイルがありません: ${PASSPHRASE_FILE}"
owner_uid="$(${STAT} -c '%u' "${PASSPHRASE_FILE}")"
[[ "${owner_uid}" == "0" ]] || \
die "パスフレーズファイルの所有者をrootにしてください: ${PASSPHRASE_FILE}"
mode_text="$(${STAT} -c '%a' "${PASSPHRASE_FILE}")"
mode_value=$((8#${mode_text}))
(( (mode_value & 077) == 0 )) || \
die "パスフレーズファイルをroot以外から読めない権限にしてください(推奨: chmod 600): 現在 ${mode_text}"
size="$(${STAT} -c '%s' "${PASSPHRASE_FILE}")"
(( size >= 4 && size <= 128 )) || \
die "パスフレーズは4~128バイトにしてください: 現在 ${size} バイト"
# AWS公式仕様により、パスフレーズファイルは改行で終端してはいけない。
line_count="$(wc -l < "${PASSPHRASE_FILE}")"
(( line_count == 0 )) || \
die "パスフレーズファイルに改行が含まれています。printfで改行なしで作成してください。"
if LC_ALL=C grep -q $'\r' "${PASSPHRASE_FILE}"; then
die "パスフレーズファイルにCR文字が含まれています。"
fi
if LC_ALL=C grep -q '[^ -~]' "${PASSPHRASE_FILE}"; then
die "パスフレーズには印字可能なASCII文字だけを使用してください。"
fi
if grep -q '[#$%]' "${PASSPHRASE_FILE}"; then
die "パスフレーズにはAWS ACMで使用できない文字(#、$、%)が含まれています。"
fi
}
check_requirements() {
[[ "${EUID}" -eq 0 ]] || die "rootユーザーで実行してください。"
mkdir -p "${WORK_BASE}"
mkdir -p "${CERT_DIR}"
mkdir -p "${LOG_DIR}"
chmod 700 "${WORK_BASE}" "${CERT_DIR}"
chmod 750 "${LOG_DIR}"
touch "${LOG_FILE}"
chmod 600 "${LOG_FILE}"
check_executable "${AWS_BIN}" "AWS CLI"
check_executable "${JQ}" "jq"
check_executable "${OPENSSL}" "OpenSSL"
check_executable "${APACHECTL}" "apachectl"
check_executable "${SYSTEMCTL}" "systemctl"
check_executable "${SHA256SUM}" "sha256sum"
check_executable "${FLOCK}" "flock"
check_executable "${STAT}" "stat"
check_executable "${CMP}" "cmp"
[[ -r "${CA_BUNDLE}" ]] || die "CAバンドルを読み込めません: ${CA_BUNDLE}"
check_passphrase_file
# 同時実行を防止する。
exec 9>"${LOCK_FILE}"
"${FLOCK}" -n 9 || die "同じ証明書のエクスポート処理がすでに実行中です。"
"${SYSTEMCTL}" is-active --quiet httpd || \
die "httpdが起動していません。証明書配置前にhttpdの状態を確認してください。"
TMP_DIR="$(mktemp -d "${WORK_BASE}/tmp_${SAFE_DOMAIN}.XXXXXXXX")"
}
############################################################
# ACM証明書の確認とエクスポート
############################################################
check_acm_certificate() {
local metadata status cert_type export_option key_origin acm_domain
local metadata_file="${TMP_DIR}/describe.json"
local error_file="${TMP_DIR}/describe.err"
log "ACM証明書の状態を確認します..."
if ! aws_cmd acm describe-certificate \
--region "${AWS_REGION}" \
--certificate-arn "${CERT_ARN}" \
>"${metadata_file}" 2>"${error_file}"; then
while IFS= read -r line; do log "AWS CLI: ${line}"; done <"${error_file}"
die "ACM証明書情報の取得に失敗しました。"
fi
status="$(${JQ} -er '.Certificate.Status' "${metadata_file}")"
cert_type="$(${JQ} -er '.Certificate.Type' "${metadata_file}")"
export_option="$(${JQ} -r '.Certificate.Options.Export // ""' "${metadata_file}")"
key_origin="$(${JQ} -r '.Certificate.CertificateKeyPairOrigin // ""' "${metadata_file}")"
acm_domain="$(${JQ} -er '.Certificate.DomainName' "${metadata_file}")"
[[ "${status}" == "ISSUED" ]] || \
die "ACM証明書の状態がISSUEDではありません: ${status}"
[[ "${cert_type}" != "IMPORTED" ]] || \
die "インポート証明書の秘密鍵はACMから再エクスポートできません。"
[[ "${key_origin}" != "ACME" ]] || \
die "ACM ACMEで発行した証明書はExportCertificate APIの対象外です。"
if [[ "${cert_type}" == "AMAZON_ISSUED" && "${export_option}" != "ENABLED" ]]; then
die "ACMパブリック証明書でエクスポートが有効になっていません: Options.Export=${export_option:-未設定}"
fi
log "ACM証明書: DomainName=${acm_domain}, Type=${cert_type}, Status=${status}, Export=${export_option:-N/A}"
}
export_certificate() {
local export_file="${TMP_DIR}/export.json"
local error_file="${TMP_DIR}/export.err"
log "ACMから証明書をエクスポートします..."
if ! aws_cmd acm export-certificate \
--region "${AWS_REGION}" \
--certificate-arn "${CERT_ARN}" \
--passphrase "fileb://${PASSPHRASE_FILE}" \
>"${export_file}" 2>"${error_file}"; then
while IFS= read -r line; do log "AWS CLI: ${line}"; done <"${error_file}"
die "ACM証明書のエクスポートに失敗しました。"
fi
"${JQ}" -er '.Certificate | select(type == "string" and length > 0)' \
"${export_file}" >"${TMP_DIR}/${DOMAIN}.cert.pem" || \
die "エクスポート結果にCertificateがありません。"
"${JQ}" -er '.CertificateChain | select(type == "string" and length > 0)' \
"${export_file}" >"${TMP_DIR}/${DOMAIN}.inca.pem" || \
die "エクスポート結果にCertificateChainがありません。"
"${JQ}" -er '.PrivateKey | select(type == "string" and length > 0)' \
"${export_file}" >"${TMP_DIR}/${DOMAIN}.privkey.pem" || \
die "エクスポート結果にPrivateKeyがありません。"
cat "${TMP_DIR}/${DOMAIN}.cert.pem" \
"${TMP_DIR}/${DOMAIN}.inca.pem" \
>"${TMP_DIR}/${DOMAIN}.full_cert.pem"
chmod 600 "${TMP_DIR}"/*
}
############################################################
# PEMファイルの検証
############################################################
public_key_sha256_from_cert() {
local cert_path="$1"
local hash_line
if ! hash_line="$(
"${OPENSSL}" x509 -in "${cert_path}" -pubkey -noout \
| "${OPENSSL}" pkey -pubin -outform DER 2>/dev/null \
| "${SHA256SUM}"
)"; then
return 1
fi
[[ -n "${hash_line}" ]] || return 1
printf '%s\n' "${hash_line%% *}"
}
public_key_sha256_from_key() {
local key_path="$1"
local hash_line
if ! hash_line="$(
"${OPENSSL}" pkey -in "${key_path}" -pubout -outform DER 2>/dev/null \
| "${SHA256SUM}"
)"; then
return 1
fi
[[ -n "${hash_line}" ]] || return 1
printf '%s\n' "${hash_line%% *}"
}
validate_exported_files() {
local cert_tmp="${TMP_DIR}/${DOMAIN}.cert.pem"
local inca_tmp="${TMP_DIR}/${DOMAIN}.inca.pem"
local encrypted_key_tmp="${TMP_DIR}/${DOMAIN}.privkey.pem"
local nopwd_key_tmp="${TMP_DIR}/${DOMAIN}.privkey_nopwd.pem"
local cert_pub_sha key_pub_sha host
log "エクスポートした証明書と秘密鍵を検証します..."
"${OPENSSL}" x509 -in "${cert_tmp}" -noout >/dev/null 2>&1 || \
die "サーバ証明書をPEM形式として読み込めません。"
# 暗号化秘密鍵の復号には鍵種別に依存しないopenssl pkeyを使用する。
"${OPENSSL}" pkey \
-in "${encrypted_key_tmp}" \
-passin "file:${PASSPHRASE_FILE}" \
-out "${nopwd_key_tmp}" \
>/dev/null 2>>"${LOG_FILE}" || \
die "秘密鍵の復号に失敗しました。パスフレーズを確認してください。"
chmod 600 "${nopwd_key_tmp}"
"${OPENSSL}" pkey -in "${nopwd_key_tmp}" -check -noout \
>>"${LOG_FILE}" 2>&1 || die "秘密鍵の整合性チェックに失敗しました。"
if ! "${OPENSSL}" x509 -in "${cert_tmp}" -checkend "${MIN_VALID_SECONDS}" -noout \
>>"${LOG_FILE}" 2>&1; then
die "新しい証明書が${MIN_VALID_SECONDS}秒以内に期限切れになるため配置しません。"
fi
for host in "${EXPECTED_HOSTS[@]}"; do
[[ -n "${host}" ]] || continue
if ! "${OPENSSL}" x509 -in "${cert_tmp}" -checkhost "${host}" -noout \
>>"${LOG_FILE}" 2>&1; then
die "証明書のSAN/CNに想定ホスト名が含まれていません: ${host}"
fi
done
cert_pub_sha="$(public_key_sha256_from_cert "${cert_tmp}")"
key_pub_sha="$(public_key_sha256_from_key "${nopwd_key_tmp}")"
[[ -n "${cert_pub_sha}" && "${cert_pub_sha}" == "${key_pub_sha}" ]] || \
die "証明書と秘密鍵の公開鍵が一致しません。"
if ! "${OPENSSL}" verify \
-CAfile "${CA_BUNDLE}" \
-untrusted "${inca_tmp}" \
"${cert_tmp}" >>"${LOG_FILE}" 2>&1; then
die "証明書チェーンの検証に失敗しました。"
fi
while IFS= read -r line; do
log "証明書情報: ${line}"
done < <("${OPENSSL}" x509 -in "${cert_tmp}" -noout \
-subject -issuer -serial -dates -fingerprint -sha256)
}
certificate_is_unchanged() {
local cert_tmp="${TMP_DIR}/${DOMAIN}.cert.pem"
local inca_tmp="${TMP_DIR}/${DOMAIN}.inca.pem"
local full_tmp="${TMP_DIR}/${DOMAIN}.full_cert.pem"
local new_pub_sha current_key_sha
local target
for target in "${TARGET_FILES[@]}"; do
[[ -s "${target}" ]] || return 1
done
"${CMP}" -s "${cert_tmp}" "${CERT_FILE}" || return 1
"${CMP}" -s "${inca_tmp}" "${INCA_FILE}" || return 1
"${CMP}" -s "${full_tmp}" "${FULL_CERT_FILE}" || return 1
new_pub_sha="$(public_key_sha256_from_cert "${cert_tmp}")" || return 1
current_key_sha="$(public_key_sha256_from_key "${PRIVKEY_NOPWD_FILE}")" || return 1
[[ "${new_pub_sha}" == "${current_key_sha}" ]]
}
############################################################
# バックアップ・配置・Apache反映
############################################################
backup_current_files() {
local target
BACKUP_DIR="${WORK_BASE}/backup_${SAFE_DOMAIN}_$(date +%Y%m%d_%H%M%S)"
mkdir -m 700 "${BACKUP_DIR}"
log "既存の証明書をバックアップします: ${BACKUP_DIR}"
for target in "${TARGET_FILES[@]}"; do
if [[ -e "${target}" ]]; then
cp -a -- "${target}" "${BACKUP_DIR}/"
fi
done
}
stage_and_install_files() {
local src target mode
log "証明書ファイルを一時名で配置します..."
while IFS='|' read -r src target mode; do
install -o root -g root -m "${mode}" "${src}" "${target}${STAGE_SUFFIX}"
done <<EOF_STAGE
${TMP_DIR}/${DOMAIN}.privkey.pem|${PRIVKEY_FILE}|600
${TMP_DIR}/${DOMAIN}.privkey_nopwd.pem|${PRIVKEY_NOPWD_FILE}|600
${TMP_DIR}/${DOMAIN}.inca.pem|${INCA_FILE}|644
${TMP_DIR}/${DOMAIN}.cert.pem|${CERT_FILE}|644
${TMP_DIR}/${DOMAIN}.full_cert.pem|${FULL_CERT_FILE}|644
EOF_STAGE
# ここから先で失敗した場合、EXITトラップで旧ファイルへ戻す。
ROLLBACK_REQUIRED=1
for target in "${TARGET_FILES[@]}"; do
mv -f -- "${target}${STAGE_SUFFIX}" "${target}"
done
restore_selinux_contexts
}
reload_apache() {
log "Apache設定を確認します..."
if ! run_and_log "${APACHECTL}" configtest; then
die "apachectl configtestに失敗しました。旧証明書へ戻します。"
fi
log "Apacheをreloadします..."
if ! run_and_log "${SYSTEMCTL}" reload httpd; then
log "ERROR: httpdのreloadに失敗しました。旧証明書へ戻します。"
rollback_files
ROLLBACK_REQUIRED=0
# reloadが一部実行されていた場合に備えて、旧証明書で再度reloadを試す。
if run_and_log "${APACHECTL}" configtest && run_and_log "${SYSTEMCTL}" reload httpd; then
log "旧証明書への復旧とApacheのreloadが完了しました。"
else
log "CRITICAL: 旧証明書へ戻しましたがApacheのreloadに失敗しました。手動確認が必要です。"
fi
die "Apacheへの新証明書反映に失敗しました。"
fi
ROLLBACK_REQUIRED=0
}
############################################################
# メイン処理
############################################################
main() {
log "========== ACM証明書エクスポート開始 =========="
check_requirements
check_acm_certificate
export_certificate
validate_exported_files
if certificate_is_unchanged; then
chown root:root "${TARGET_FILES[@]}"
chmod 600 "${PRIVKEY_FILE}" "${PRIVKEY_NOPWD_FILE}"
chmod 644 "${INCA_FILE}" "${CERT_FILE}" "${FULL_CERT_FILE}"
restore_selinux_contexts
log "ACM上の証明書は現在の配置済み証明書と同一です。Apacheのreloadは行いません。"
log "========== ACM証明書エクスポート正常終了 =========="
return 0
fi
backup_current_files
stage_and_install_files
reload_apache
log "ACM証明書の配置とApacheへの反映が完了しました。"
log "バックアップ先: ${BACKUP_DIR}"
log "========== ACM証明書エクスポート正常終了 =========="
}
main "$@"
■基本設定
基本設定の箇所は実際の環境に合わせて書き換えて下さい。
DOMAIN="${DOMAIN:-example.jp}"
AWS_REGION="${AWS_REGION:-ap-northeast-1}"
CERT_ARN="${CERT_ARN:-arn:aws:acm:ap-northeast-1:123456789012:certificate/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}"
# 証明書に含まれていることを確認するホスト名。
# 必要に応じて "www.example.jp" などを追加してください。
EXPECTED_HOSTS=(
"example.jp"
)
最終更新:2026年07月30日 13:23