nexus.properties 에 키스토어 경로를 적었는데 기동 로그에는 전혀 다른 경로를 찾는다고 나온다.
/opt/nexus/etc/ssl/keystore.jks (No such file or directory)
Nexus 는 기동 시 karaf.etc 가 가리키는 디렉터리의 nexus.properties 를 읽는다. 설치 디렉터리 쪽(<install>/etc)과 데이터 디렉터리 쪽(<data>/etc) 두 군데에 같은 이름의 파일이 있을 수 있어, 엉뚱한 파일을 고치면 아무 변화가 없다.
실제로 어느 쪽을 읽는지 프로세스에서 확인한다.
ps -ef | grep '[j]ava.*nexus' | tr ' ' '\n' | grep -E 'karaf\.(etc|base|home|data)'
그리고 경로가 XML 에 직접 박혀 있는 경우도 있으므로 함께 검색한다.
grep -Rn 'keystore.jks\|ssl/keystore' <install-dir> <data-dir> 2>/dev/null
jetty-https.xml 의 KeyStorePath 가 ssl.etc 프로퍼티를 기준으로 잡혀 있으면, nexus.properties 의 nexus-https-keystore-path 를 바꿔도 반영되지 않는다. 이때는 ssl.etc 를 맞추거나 XML 의 경로를 직접 고친다.
java.io.IOException: Keystore was tampered with, or password was incorrect
Caused by: java.security.UnrecoverableKeyException: Password verification failed
키스토어 자체의 비밀번호와 개인키 비밀번호가 다르거나, jetty-https.xml 의 세 자리(KeyStorePassword · KeyManagerPassword · TrustStorePassword)에 서로 다른 값이 들어간 경우다. 먼저 키스토어를 직접 열어 본다.
keytool -list -v -keystore <keystore-path> -storepass "${KEYSTORE_PASSWORD}"
확인할 것은 세 가지다.
nexus-https-keystore-type=PKCS12 를 함께 적는다.PrivateKeyEntry 인지. trustedCertEntry 만 있으면 서버 인증서로 쓸 수 없다.Jetty 는 키스토어와 개인키 비밀번호가 같은 편이 다루기 쉽다. 다르면 keytool -keypasswd 로 맞춘다.
루트 CA 를 만들고 그것으로 서버 인증서에 서명해 키스토어를 구성하는 순서다. import 순서가 어긋나면 체인이 끊긴다.
# 1. 루트 CA
openssl req -x509 -newkey rsa:4096 -sha256 -days 3650 -nodes \
-keyout rootCA.key -out rootCA.crt \
-subj "/C=KR/O=<org>/CN=<org> Root CA"
# 2. 서버 키와 CSR - 키스토어 안에서 만든다
keytool -genkeypair -alias nexus-ssl -keyalg RSA -keysize 2048 \
-keystore keystore.jks -storepass "${KEYSTORE_PASSWORD}" \
-dname "CN=<nexus-fqdn>, O=<org>, C=KR" \
-ext "SAN=dns:<nexus-fqdn>,dns:*.<domain>,ip:<ip>"
keytool -certreq -alias nexus-ssl -keystore keystore.jks \
-storepass "${KEYSTORE_PASSWORD}" -file nexus.csr
# 3. 루트 CA 로 서명 - SAN 을 다시 명시해야 한다
cat > nexus.ext <<'EOF'
basicConstraints=CA:FALSE
keyUsage=digitalSignature,keyEncipherment
extendedKeyUsage=serverAuth
subjectAltName=@alt
[alt]
DNS.1=<nexus-fqdn>
DNS.2=*.<domain>
IP.1=<ip>
EOF
openssl x509 -req -in nexus.csr -CA rootCA.crt -CAkey rootCA.key \
-CAcreateserial -out nexus.crt -days 825 -sha256 -extfile nexus.ext
# 4. 루트 CA 를 먼저, 서버 인증서를 나중에 넣는다
keytool -importcert -trustcacerts -alias root-ca -file rootCA.crt \
-keystore keystore.jks -storepass "${KEYSTORE_PASSWORD}" -noprompt
keytool -importcert -alias nexus-ssl -file nexus.crt \
-keystore keystore.jks -storepass "${KEYSTORE_PASSWORD}"
openssl x509 -req 로 서명할 때 확장(-extfile) 을 주지 않으면 CSR 의 SAN 이 사라진다. 서명 뒤에 반드시 확인한다.
openssl x509 -in nexus.crt -noout -text | grep -A1 'Subject Alternative Name'
openssl verify -CAfile rootCA.crt nexus.crt
openssl x509 -in nexus.crt -noout -issuer -subject
서버 인증서의 별칭에 루트 CA 를 먼저 넣지 않은 상태로 -importcert 를 하면 "Failed to establish chain from reply" 가 난다. 순서가 중요한 이유다.
ss -lntp | grep 8443
openssl s_client -connect <nexus-fqdn>:8443 -showcerts </dev/null
curl -v --cacert rootCA.crt https://<nexus-fqdn>:8443/
체인이 두 개 이상 나오고 Verify return code: 0 이면 정상이다.
자체 서명 CA 로 만든 Nexus 를 컨테이너 레지스트리로 쓰려면 루트 CA 를 각 노드가 신뢰해야 한다.
cp rootCA.crt /etc/pki/ca-trust/source/anchors/
update-ca-trust extract
mkdir -p /etc/docker/certs.d/<nexus-fqdn>:8443
cp rootCA.crt /etc/docker/certs.d/<nexus-fqdn>:8443/ca.crt
containerd 를 쓰면 /etc/containerd/certs.d/<host>:<port>/hosts.toml 쪽을 본다.
nexus.properties 와 jetty-https.xml 의 기본 구성.