概要
JS7 各コンポーネント間の通信を https で行う際に必要となるKeyStore、TrustStore の作成方法についてのメモ。
2パターン書きますが、どちらでも通信できることは確認済みです。
基本は手段1のRoot CA + サーバー毎の証明書で対応。
ただし各ホストの宛先をドメイン名で指定可能ならば手段2がお手軽。
動作確認環境
JS7 JobScheduler 2.5.3
OpenSSL 1.0.2k-fips
openjdk 11.0.18 2023-01-17 LTS
手段1 Root CA のTrustStore、サーバー毎のKeyStoreを生成
各サーバーの証明書は、Root CA が発行する。
またSANに、宛先として指定する値(ホスト名、ドメイン名、IPアドレス)を登録。
Root CA の証明書をTrustStoreに格納することで、各サーバー証明書のチェックが可能となる。
Root CA 証明書を管理しておけば、後から追加でサーバー証明書を作成することも可能。
Root CA 証明書(自己署名)作成
各証明書発行用のRoot CA 証明書を自己署名で作成
あわせて TrustStore用に、PKCS#12形式証明書を作成
※要keytoolコマンド (openjdkインストールで利用可)
cn="JS7 Root CA"
company="Saitamano KUSA Inc."
country="JP"
crtfile="js7_ca.crt"
keyfile="js7_ca.key"
truststore="https-truststore.p12"
p12pass="jobscheduler"
p12alias="$cn"
subject_text="/C=${country}/O=${company}/CN=${cn}"
# ※最小はCNのみ
subject_text="/CN=${cn}"
# 自己署名証明書、秘密鍵を作成
openssl req -new -x509 -days 3650 -newkey rsa:2048 -nodes -out $crtfile -keyout $keyfile -subj "$subject_text"
# 上記証明書より、TrustStore用にPKCS#12形式証明書を作成 ※要openjdk
[ -f $truststore ] && rm $truststore
keytool -importcert -noprompt -file $crtfile -alias "$p12alias" -keystore $truststore -storepass $p12pass -storetype PKCS12 -trustcacerts
個々のサーバー証明書を作成
各サーバー用の証明書を発行。認証用にSANを追加する。
あわせてKeyStore用に、PKCS#12形式証明書を作成
#!/usr/bin/env bash
gen_keystore() {
# SAN 対応のPEM形式証明書を作成し、CA証明書で署名。
# あわせてKeyStore用のPKCS#12形式証明書を作成
# $1 でCommon Name を指定(ホスト名、ドメイン名)
# --domain , --ip でSANに登録する値を指定可能
declare -a ip_ranges=()
declare ip_range
declare -a domain_names=()
declare domain_name
declare -a san_values=()
declare san_value
declare common_name
declare subject_text
declare keyfile
declare csrfile
declare crtfile
declare keystore
declare p12alias
declare p12pass="jobscheduler"
# あらかじめ用意しておいた Root CA 証明書、秘密鍵
declare ca_keyfile="js7_ca.key"
declare ca_crtfile="js7_ca.crt"
options=$(getopt --options i:d:h \
--longoptions ip:,domain:,help \
-- "$@") || return 1
eval set -- "$options"
while true; do
case "$1" in
-i | --ip)
[ "${2:0:1}" == "-" ] || [ ! "$2" ] && { echo "ip が指定されていません" >&2 && return 1; }
ip_ranges+=("$2")
shift 2
;;
-d | --domain)
[ "${2:0:1}" == "-" ] || [ ! "$2" ] && { echo "domain が指定されていません" >&2 && return 1; }
domain_names+=("$2")
shift 2
;;
-h | --help)
# usage (省略)
exit
;;
--)
shift
break
;;
*)
echo Error
return 1
;;
esac
done
shift $((OPTIND - 1))
common_name=$1
if echo "$common_name" | grep -E '^([a-zA-Z0-9]+(-[a-zA-Z0-9]+)*\.?)+([a-zA-Z]{2,})?$' >/dev/null; then
subject_text="/CN=${common_name}"
san_values+=("DNS:${common_name}")
else
echo "CNの指定に問題あり : ${common_name}"
return 1
fi
# 各種出力ファイルはCN名ベースとする
keyfile="${common_name}.key"
csrfile="${common_name}.csr"
crtfile="${common_name}.crt"
keystore="${common_name}-keystore.p12"
p12alias="${common_name}"
# SNI対応用にSAN値を生成
for domain_name in "${domain_names[@]}"; do
if echo "$domain_name" | grep -E '^(\*\.)?([a-zA-Z0-9]+(-[a-zA-Z0-9]+)*\.?)+([a-zA-Z]{2,})?$' >/dev/null; then
[ "$domain_name" != "$common_name" ] && san_values+=("DNS:${domain_name}")
else
echo "ドメイン名またはホスト名ではありません : ${domain_name}"
return 1
fi
done
for ip_range in "${ip_ranges[@]}"; do
if echo "$ip_range" | grep -E '^([0-9]{1,3}\.){3}[0-9]{1,3}(/([0-9]|[1-2][0-9]|3[0-2]))?$' >/dev/null; then
san_values+=("IP:${ip_range}")
else
echo "CIDR表記ではありません : ${ip_range}"
return 1
fi
done
san_value=$(
IFS=","
echo "${san_values[*]}"
)
san_value="subjectAltName = $san_value"
# 秘密鍵、CSR生成
openssl req -new -newkey rsa:2048 -nodes -out "$csrfile" -keyout "$keyfile" -subj "$subject_text"
# CSRより証明書をPEM形式で作成、CA証明書で署名 あわせてSANも追加する
openssl x509 -req -in "$csrfile" -out "$crtfile" -days 3650 -CA $ca_crtfile -CAkey $ca_keyfile -CAcreateserial -extfile <(echo "$san_value")
# 上記証明書と秘密鍵を使ってPKCS#12 形式で出力(KeyStore用)
openssl pkcs12 -export -inkey "$keyfile" -in "$crtfile" -out "$keystore" -name "$p12alias" -password "pass:$p12pass"
}
# 証明書作成 --domain, --ip でSANに登録する値を指定
gen_keystore js7joc --domain "*.local" --domain "*.mshome.net" --domain "localhost" --ip "192.168.11.101" --ip "127.0.0.1"
gen_keystore js7controller --domain "*.local" --domain "*.mshome.net" --domain "localhost" --ip "192.168.11.102" --ip "127.0.0.1"
gen_keystore js7agent --domain "*.local" --domain "*.mshome.net" --domain "localhost" --ip "192.168.11.103" --ip "127.0.0.1"
これにより作られた https-truststore.p12
をTrustStoreとして、
js7joc-keystore.p12
js7controller-keystore.p12
js7agent-keystore.p12
を各サーバーのKeyStoreとして使用する。
この手段を用いた場合は、Root CA の証明書、秘密鍵を適切に管理してください。
手段2 各サーバー共通のKeyStore、TrustStoreを生成
TrustStore、KeyStoreを、共通の証明書ファイルとするやり方
SANの登録値にワイルドカードドメイン名を指定。(alt_names に記載)
※controller, agent 登録時のURLは、このドメイン名で指定する必要あり
(IP指定は非推奨。IPが変更となった場合に、証明書の作り直し、全差し替えが発生する)
内部DNSやmDNS利用で、同一ドメインで名前解決ができるならば、1つの証明書のみで、お手軽に済ませられる。
下記のコマンド例は、以下の環境で動作確認済みです
openssl 1.1.1 および 3.0
openjdk 11 および 17
cn="JobScheduler"
crtfile="https-keystore.crt"
keyfile="https-keystore.key"
keystore="https-keystore.p12"
truststore="https-truststore.p12"
p12pass="jobscheduler"
p12alias="$cn"
# 自己署名でSANに vault-cluster を含めた証明書を生成
openssl req -x509 -days 3650 -nodes -newkey rsa:2048 -out "$crtfile" -keyout "$keyfile" -config - <<EOF
[req]
distinguished_name = req_distinguished_name
x509_extensions = v3_req
prompt = no
[req_distinguished_name]
CN = $cn
[v3_req]
keyUsage = digitalSignature, keyEncipherment, dataEncipherment
extendedKeyUsage = serverAuth
subjectAltName = @alt_names
[alt_names]
DNS.1 = localhost
DNS.2 = *.local
DNS.3 = *.mshome.net
IP.1 = 127.0.0.1
EOF
# 上記証明書と秘密鍵を使ってPKCS#12 形式で出力(KeyStore用)
openssl pkcs12 -export -inkey "$keyfile" -in "$crtfile" -out "$keystore" -name "$p12alias" -password "pass:$p12pass"
# 上記証明書を使ってPKCS#12 形式で出力(TrustStore用)
keytool -importcert -noprompt -file $crtfile -alias "$p12alias" -keystore $truststore -storepass $p12pass -storetype PKCS12 -trustcacerts
これにより作られた https-keystore.p12
をKeyStore https-truststore.p12
をTrustStoreとして使用する。
(上記の例では、宛先に localhost、xxx.local、 xxx.mshome.net、127.0.0.1 のいずれかの指定ならば対応可能)
用語
KeyStore
証明書+秘密鍵を PKCS#12形式で保存したもの
自サーバーの情報を表すために使用
TrustStore
発行者の証明書を PKCS#12形式で保存したもの
証明書認証時に、接続相手サーバーの証明書が信頼できることを確認するために使用
SAN(Subject Alternative Name)
CommonNameの別名として、自サーバーの情報を表す。
ドメイン、ホスト名、IPアドレスなどの情報を格納できる。
JOC、Controller、Agentとの接続時に、接続先URLとして指定した値(IP、ホスト名など)と、接続先サーバー証明書上のSANに記載された情報が一致する必要がある。
本例では、SANにホスト名、ドメイン名(ワイルドカード使用可)、IPアドレスを指定する
※接続先がSANに含まれていない場合、認証エラーとなる。
※接続時に常にドメイン名を使う場合は、CommonNameにワイルドカード付ドメイン名などを指定することで、SANは指定しなくても接続可能。ただしこの場合、IP指定での接続はできない
keytool
証明書の生成、管理を行うコマンド
TrustStore 生成時に必須 ( openssl コマンドでは不可 )
OpenJDK インストールで利用可能
※JS7 インストール環境であれば、OpenJDK が入っている
KeyStore Explorer
https://keystore-explorer.org/
GUIでの証明書管理が可能。
KeyStore 、TrustStore の内容を確認する際に、インストールしてあると便利。