#chiroito ’s blog

Java を中心とした趣味の技術について

Google Trust Services CAの証明書を OpenShift に適用

※2024年3月13日に更新

自宅にGoogle Domainsでドメイン情報を管理しながらOpenShiftを導入したところ、Chromeからコンソールに繋ごうとすると接続できなくて困ったので、それを修正する備忘録です。

OpenShiftのインストールが終わるとこんな画面になります。

暫定対処としては、PCで、この画面のどこかをマウスでクリックしてthisisunsafeと入力すると開けます。iPad ではできませんでした。

最終的なゴールはちゃんと保護された状態で接続できるようにすることです。

事前準備

Google Trust Service の EABキーとACME DNS APIのアクセストークンが必要です。

まずは、Google Trust Services にアクセスするために必要な情報を取得します。

「EABキーを取得」をクリックします。

次に、同じくセキュリティにあるACME DNS APIにあるトークンを作成をクリックします。

証明書を作成

まずは、証明書を作成するツールをダウンロードします。

git clone https://github.com/acmesh-official/acme.sh.git
cd acme.sh

次に証明書作成に必要な環境変数を用意します。ocコマンドがちゃんと動かない場合は、Red Hat Hybrid Cloud Consolekubeconfig をダウンロードしてきてホームディレクトリに.kube/configファイルとして配置しましょう。

export LE_API=$(oc whoami --show-server | cut -f 2 -d ':' | cut -f 3 -d '/' | sed 's/-api././')
export LE_WILDCARD=$(oc get ingresscontroller default -n openshift-ingress-operator -o jsonpath='{.status.domain}')
export EAB_KEY_ID=<Google DomainsのEABキーID>
export EAB_HMAC_KEY=<Google DomainsのEAB HMACキー>
export GOOGLEDOMAINS_ACCESS_TOKEN=<ACME DNS APIのトークン>
export MAIL_ADDRESS=<自身のメールアドレス>
export DOMAIN=<自身のOpenShiftのドメイン> #今回はo.chiroito.dev

あとは発行の処理をするだけです。

./acme.sh --issue -d ${LE_API} -d *.${LE_WILDCARD} --dns dns_googledomains --server google

以下のようなログが出れば完了です。

[Wed Mar 13 10:28:06 JST 2024] Your cert is in: /root/.acme.sh/api.o.chiroito.dev_ecc/api.o.chiroito.dev.cer
[Wed Mar 13 10:28:06 JST 2024] Your cert key is in: /root/.acme.sh/api.o.chiroito.dev_ecc/api.o.chiroito.dev.key
[Wed Mar 13 10:28:06 JST 2024] The intermediate CA cert is in: /root/.acme.sh/api.o.chiroito.dev_ecc/ca.cer
[Wed Mar 13 10:28:06 JST 2024] And the full chain certs is there: /root/.acme.sh/api.o.chiroito.dev_ecc/fullchain.cer
[Wed Mar 13 10:28:06 JST 2024] _on_issue_success
[Wed Mar 13 10:28:06 JST 2024] 'dns_googledomains' does not contain 'dns'
[Wed Mar 13 10:28:06 JST 2024] The NOTIFY_HOOK is empty, just return.

エラーが起きたときは、--debugを付けて再実行しましょう。再実行時に--renewを付けても一定の間隔を待たないと発行できないようで、--forceを付けて強制的に再発行できます。

以前に比べるとかなり簡単に証明書を発行できるようになりました。

環境変数の登録が終わった後は、以下のコマンドでGoogle(?)にアカウントを作ります。

acme.sh --register-account  -m ${MAIL_ADDRESS} --server google \
--eab-kid ${EAB_KEY_ID} --eab-hmac-key ${EAB_HMAC_KEY}

[Tue Apr 18 18:17:35 JST 2023] Registering account: https://dv.acme-v02.api.pki.goog/directory
[Tue Apr 18 18:17:48 JST 2023] Registered
[Tue Apr 18 18:17:49 JST 2023] ACCOUNT_THUMBPRINT='XXXXXXX'

上記のログで最後の行に出力されているものを実行します。

ACCOUNT_THUMBPRINT='XXXXXXX'

証明書を作り始めましょう。1度目は失敗します。

./acme.sh --issue -d ${LE_API} -d *.${LE_WILDCARD} --dns dns_googledomains --server google

必要なTXTレコードが無いらしく、以下のエラーが発生します。

[Tue Apr 18 19:01:00 JST 2023] Using CA: https://dv.acme-v02.api.pki.goog/directory
[Tue Apr 18 19:01:00 JST 2023] Multi domain='DNS:api.o.chiroito.dev,DNS:*.apps.o.chiroito.dev'
[Tue Apr 18 19:01:00 JST 2023] Getting domain auth token for each domain
[Tue Apr 18 19:01:28 JST 2023] Getting webroot for domain='api.o.chiroito.dev'
[Tue Apr 18 19:01:28 JST 2023] Getting webroot for domain='*.apps.o.chiroito.dev'
[Tue Apr 18 19:01:28 JST 2023] Can not find dns api hook for: dns_googledomains
[Tue Apr 18 19:01:28 JST 2023] You need to add the txt record manually.
[Tue Apr 18 19:01:28 JST 2023] Add the following TXT record:
[Tue Apr 18 19:01:28 JST 2023] Domain: '_acme-challenge.api.o.chiroito.dev'
[Tue Apr 18 19:01:28 JST 2023] TXT value: '65fNOPeBc5XQs0Q1nuNk3Z3npDKZ_cRjYKalHaxgsZo'
[Tue Apr 18 19:01:28 JST 2023] Please be aware that you prepend _acme-challenge. before your domain
[Tue Apr 18 19:01:28 JST 2023] so the resulting subdomain will be: _acme-challenge.api.o.chiroito.dev
(略)
[Tue Apr 18 19:01:28 JST 2023] Domain: '_acme-challenge.apps.o.chiroito.dev'
[Tue Apr 18 19:01:28 JST 2023] TXT value: 'LIAk1oBiufG5CdkJQiTZinorXzI9AJAIsdI5Jc1e6xI'
(略)
[Tue Apr 18 19:01:28 JST 2023] Please add the TXT records to the domains, and re-run with --renew.
[Tue Apr 18 19:01:28 JST 2023] Please add '--debug' or '--log' to check more details.
[Tue Apr 18 19:01:28 JST 2023] See: https://github.com/acmesh-official/acme.sh/wiki/How-to-debug-acme.sh

こういうのが複数行出力されます。

[Tue Apr 18 19:01:28 JST 2023] Domain: '_acme-challenge.api.o.chiroito.dev'
[Tue Apr 18 19:01:28 JST 2023] TXT value: '65fNOPeBc5XQs0Q1nuNk3Z3npDKZ_cRjYKalHaxgsZo'

ログで言われたとおりGoogle Domainsで以下のTXTレコードを作ります。出力された分全部必要です。今回は2つ出力されたので2つ作ります。

ドメイン
_acme-challenge.api.o.chiroito.dev 65fNOPeBc5XQs0Q1nuNk3Z3npDKZ_cRjYKalHaxgsZo
_acme-challenge.apps.o.chiroito.dev LIAk1oBiufG5CdkJQiTZinorXzI9AJAIsdI5Jc1e6xI

追加した後はこんな感じになります。

しばらくしたら--renewを追加して再実行しましょう。

./acme.sh --issue -d ${LE_API} -d *.${LE_WILDCARD} --dns dns_googledomains --server google --renew

(略)

[Tue Apr 18 21:09:54 JST 2023] Your cert is in: /home/chiro/.acme.sh/api.o.chiroito.dev_ecc/api.o.chiroito.dev.cer
[Tue Apr 18 21:09:54 JST 2023] Your cert key is in: /home/chiro/.acme.sh/api.o.chiroito.dev_ecc/api.o.chiroito.dev.key
[Tue Apr 18 21:09:54 JST 2023] The intermediate CA cert is in: /home/chiro/.acme.sh/api.o.chiroito.dev_ecc/ca.cer
[Tue Apr 18 21:09:54 JST 2023] And the full chain certs is there: /home/chiro/.acme.sh/api.o.chiroito.dev_ecc/fullchain.cer
[Tue Apr 18 21:09:54 JST 2023] _on_issue_success

最後の行が出力されたら発行完了です。

OpenShiftへ証明書を適用

証明書の作成されたディレクトリへ移動して、ocコマンドで登録していきます。

cd /home/chiro/.acme.sh/api.o.chiroito.dev_ecc/
oc create configmap google-domain-ca --from-file=ca-bundle.crt=./ca.cer -n openshift-config
oc patch proxy/cluster --type=merge --patch='{"spec":{"trustedCA":{"name":"google-domain-ca"}}}'
# 次のコマンドを実行するとしばらく繋がらなくなる
oc create secret tls google-domain --cert=fullchain.cer --key=api.${DOMAIN}.key -n openshift-ingress
# 次のコマンドを実行するとしばらく繋がらなくなる
oc patch ingresscontroller.operator default --type=merge -p '{"spec":{"defaultCertificate": {"name": "google-domain"}}}' -n openshift-ingress-operator

あとは接続できなくなってから1,2分待つと保護された状態で接続できるようになります。

OpenShift がおかしくなったと感じたら。

失敗したときは、各行でそれぞれ以下のコマンドを実行しました。OpenShiftの知識はゼロなので、雰囲気でやってます。

# 作ったconfigmapを消す
oc delete configmap google-domain-ca -n openshift-config

# CAの設定を消す。最初の値を確認忘れのため、null を入れてます
oc patch proxy/cluster --type=merge --patch='{"spec":{"trustedCA":null}}'

# 作った secret の情報を消す
oc delete secret google-domain -n openshift-ingress

# defaultCertificate の設定を消す。最初の値を確認忘れのため、null を入れてます
oc patch ingresscontroller.operator default --type=merge -p '{"spec":{"defaultCertificate": null}}' -n 
openshift-ingress-operator

参考

Google Trust Services CA · acmesh-official/acme.sh Wiki · GitHub

Let’s Automate :: Let’s Encrypt TLS Certs for OpenShift 4 | by Karan Singh | Medium

第3章 証明書の設定 OpenShift Container Platform 4.12 | Red Hat Customer Portal

困ったときのGitコマンド集

自分がGitで困ったときに使ったGitコマンドをここに日々追加していきます。

PR をレビューしてもらっていろいろいじっていたら、よく分からなくなって変なのまでリモートに行ってしまった。

git push -f origin <戻したい位置>:<ブランチ>

ローカルで良く分からなくなったから、どこかまで戻したい。

git reset --hard <戻したい位置>

日本語訳:JEP 425 : Virtual Threads (Preview) Part 5

個人的に気になっている Project Loom で Virtual Threads がプレビューされたので仕様である JEP を翻訳してみました。 ボリュームが多いため、いくつかのパートに分けて公開していきます。

原文はこちら:JEP 425: Virtual Threads (Preview)

全パートはこちら

他の案

  • 非同期APIに依存し続けます。非同期APIは同期APIとの統合が難しく、同じI/O操作の2つの表現という分断された世界を作り出し、トラブルシューティング、監視、デバッグ、プロファイリングの目的でプラットフォームが状況に応じて使用できる、一連の操作に対する統一した概念を提供しません。
  • Java言語に構文的なスタックレス・コルーチン(すなわちasync/await)を追加することです。これらはユーザモードスレッドよりも実装が簡単で、一連の操作の状況を表す統一的な構造を提供するでしょう。しかし、この構成は新しく、スレッドとは別物で、多くの点でスレッドと似ていますが、ニュアンス的には異なるものです。スレッド用に設計されたAPIとコルーチン用に設計されたAPIの間で世界を二分し、プラットフォームとそのツールのすべてのレイヤーに新しいスレッドのような構造を導入する必要があるのです。これは、エコシステムが採用するのに時間がかかり、ユーザーモードのスレッドのようにエレガントでプラットフォームと調和したものにはならないでしょう。 構文的コルーチンを採用しているほとんどの言語は、ユーザーモードスレッドを実装できない(例:Kotlin)、従来のセマンティックの保証(例:本質的にシングルスレッドなJavaScript)、または言語固有の技術的制約(例:C++)のためにそうしてきました。これらの制約は、Javaには当てはまりません。
  • java.lang.Threadとは関係なく、ユーザモードのスレッドを表す新しいパブリッククラスを導入します。これは、Threadクラスが25年以上にわたって蓄積してきた不要なお荷物を捨てる機会になるでしょう。私たちは、このアプローチのいくつかのバリエーションを検討し、プロトタイプを作成しましたが、どの場合も、既存のコードをどのように実行するかという問題に直面することになりました。主な問題は、既存のコードで Thread.currentThread() が直接的または間接的に広く使用されていることです(例えば、ロック所有権の決定やスレッドローカル変数など)。このメソッドは、現在の実行スレッドを表すオブジェクトを返さなければなりません。もしユーザモードスレッドを表す新しいクラスを導入した場合、 currentThread() は Thread のように見えるがユーザモードスレッドオブジェクトに委ねるある種のラッパーオブジェクトを返さなければならないでしょう。現在の実行スレッドを表すオブジェクトが2つあると混乱するので、結局、古いThread APIを維持することは大きなハードルではないと判断しました。currentThread()などの一部のメソッドを除いて、開発者がThread APIを直接使うことはほとんどなく、ExecutorServiceなどの上位のAPIを使ってやり取りすることがほとんどです。Thread クラスや、ThreadGroup などの関連クラスから、不要なメソッドを非推奨にしたり削除したりすることで、時間をかけて不要な荷物を取り除いていく予定です。

テスト

  • 既存のテストは、私たちがここで提案する変更が、多数の構成と実行モードにおいて、予期せぬ回帰を引き起こさないことを保証するものです。
  • 私たちは jtreg テストツールを拡張し、既存のテストを仮想スレッドのコンテキストで実行できるようにする予定です。これにより、多くのテストで2つのバージョンを用意する必要がなくなります。
  • 新しいテストでは、すべての新しいAPIと改良されたAPI、および仮想スレッドをサポートするために変更されたすべての領域を検証します。
  • 新しい負荷テストは、信頼性と性能に重要な領域を対象とします。
  • 新しいマイクロベンチマークは、パフォーマンスが重要な部分をターゲットにします。
  • Helidon や Jetty などの既存のサーバーを大規模なテストに使用します。

リスクと前提条件

本提案の主なリスクは、既存のAPIやその実装の変更に伴う互換性の問題です。

  • java.io.BufferedInputStream, BufferedOutputStream, BufferedReader, BufferedWriter, PrintStream, および PrintWriter クラスで使用される内部の(および文書化されていない)ロックプロトコルの改訂は、入出力メソッドが呼び出されたストリーム上で同期することを想定しているコードに影響を与えるかもしれません。この変更は、これらのクラスを継承し、スーパークラスによるロックを前提としているコードには影響を与えません。また、java.io.Reader または java.io.Writer を継承し、それらの API によって公開されるロック・オブジェクトを使用するコードにも影響しません。
  • java.lang.ThreadGroup は、スレッドグループを破棄できなくなりました。また、デーモンスレッドグループの概念をサポートしなくなり、その suspend(), resume(), stop() メソッドは常に例外をスローするようになりました。

プラットフォームスレッドと仮想スレッドの間には、既存のコードと仮想スレッドや新しいAPIを利用する新しいコードを組み合わせたときに、いくつかの動作の違いが見られることがあります。

  • Thread.setPriority(int) メソッドは、常に Thread.NORM_PRIORITY の優先度を持つ仮想スレッドには効果がありません。
  • Thread.setDaemon(boolean) メソッドは、常にデーモンスレッドである仮想スレッドに対して何の効果もありません。
  • Thread.stop()、suspend()、resume()メソッドは、仮想スレッド上で起動されるとUnsupportedOperationExceptionをスローします。
  • Thread API は、スレッドローカル変数をサポートしないスレッドの作成をサポートします。 ThreadLocal.set(T) および Thread.setContextClassLoader(ClassLoader) は、スレッドローカルをサポートしないスレッドのコンテキストで起動すると、UnsupportedOperationException をスローします。
  • Thread.getAllStackTraces() は、すべてのスレッドの Map ではなく、すべてのプラットフォームス レッドの Map を返すようになりました。
  • java.net.Socket, ServerSocket, DatagramSocket によって定義されたブロッキング I/O メソッドは、仮想スレッドのコンテキストで呼び出された場合、割り込み可能になりました。ソケット操作でブロックされているスレッドが割り込まれた場合、既存のコードが壊れる可能性がありました。この場合、スレッドを起動し、ソケットをクローズします。
  • 仮想スレッドは ThreadGroup のアクティブなメンバではありません。仮想スレッドで Thread.getThreadGroup() を呼び出すと、空のダミー "VirtualThreads" グループが返されます。
  • SecurityManagerが設定された状態で実行されている場合、仮想スレッドには権限はありません。
  • JVM TIでは、GetAllThreadsおよびGetAllStackTraces関数は、仮想スレッドを返しません。ThreadStartイベントとThreadEndイベントが有効な既存のエージェントは、イベントをプラットフォームスレッドに制限する機能がないため、パフォーマンスの問題が発生することも考えられます。
  • java.lang.management.ThreadMXBean APIは、プラットフォームスレッドの監視と管理をサポートしますが、仮想スレッドはサポートしません。
  • -XX:+PreserveFramePointerフラグは、仮想スレッドのパフォーマンスに重大な悪影響を及ぼします。

依存関係

  • JDK 18のJEP 416 (Reimplement Core Reflection with Method Handles)では、VM-native Reflectionの実装が削除されました。これにより、メソッドがリフレクションで呼び出されたときに、仮想スレッドがうまく一時停止するようになりました。
  • JDK 13 の JEP 353 (Reimplement the Legacy Socket API) と JDK 15 の JEP 373 (Reimplement the Legacy DatagramSocket API) は、java.net.Socket、ServerSocket、DatagramSocket の実装を仮想スレッドでの使用に向けて設計した新しい実装と置き換えたものです。
  • JDK 18 の JEP 418 (Internet-Address Resolution SPI) は、ホスト名とアドレス探索のためのサービスプロバイダインターフェースを定義しました。これにより、サードパーティーライブラリーは、ホスト探索中にスレッドを固定化しない代替の java.net.InetAddress 解決手段を実装できます。