CRDとReconcileでテトリスするKubernetesのCustom Controllerを作った

テトリスするCustom Controllerを作りました。(正確に言うと「テトリスに似た何か」です)

github.com

単にテトリスをアプリとしてKubernetesクラスター上に載せるわけではなく、(無駄に)Custom Resourceにフィールドの状態などの情報を保存しReconcileで情報を更新することでテトリスを実装したものになります。T4sというリソースをデプロイすることで遊べます(詳しくはREADME参照)。

マニフェストの例:

apiVersion: t4s.tkna.net/v1
kind: T4s
metadata:
  name: t4s-sample
spec:
  width: 11
  height: 20
  wait: 1000
  nodePort: 30080

アーキテクチャ

t4s/design.md at master · tkna/t4s · GitHub

全体アーキテクチャ
全体アーキテクチャ

こんな感じのアーキテクチャで、複数のControllerが連携してピタゴラスイッチしています。特にBoardはフィールドの情報や現在操作対象のブロックの位置情報等を持っていて、何か動きがある度にそれをReconcileで書き換えています。もはや冪等性とは?という感じですが、最終的にはGameOverというステートに落ち着くので長い目で見たら冪等と言えるかもしれません(?)

メトリクス

スコア(消した行数)はメトリクスとして見ることができます。

一度に消した行数(1~4)ごとのカウント
一度に消した行数(1~4)ごとのカウント

注意点

  • 簡単のため、1 namespaceにつきT4sは1つのみにしています。
  • 操作に若干のタイムラグがあるかもしれませんが、基本的に非同期に処理をしているのと、その他いろいろ無理をしているので大目に見てください。
  • Custom Resourceをかなり高頻度で読み取り/更新します。特に、たくさんのnamespaceにT4sをデプロイすると動作速度が低下するか動かなくなると思いますのでご注意下さい。

参考

下記ページや書籍を参考にさせていただきました。

VirtualBox上のUbuntuのディスク拡張

Vagrant+VirtualBoxで作ったUbuntu 18.04で、/dev/sda1(partition: primary, filesystem: ext4)がfullになってしまったのでディスクを拡張したい。

$ df -h
Filesystem      Size  Used Avail Use% Mounted on
(略)
/dev/sda1       9.7G  9.7G   16K 100% /
(略)

環境

事前準備

  • VMを電源OFFにする
  • 念のためVMのバックアップを取る *.vbox*.vmdk、Logs、Snapshotsなどが入っているフォルダを丸ごとどこかにコピーしておく。

VMDK→VDIへの変換、ディスク拡張、アタッチ

VirtualBoxのコマンドでもできそうだったが、下記はGUIからの手順

VMDKをVDIに変換

VMDK形式だと拡張できないため、VDIに変換

  1. VirtualBoxGUIから、画面上部「ツール」 の右側のアイコン ->「メディア」を選択
  2. 対象のディスク(.vmdk)を選択して右クリック -> 「コピー」
  3. ディスクイメージのファイルタイプを聞かれるので、VDIを選択 -> 「次へ」
  4. 適当に名前をつけて「コピー」

ディスクの拡張

仮想化環境のディスク容量を拡張する - Avintonジャパン株式会社

こちらを参考に、VDIに変換済みのディスクを拡張。

拡張したディスクイメージ(VDI)をVMにアタッチ

  1. VirtualBox GUI上で対象のVMの「設定」 -> 「ストレージ」からもともとのディスクをデタッチする。 ※vagrantを使った場合ubuntu-bionic-18.04-cloudimg.vmdkubuntu-bionic-18.04-cloudimg-configdrive.vmdkがアタッチされているはず。両方とも「割り当てを除去」
  2. 「ハードディスクの追加」から、VDIに変換済みのディスクイメージを選択

拡張したディスクをOSから認識させる

※以下はVirtualBox関係なし

まだ容量の増えたDiskをアタッチしただけなので、OS上では認識されていない。

$ df -h
Filesystem      Size  Used Avail Use% Mounted on
(略)
/dev/sda1       9.7G  9.7G   16K 100% /
(略)

パーティションを拡張

$ sudo cfdisk /dev/sda
Disk: /dev/sdaSize: 20 GiB, 21474836480 bytes, 41943040 sectorsLabel: dos, identifier: 0x035e1d7d
   Device           Boot      Start            End        Sectors       Size       Id Type
>> /dev/sda1        *          2048       20971486       20969439        10G       83 Linux
   Free space              20971520       41943039       20971520        10G

/dev/sda1の後ろにFree spaceがあることがわかるので、[ Resize ]を選択し、パーティションを20GBにリサイズ。

Partition 1 resized.と出たあと、[ Write ]を選択して変更を書き込む。

ファイルシステム上でも拡張

$ sudo resize2fs /dev/sda1
resize2fs 1.44.1 (24-Mar-2018)
Filesystem at /dev/sda1 is mounted on /; on-line resizing required
old_desc_blocks = 2, new_desc_blocks = 3
The filesystem on /dev/sda1 is now 5242619 (4k) blocks long.

以下のとおり、OSから認識された。

$ df -h
Filesystem      Size  Used Avail Use% Mounted on
(略)
/dev/sda1        20G  9.7G  9.7G  50% /
(略)

参考

Vagrant (VirtualBox) でディスクサイズを変更する方法

Vagrant+VirtualBoxで、ディスクを拡張する - CLOVER🍀

vagrantのexperimental機能、Vagrant Disksを使う → Stderr: VBoxManage.exe: error: Could not find a controller named 'SATA Controller'というエラーが出たため断念

2021年振り返り

すでに年が明けてしまったが、ITエンジニア界では年の暮れに1年を振り返る風習があるようなので、私も見習って書いてみることにする。(趣味/業務関係なく、技術サイドから。手を動かす系はだいたい趣味としてやっている)

(↓長くなったので目次)

仮想ルータ

1月はFRRouting, Eve-NG, CML Sandboxなどを使って仮想ルータをいじっていた。

一応ネットワーク関連の仕事をしているはずなのに、どちらかというとサーバ系/管理システム系の仕事がメインで、ネットワーク装置に触れることなくここまで来てしまった、という危機感から、できるだけお手軽にルータ/スイッチを触って手を動かしながら勉強できる環境を探してきた。2020年はCisco Packet Tracer等を試していたが、MPLSまではできないことがわかっていたため、こちらの記事を参考に、FRRouting+Linuxでできそうだったのでやってみることにした。(今思うと、Eve-NGやCMLの方が簡単だったと思うが、いろいろ勉強にはなった)

なぜMPLSかというと、

  • 諸事情によりSegment Routingをちゃんと知っておく必要がありそうだったが、その前段となるキャリアネットワークの基礎技術として必須だと思った。またMPLS-(L3)VPNに関しては、IGP(OSPF), VRF, EGP(BGP)等、色々な技術の集合体なので、ひととおり勉強できそうだと思った。
  • 諸事情により「リソース管理」というキーワードについて考えており、ネットワークリソースと呼ばれているものの実体や、既存技術としての(L2/L3)VPNサービスで、リソースの管理(帯域制御、予約等)が(管理システムの下のレイヤで)どのように実現されているかに興味があった。

Linux+Docker+FRRoutingに関しては、当初DockerにFRRoutingを入れて、docker networkで複数コンテナをつないでいけば簡単なのでは?と思っていたのだが、docker networkのしくみを使った場合、IPアドレスやデフォルトルートが自動設定される機能があり、一筋縄ではいかなかった。

ただし、ここで1つ問題があります。Docker Networkを使ったネットワーク作成はお手軽なのですが、コンテナをつないだ際、IPアドレスが自動で設定されます。(もしくは--ipオプションで手動指定)。こうして自動もしくは手動でコンテナkernelによって設定されたIPアドレスは、FRRoutingを使って変更・削除することはできません。また、デフォルトルートを含む複数のルートも自動で設定されてしまい、FRRoutingからは変更・削除できません(かつ、Kernel Routeは最優先)

なので純粋にルータを勉強したい(かつお手軽に)場合は、Packet Tracer、GNS3/Eve-NG、CML等を使った方がよいと思われる。ただdocker networkの勉強にはなったし、この過程でtwitterでslankdevさんにいろいろ教えてもらったりする経験ができたのはとても良かった。本当にありがとうございます。自分もいつかFRRoutingでプロトコル実装したりしてcontributeできるようになりたい。

その後はEve-NGでMPLS-TEの設定をしてみたり、Fast Reroute, Path Protectionを試したりして、RSVP-TEによる帯域予約のしくみをなんとなく理解した。SRに関しても、CML Sandboxで基本的な構成を試す所までやったが、他に優先事項ができてしまい、さらに深堀まではできなかったので、今後の課題。

なお、この一連のネットワークネタについて、社内の若手中心の勉強会で発表し啓蒙活動(?)も行った。実際にやってみようと思った人がどれだけいるかわからないけど、少しでも興味を持ってくれる人が増えることを願っている。

Schema Matching

古からの技術であるSchema Matchingの調査をしていた。半分苦し紛れではあったが、有名な論文で提案されている手法の一部をpythonで実装して評価し、研究会や国際学会(のLT的な枠)のネタにはなった。

python実装の1つ、flexmatcherを試した時の記録

Terraform/Stackstorm

TerraformやStackstormの内部構造について調査をしていた。特にTerraformのProviderまわりについては、コードレベルで理解できた気がする。

その他、何かとTerraformにはお世話になった1年だった。

機械学習/深層学習/自然言語処理

個人的にはAIより仮想化とかミドルウェアとかインフラ系の技術の方が多少馴染みがあるのだが、今後避けては通れないだろうし直近でも必要になりそうだったので、GWに有名なNLP本を買ってひととおり手を動かしてみた。

今までほぼブラックボックスとして使っていたWord2Vecのしくみや、RNN, LSTM, Seq2seq, Attentionのアイデアについて理解できた(気になる)良い本だった。

あとはPyTorch, BERT関連で以下のUdemyもやった。

お仕事の方では、インターンのテーマ設定とサポートをやっていた。ざっくり言うとTerraform×機械学習/深層学習といったテーマで、Amazon Sagemakerを使って色々試行錯誤してもらった。Sagemakerのお作法に慣れる必要があり、(今までGoogle Colab等で自前で全て書いていた所からすると)少し学習コストが高く、今回のようなアドホックな研究用途には余り適していなかったかもしれないが、AutoML機能等は便利に使えたのかなと思う。特にビジネスの現場において最速でAIを適用していくために、よくデザインされたフレームワークだと感じた。

Go言語と静的解析

上記のTerraform×機械学習/深層学習をやるにあたって、学習データを作成する必要があった。既存の学習データはなく、その元となる情報はTerraform provider (AWS) の中にしかない状況。しかも単純なgrep等では抽出できず、変数の定義を辿っていくような深い解析が必要。

静的解析をはじめよう - Gopherをさがせ!

そこで静的解析。上記サイトと本を参考に、terraform-provider-awsを静的解析して学習データを作成するコードを書いた。2週間ぐらい?かかった気がするが、何とか間に合わせることができた。(上記サイトと本が非常に役立ちました。ありがとうございます。)

コードについては、競プロ的に言うとメモ化再帰を駆使した1300行ぐらいのもの。ASTをダンプしたものを見つつ、再帰とtype switchとの戦い。でもこういった静的解析をサクッと書けるようになると、プロジェクトに特化して痒いところに手が届くソースコードチェックができて便利ですね。

TCP

理由は分からないが、リソースの限界とかトレードオフとかボトルネックとかそういうのが好きなようで、いろんなリソース限界を発生させたり可視化したりするシリーズをやりたいと思っている。何を言っているかわからないかもしれないが、例えばこういうやつ。

inode (アイノード) を枯渇させてみる - CUBE SUGAR CONTAINER

システム内のいろいろなリソース(資源)に着目し、その限界を知ることでそのシステムのしくみや特性を理解する、というアプローチは教育コンテンツ的な観点でも面白いのではないだろうか。

まずは基本的な所からということで、

第1回 FTPでスループット計測するときの注意事項:教科書には載っていない ネットワークエンジニアの実践技術|gihyo.jp … 技術評論社

帯域と遅延、TCPウィンドウサイズの関係について。AWSのいろんなリージョンにiperfのサーバを置いて実測などしていた(純粋に測りたかったらtc等で遅延を挿入した方がよいと思うが、リアル環境でどうなるかやってみたかった)。最初Windowsのiperfクライアントで計測していた時に、受信側の挙動がちょっと思ったのと違う結果だったので、rfc1122を読んでみたり、tcpソースコードを読んでみたり、wiresharkソースコードを読んでみたりしていた。

当初はブログかQiita等にまとめようと思っていたが、最終的にLinuxのiperfクライアントでやったら普通に思った通りの結果になったので、急に自明に思えてしまって頓挫中。もう少し面白い感じにして何かしらアウトプットできるようにしたい。

マイクロサービス

テトリスを無駄にマイクロサービスにしたら面白いのでは?と急に思い立って作り始めた。

正確に言うと急にではなく、以下のような思惑があった。

  1. 普通分割しないようなものを分割するとどこかで性能限界が来るはずで、それを目の当たりにしたい(リソース限界的な興味)
  2. マイクロサービス(アーキテクチャのシステム)を作る人向けのフレームワークについて考えており、フレームワーク側にどのような機能があると嬉しいのか、イメージを掴みたい。

とりあえずゲームの基本部分を4つのサービスに分けて作ったのだが、

GitHub - tkna/tetris_microservices: How far can Tetris be microservices?

  • 1については、理論的にはメソッド単位、もしくはさらに細かく変数単位にして、変数(メモリ)を各マイクロサービスのデータベースと考える、ということができることに気付いてしまった(やろうと思えばできるけどめちゃくちゃ面倒くさい)。
  • 2について考えるのであれば、1のように1つのゲームを細かく分けて作るようなことは非現実的なので、どちらかというとゲーム以外にユーザ登録サービスとかハイスコアサービスとかを作って普通にオンラインゲームのサービスとして作った方が良さそう。

という割と普通のことに気付いてしまい、頓挫ペンディング中。静的解析で無駄に自動分割してくれる、とか、無駄に分割したサービスをAWS Step Functionsとlambdaで実装する、とか、無駄にKubernetes Operatorとして実装してみる、とかも考えていたが、実用性がなさそうなので現実路線の方で行くかもしれない。

競プロ

11月ぐらいから競プロも始めてみた。まだ全然できなくて恥ずかしいのだが、計算量について考えるよい機会になっている。(メモ化再帰と尺取り法は書けるようになった...)。プログラミングはどちらかというと自分の作りたいものをマイペースに作る方が好きだったのだが(というほど何かを作っているわけではない)、これはこれで面白いですね。何となく受験勉強を思い出す。色々問題を解いて解法のパターンを知れば知るほど強くなれる(少なくとも私のような初心者レベルでは)、その結果がすぐ数字で見れる、という点で。

まとめ

2021年も色々と自分が知らなかった分野に(無謀にも)踏み込むことができた(広く浅くやり過ぎて何がやりたいのかわからない感じになっている気もする...) 今年はもうちょっとまとまったアウトプットとして出せるようにしたい。あと本当はより現場に近い所でリアルなソフトウェアエンジニアリングをしていきたいという気持ちがあるので、現場で通用するエンジニアリング力(+テックリード力?)の方も引き続き高めていきたい。

その他

  • OpenDayLight

ORM, Swagger/OpenAPI, YANGあたりに興味があり、OpenDayLightを動かしてみようと思ったが失敗

OpenDaylight(Oxygen)でFeature間のコンフリクト? -

  • 暗渠

運動不足解消のために散歩を始めた結果、街中に怪しげな小径があることに気付き、それが暗渠と呼ばれる川の跡であることを知り、東京の地形と川と暗渠の沼に片足を踏み入れることになった。

flexmatcherを試してみる

諸々の事情でSchema Matchingの現状を調査中。あまり実装が見当たらないが、python実装の1つ、flexmatcherを試してみる。 (2017年から活動がないようなのが残念)

今回はUbuntu 18.04.5 のVM上で試してみた。

ドキュメントに従ってインストール

$ pip3 install flexmatcher
$ pip3 list | grep flexmatcher
flexmatcher (1.0.4)

ドキュメントに記載のExampleを動かしてみると、途中のtrainする部分でエラーが出た。

>>> fm.train()
Training FlexMatcher ...
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/local/lib/python3.6/dist-packages/flexmatcher/flexmatcher.py", line 156, in train
    clf_inst.fit(self.train_data)
  File "/usr/local/lib/python3.6/dist-packages/flexmatcher/classify/charDistClassifier.py", line 58, in fit
    self.features = feat_df.ix[:, 1:].as_matrix()
  File "/usr/local/lib/python3.6/dist-packages/pandas/core/generic.py", line 5141, in __getattr__
    return object.__getattribute__(self, name)
AttributeError: 'DataFrame' object has no attribute 'ix'

AttributeError: 'DataFrame' object has no attribute 'ix'は、pandasのバージョンが新しい(1.0.0以上)ことが原因のようなので、ダウングレードしてみる。

参考:

githubの最終コミットが2017年10月あたりだったので、 pandasのRelease historyから、それより前のバージョンを探す。

2017年7月リリースの0.20.3にしてみる。

$ pip3 uninstall pandas
$ pip3 install pandas==0.20.3
$ pip3 list | grep pandas
pandas (0.20.3)

これでExampleを実行したらうまくいった。

>>> print(predicted_mapping['rt'])
movie_rating

※ドキュメントではprint(predicted_mapping['rc'])となっているが、'rt'の誤りと思われる。

※ちなみにここにあるexample.pyは、flexmatcherのコンストラクタの仕様が若干違うようで、そのままだとflexmatcher 1.0.4では動かないので注意。

OpenDaylight(Oxygen)でFeature間のコンフリクト?

概要

結構前の資料だが、この辺りの資料を見て、YANGとそれを使ったMD-SALのしくみが気になったので、OpenDaylightを試してみようと思った。

あまり最近の参考になりそうなページがない…かなり古く画像のリンクも切れてしまっているが、こちらを参考にしてやってみた。

また、dlux(UI)の部分についてはこちらを参考にした。

Mininetで構築したスイッチをODLから管理する(まずはGUIトポロジー確認)、というのをやりたかったが、結果としてうまく動かなかった。機能間のコンフリクト?が起きているのか、原因の詳細まで解析していない。記事にある古いバージョンのODLを使えばうまくいくのかもしれない。

環境

Ubuntu VMのメモリ/CPUは4GB RAM, 2 Core (最初1GB RAMだったがさすがに足りなくエラーになった。CPU使用率も常に100%いくので、もっと増やした方がいい)

  • OpenDaylight Oxygen(0.8.4)

【SDNチャレンジ】 第10回 OpenDaylight動作編① ではかなり前のHeliumリリースを使っているが、JDK1.7を入れたくない、できれば最新に近いバージョンで動いてくれたら嬉しい、という淡い期待を持って、Oxygenを選択。

※最新版(Aluminium)は、dlux(UI)やl2switchが未対応のようだったので、dluxが動きそうなOxygenを使うことにした。

参考:I can't install dlux and l2switch features in opendaylight - Stack Overflow

※dluxについては、このドキュメントページがあったので、Oxygenで動きそうと思った。 Using the OpenDaylight User Interface (DLUX) — OpenDaylight Documentation Oxygen documentation

実施内容

JDK8をインストール

# apt install openjdk-8-jre-headless

OpenDaylight Oxygen (0.8.4)をダウンロード

# wget https://nexus.opendaylight.org/content/repositories/opendaylight.release/org/opendaylight/integration/karaf/0.8.4/karaf-0.8.4.zip
# unzip karaf-0.8.4.zip
# cd karaf-0.8.4

ODLを起動

# ./bin/karaf
karaf: JAVA_HOME not set; results may vary
Apache Karaf starting up. Press Enter to open the shell now...
100% [========================================================================]

Karaf started in 0s. Bundle stats: 13 active, 13 total

    ________                       ________                .__  .__       .__     __
    \_____  \ ______   ____   ____ \______ \ _____  ___.__.|  | |__| ____ |  |___/  |_
     /   |   \\____ \_/ __ \ /    \ |    |  \\__  \<   |  ||  | |  |/ ___\|  |  \   __\
    /    |    \  |_> >  ___/|   |  \|    `   \/ __ \\___  ||  |_|  / /_/  >   Y  \  |
    \_______  /   __/ \___  >___|  /_______  (____  / ____||____/__\___  /|___|  /__|
            \/|__|        \/     \/        \/     \/\/            /_____/      \/


Hit '<tab>' for a list of available commands
and '[cmd] --help' for help on a specific command.
Hit '<ctrl-d>' or type 'system:shutdown' or 'logout' to shutdown OpenDaylight.

opendaylight-user@root>

個々の機能がfeatureという形のプラグイン形式(OSGi)になっており、必要なものを読み込んで使うようだ。

まずodl-l2switch-switchを入れてみる。

opendaylight-user@root>feature:install odl-l2switch-switch

別ターミナルでポート状態確認。

# ss -antu | grep -e 6653 -e 6633
tcp  LISTEN    0      128                      *:6653                   *:*
tcp  LISTEN    0      128                      *:6633                   *:*

6653と6633がLISTENになった。 次にodl-dlux-coreを入れてみる。

opendaylight-user@root>feature:install odl-dlux-core

ここでUI(http://[my_ip]:8181/index.html)にアクセスしてみると、なぜかhttp://[my_ip]:8181/index.html#/topologyに飛ばされ、画面は灰色。

試しにodl-dluxapps-nodesを追加してみる。

opendaylight-user@root>feature:install odl-dluxapps-nodes

UIは見れるようになったが、6653と6633がLISTENされなくなってしまった。

# ss -antu | grep -e 6653 -e 6633
#

この状態でmininetを起動しても、

# mn --controller=remote
*** Creating network
*** Adding controller
Unable to contact the remote controller at 127.0.0.1:6653
Unable to contact the remote controller at 127.0.0.1:6633
(以下略)

となる。

ちなみに逆の順番で、dlux→l2switchの順にinstallすると、以下のエラーが出る。

opendaylight-user@root>feature:install odl-dlux-core odl-dluxapps-nodes
opendaylight-user@root>feature:install odl-l2switch-switch
Exception in thread "config-blank-txn-0" java.lang.IllegalStateException: Error while copying old configuration from ModuleInternalInfo [name=ModuleIdentifier{factoryName='distributed-operational-datastore-provider', instanceName='distributed-operational-store-module'}] to org.opendaylight.controller.config.yang.config.distributed_datastore_provider.DistributedOperationalDataStoreProviderModuleFactory@18015a7d
        at org.opendaylight.controller.config.manager.impl.ConfigTransactionControllerImpl.copyExistingModule(ConfigTransactionControllerImpl.java:220)
        at org.opendaylight.controller.config.manager.impl.ConfigTransactionControllerImpl.copyExistingModulesAndProcessFactoryDiff(ConfigTransactionControllerImpl.java:112)
        at org.opendaylight.controller.config.manager.impl.ConfigRegistryImpl.beginConfigSafe(ConfigRegistryImpl.java:240)
        at org.opendaylight.controller.config.manager.impl.ConfigRegistryImpl.beginConfig(ConfigRegistryImpl.java:198)
        at org.opendaylight.controller.config.manager.impl.osgi.BlankTransactionServiceTracker.lambda$new$0(BlankTransactionServiceTracker.java:40)
        at org.opendaylight.controller.config.manager.impl.osgi.BlankTransactionServiceTracker.blankTransactionSync(BlankTransactionServiceTracker.java:75)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
        at java.lang.Thread.run(Thread.java:748)
Caused by: java.lang.ClassCastException: org.opendaylight.controller.config.yang.config.distributed_datastore_provider.OperationalProperties cannot be cast to org.opendaylight.controller.config.yang.config.distributed_datastore_provider.OperationalProperties
        at org.opendaylight.controller.config.yang.config.distributed_datastore_provider.AbstractDistributedOperationalDataStoreProviderModuleFactory.handleChangedClass(AbstractDistributedOperationalDataStoreProviderModuleFactory.java:83)
        at org.opendaylight.controller.config.yang.config.distributed_datastore_provider.AbstractDistributedOperationalDataStoreProviderModuleFactory.createModule(AbstractDistributedOperationalDataStoreProviderModuleFactory.java:59)
        at org.opendaylight.controller.config.yang.config.distributed_datastore_provider.DistributedOperationalDataStoreProviderModuleFactory.createModule(DistributedOperationalDataStoreProviderModuleFactory.java:30)
        at org.opendaylight.controller.config.manager.impl.ConfigTransactionControllerImpl.copyExistingModule(ConfigTransactionControllerImpl.java:217)
        ... 8 more
Exception in thread "config-blank-txn-1" java.lang.IllegalStateException: Error while copying old configuration from ModuleInternalInfo [name=ModuleIdentifier{factoryName='distributed-operational-datastore-provider', instanceName='distributed-operational-store-module'}] to org.opendaylight.controller.config.yang.config.distributed_datastore_provider.DistributedOperationalDataStoreProviderModuleFactory@18015a7d
        at org.opendaylight.controller.config.manager.impl.ConfigTransactionControllerImpl.copyExistingModule(ConfigTransactionControllerImpl.java:220)
        at org.opendaylight.controller.config.manager.impl.ConfigTransactionControllerImpl.copyExistingModulesAndProcessFactoryDiff(ConfigTransactionControllerImpl.java:112)
        at org.opendaylight.controller.config.manager.impl.ConfigRegistryImpl.beginConfigSafe(ConfigRegistryImpl.java:240)
        at org.opendaylight.controller.config.manager.impl.ConfigRegistryImpl.beginConfig(ConfigRegistryImpl.java:198)
        at org.opendaylight.controller.config.manager.impl.osgi.BlankTransactionServiceTracker.lambda$new$0(BlankTransactionServiceTracker.java:40)
        at org.opendaylight.controller.config.manager.impl.osgi.BlankTransactionServiceTracker.blankTransactionSync(BlankTransactionServiceTracker.java:75)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
        at java.lang.Thread.run(Thread.java:748)
Caused by: java.lang.ClassCastException: org.opendaylight.controller.config.yang.config.distributed_datastore_provider.OperationalProperties cannot be cast to org.opendaylight.controller.config.yang.config.distributed_datastore_provider.OperationalProperties
        at org.opendaylight.controller.config.yang.config.distributed_datastore_provider.AbstractDistributedOperationalDataStoreProviderModuleFactory.handleChangedClass(AbstractDistributedOperationalDataStoreProviderModuleFactory.java:83)
        at org.opendaylight.controller.config.yang.config.distributed_datastore_provider.AbstractDistributedOperationalDataStoreProviderModuleFactory.createModule(AbstractDistributedOperationalDataStoreProviderModuleFactory.java:59)
        at org.opendaylight.controller.config.yang.config.distributed_datastore_provider.DistributedOperationalDataStoreProviderModuleFactory.createModule(DistributedOperationalDataStoreProviderModuleFactory.java:30)
        at org.opendaylight.controller.config.manager.impl.ConfigTransactionControllerImpl.copyExistingModule(ConfigTransactionControllerImpl.java:217)
        ... 8 more
Exception in thread "config-blank-txn-2" java.lang.IllegalStateException: Error while copying old configuration from ModuleInternalInfo [name=ModuleIdentifier{factoryName='distributed-operational-datastore-provider', instanceName='distributed-operational-store-module'}] to org.opendaylight.controller.config.yang.config.distributed_datastore_provider.DistributedOperationalDataStoreProviderModuleFactory@18015a7d
        at org.opendaylight.controller.config.manager.impl.ConfigTransactionControllerImpl.copyExistingModule(ConfigTransactionControllerImpl.java:220)
        at org.opendaylight.controller.config.manager.impl.ConfigTransactionControllerImpl.copyExistingModulesAndProcessFactoryDiff(ConfigTransactionControllerImpl.java:112)
        at org.opendaylight.controller.config.manager.impl.ConfigRegistryImpl.beginConfigSafe(ConfigRegistryImpl.java:240)
        at org.opendaylight.controller.config.manager.impl.ConfigRegistryImpl.beginConfig(ConfigRegistryImpl.java:198)
        at org.opendaylight.controller.config.manager.impl.osgi.BlankTransactionServiceTracker.lambda$new$0(BlankTransactionServiceTracker.java:40)
        at org.opendaylight.controller.config.manager.impl.osgi.BlankTransactionServiceTracker.blankTransactionSync(BlankTransactionServiceTracker.java:75)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
        at java.lang.Thread.run(Thread.java:748)
Caused by: java.lang.ClassCastException: org.opendaylight.controller.config.yang.config.distributed_datastore_provider.OperationalProperties cannot be cast to org.opendaylight.controller.config.yang.config.distributed_datastore_provider.OperationalProperties
        at org.opendaylight.controller.config.yang.config.distributed_datastore_provider.AbstractDistributedOperationalDataStoreProviderModuleFactory.handleChangedClass(AbstractDistributedOperationalDataStoreProviderModuleFactory.java:83)
        at org.opendaylight.controller.config.yang.config.distributed_datastore_provider.AbstractDistributedOperationalDataStoreProviderModuleFactory.createModule(AbstractDistributedOperationalDataStoreProviderModuleFactory.java:59)
        at org.opendaylight.controller.config.yang.config.distributed_datastore_provider.DistributedOperationalDataStoreProviderModuleFactory.createModule(DistributedOperationalDataStoreProviderModuleFactory.java:30)
        at org.opendaylight.controller.config.manager.impl.ConfigTransactionControllerImpl.copyExistingModule(ConfigTransactionControllerImpl.java:217)
        ... 8 more
Exception in thread "config-blank-txn-3" java.lang.IllegalStateException: Error while copying old configuration from ModuleInternalInfo [name=ModuleIdentifier{factoryName='distributed-operational-datastore-provider', instanceName='distributed-operational-store-module'}] to org.opendaylight.controller.config.yang.config.distributed_datastore_provider.DistributedOperationalDataStoreProviderModuleFactory@18015a7d
        at org.opendaylight.controller.config.manager.impl.ConfigTransactionControllerImpl.copyExistingModule(ConfigTransactionControllerImpl.java:220)
        at org.opendaylight.controller.config.manager.impl.ConfigTransactionControllerImpl.copyExistingModulesAndProcessFactoryDiff(ConfigTransactionControllerImpl.java:112)
        at org.opendaylight.controller.config.manager.impl.ConfigRegistryImpl.beginConfigSafe(ConfigRegistryImpl.java:240)
        at org.opendaylight.controller.config.manager.impl.ConfigRegistryImpl.beginConfig(ConfigRegistryImpl.java:198)
        at org.opendaylight.controller.config.manager.impl.osgi.BlankTransactionServiceTracker.lambda$new$0(BlankTransactionServiceTracker.java:40)
        at org.opendaylight.controller.config.manager.impl.osgi.BlankTransactionServiceTracker.blankTransactionSync(BlankTransactionServiceTracker.java:75)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
        at java.lang.Thread.run(Thread.java:748)
Caused by: java.lang.ClassCastException: org.opendaylight.controller.config.yang.config.distributed_datastore_provider.OperationalProperties cannot be cast to org.opendaylight.controller.config.yang.config.distributed_datastore_provider.OperationalProperties
        at org.opendaylight.controller.config.yang.config.distributed_datastore_provider.AbstractDistributedOperationalDataStoreProviderModuleFactory.handleChangedClass(AbstractDistributedOperationalDataStoreProviderModuleFactory.java:83)
        at org.opendaylight.controller.config.yang.config.distributed_datastore_provider.AbstractDistributedOperationalDataStoreProviderModuleFactory.createModule(AbstractDistributedOperationalDataStoreProviderModuleFactory.java:59)
        at org.opendaylight.controller.config.yang.config.distributed_datastore_provider.DistributedOperationalDataStoreProviderModuleFactory.createModule(DistributedOperationalDataStoreProviderModuleFactory.java:30)
        at org.opendaylight.controller.config.manager.impl.ConfigTransactionControllerImpl.copyExistingModule(ConfigTransactionControllerImpl.java:217)
        ... 8 more
Exception in thread "config-blank-txn-4" java.lang.IllegalStateException: Error while copying old configuration from ModuleInternalInfo [name=ModuleIdentifier{factoryName='distributed-operational-datastore-provider', instanceName='distributed-operational-store-module'}] to org.opendaylight.controller.config.yang.config.distributed_datastore_provider.DistributedOperationalDataStoreProviderModuleFactory@18015a7d
        at org.opendaylight.controller.config.manager.impl.ConfigTransactionControllerImpl.copyExistingModule(ConfigTransactionControllerImpl.java:220)
        at org.opendaylight.controller.config.manager.impl.ConfigTransactionControllerImpl.copyExistingModulesAndProcessFactoryDiff(ConfigTransactionControllerImpl.java:112)
        at org.opendaylight.controller.config.manager.impl.ConfigRegistryImpl.beginConfigSafe(ConfigRegistryImpl.java:240)
        at org.opendaylight.controller.config.manager.impl.ConfigRegistryImpl.beginConfig(ConfigRegistryImpl.java:198)
        at org.opendaylight.controller.config.manager.impl.osgi.BlankTransactionServiceTracker.lambda$new$0(BlankTransactionServiceTracker.java:40)
        at org.opendaylight.controller.config.manager.impl.osgi.BlankTransactionServiceTracker.blankTransactionSync(BlankTransactionServiceTracker.java:75)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
        at java.lang.Thread.run(Thread.java:748)
Caused by: java.lang.ClassCastException: org.opendaylight.controller.config.yang.config.distributed_datastore_provider.OperationalProperties cannot be cast to org.opendaylight.controller.config.yang.config.distributed_datastore_provider.OperationalProperties
        at org.opendaylight.controller.config.yang.config.distributed_datastore_provider.AbstractDistributedOperationalDataStoreProviderModuleFactory.handleChangedClass(AbstractDistributedOperationalDataStoreProviderModuleFactory.java:83)
        at org.opendaylight.controller.config.yang.config.distributed_datastore_provider.AbstractDistributedOperationalDataStoreProviderModuleFactory.createModule(AbstractDistributedOperationalDataStoreProviderModuleFactory.java:59)
        at org.opendaylight.controller.config.yang.config.distributed_datastore_provider.DistributedOperationalDataStoreProviderModuleFactory.createModule(DistributedOperationalDataStoreProviderModuleFactory.java:30)
        at org.opendaylight.controller.config.manager.impl.ConfigTransactionControllerImpl.copyExistingModule(ConfigTransactionControllerImpl.java:217)
        ... 8 more

ExistingModuleという文字も見え、機能がコンフリクトしているのか、依存関係の問題か、そのあたりの問題が起きていそう。karaf-0.8.4/data/logにkaraf.logがあることも発見したが、解析するより素直にHeliumを使った方が速そう。

MPLS-TE Path Protectionの設定

Eve-NGを使ってMPLS-TEの環境を作り、Path Protectionを試してみる。

Path Protectionについて

Path ProtectionはGlobal Repairと呼ばれる方式の1つであり、プライマリパスのどこかで障害が発生した際、あらかじめ準備しておいたセカンダリパスに切り替えることで復旧を行う。Local Repair方式でリンクやノードに対してProtectionを設定するFast Reroute(Link Protection/Node Protection)とは異なる。

参考:

環境

  • Eve-NG Community Edition Version 2.0.3-112
  • IOL Version 15.5(2)T

検証構成

f:id:tk_n:20210130135836p:plain

R1~R4で、Loopback I/Fも含めてOSPFを設定しておく。

設定

共通設定(R1~R4)

  • MPLS-TEの有効化
mpls traffic-eng tunnels
  • 各I/FでMPLS-TE有効化+RSVPで帯域確保
interface Ethernet0/0
 mpls traffic-eng tunnels
 ip rsvp bandwidth 300
!         
  • OSPF-TEの有効化
router ospf 1
 mpls traffic-eng router-id Loopback0
 mpls traffic-eng area 0
!

確認

R1#show ip rsvp interface 
interface    rsvp  allocated  i/f max  flow max sub max  VRF            
Et0/0        ena   0          300K     300K     0       
Et0/1        ena   0          300K     300K     0       

RSVPでI/Fごとに帯域が割り当てられているのがわかる。 (まだパスは作っていないのでallocatedは0)

OSPF-TEを有効にする時(もしくは割当帯域を変える時)にパケットキャプチャすると、LS Updateで帯域使用率等の情報が広報されるのが見える。 f:id:tk_n:20210130140230p:plain

プライマリパスの作成(R1)

プライマリパスはR1->R2->R3とする。 Head-EndとなるR1で、Tunnelインターフェースとして設定

interface Tunnel123
 description R1->R2->R3
 ip unnumbered Loopback0
 tunnel mode mpls traffic-eng
 tunnel destination 10.10.10.3
 tunnel mpls traffic-eng autoroute announce
 tunnel mpls traffic-eng bandwidth 150
 tunnel mpls traffic-eng path-option 10 explicit name PRIMARY
 tunnel mpls traffic-eng record-route
!
ip explicit-path name PRIMARY enable
 next-address 10.10.10.2
 next-address 10.10.10.3
!         

確認

R1#show mpls traffic-eng tunnels           

Name: R1->R2->R3                          (Tunnel123) Destination: 10.10.10.3
  Status:
    Admin: up         Oper: up     Path: valid       Signalling: connected
    path option 10, type explicit PRIMARY (Basis for Setup, path weight 20)

  Config Parameters:
    Bandwidth: 150      kbps (Global)  Priority: 7  7   Affinity: 0x0/0xFFFF
    Metric Type: TE (default)
    AutoRoute:  enabled   LockDown: disabled  Loadshare: 150      bw-based
    auto-bw: disabled
  Active Path Option Parameters:
    State: explicit path option 10 is active
    BandwidthOverride: disabled  LockDown: disabled  Verbatim: disabled

  InLabel  :  - 
  OutLabel : Ethernet0/0, 17
  RSVP Signalling Info:
       Src 10.10.10.1, Dst 10.10.10.3, Tun_Id 123, Tun_Instance 5
    RSVP Path Info:
      My Address: 192.168.1.1   
      Explicit Route: 192.168.1.2 192.168.2.1 192.168.2.2 10.10.10.3 
      Record   Route: 
      Tspec: ave rate=150 kbits, burst=1000 bytes, peak rate=150 kbits
    RSVP Resv Info:
      Record   Route:  192.168.2.1 192.168.2.2
      Fspec: ave rate=150 kbits, burst=1000 bytes, peak rate=150 kbits
  History:
    Tunnel:
      Time since created: 4 minutes, 25 seconds
      Time since path change: 1 minutes, 9 seconds
      Number of LSP IDs (Tun_Instances) used: 5
    Current LSP:
      Uptime: 1 minutes, 9 seconds
      Selection: reoptimization
    Prior LSP:
      ID: path option 10 [4]
      Removal Trigger: configuration changed

Statusがupになり、Explicit Routeが登録されている。

R1#show ip rsvp interface 
interface    rsvp  allocated  i/f max  flow max sub max  VRF            
Et0/0        ena   150K       300K     300K     0       
Et0/1        ena   0          300K     300K     0 

Et0/0がAllocatedになった。

R1#show ip rsvp reservation 
To            From          Pro DPort Sport Next Hop      I/F      Fi Serv BPS
10.10.10.3    10.10.10.1    0   123   5     192.168.1.2   Et0/0    SE LOAD 150K

reservationが登録されている。

R2#show mpls forwarding-table 
Local      Outgoing   Prefix           Bytes Label   Outgoing   Next Hop    
Label      Label      or Tunnel Id     Switched      interface              
17         Pop Label  10.10.10.1 123 [5]   \
                                       0             Et0/1      192.168.2.2 

R2でmpls forwarding-tableを見てみると、エントリが追加されている。

R1->R3へtraceroute

R1#traceroute 10.10.10.3
Type escape sequence to abort.
Tracing the route to 10.10.10.3
VRF info: (vrf in name/id, vrf out name/id)
  1 192.168.1.2 [MPLS: Label 17 Exp 0] 1 msec 1 msec 1 msec
  2 192.168.2.2 1 msec 1 msec * 

登録されたLSPを使ったMPLSでR2->R3と経由していることがわかる。

セカンダリパスの設定(R1)

セカンダリパスはR1->R4->R3とする。 プライマリ同様にHead-End(R1)で設定。

interface Tunnel123
 tunnel mpls traffic-eng path-option protect 10 explicit name SECONDARY
!
ip explicit-path name SECONDARY enable
 next-address 10.10.10.4
 next-address 10.10.10.3

確認

R1#show mpls traffic-eng tunnels 

Name: R1->R2->R3                          (Tunnel123) Destination: 10.10.10.3
  Status:
    Admin: up         Oper: up     Path: valid       Signalling: connected
    path option 10, type explicit PRIMARY (Basis for Setup, path weight 20)
    Path Protection: 0 Common Link(s), 0 Common Node(s)
    path protect option 10, type explicit SECONDARY (Basis for Protect, path weight 20)

  Config Parameters:
    Bandwidth: 150      kbps (Global)  Priority: 7  7   Affinity: 0x0/0xFFFF
    Metric Type: TE (default)
    AutoRoute:  enabled   LockDown: disabled  Loadshare: 150      bw-based
    auto-bw: disabled
  Active Path Option Parameters:
    State: explicit path option 10 is active
    BandwidthOverride: disabled  LockDown: disabled  Verbatim: disabled

  InLabel  :  - 
  OutLabel : Ethernet0/0, 17
  RSVP Signalling Info:
       Src 10.10.10.1, Dst 10.10.10.3, Tun_Id 123, Tun_Instance 5
    RSVP Path Info:
      My Address: 192.168.1.1   
      Explicit Route: 192.168.1.2 192.168.2.1 192.168.2.2 10.10.10.3 
      Record   Route: 
      Tspec: ave rate=150 kbits, burst=1000 bytes, peak rate=150 kbits
    RSVP Resv Info:
      Record   Route:  192.168.2.1 192.168.2.2
      Fspec: ave rate=150 kbits, burst=1000 bytes, peak rate=150 kbits
  History:
    Tunnel:
      Time since created: 27 minutes, 35 seconds
      Time since path change: 24 minutes, 20 seconds
      Number of LSP IDs (Tun_Instances) used: 5
    Current LSP:
      Uptime: 24 minutes, 20 seconds
      Selection: reoptimization
    Prior LSP:
      ID: path option 10 [4]
      Removal Trigger: configuration changed

path protect option 10, type explicit SECONDARYが追加された。 Path Protection:の行はプライマリパスと共有しているリンクとノードの数(Head-end, Tail-endは除く)なので、この構成の場合0となる。

R1#show mpls traffic-eng tunnels tunnel 123 protection 
R1->R2->R3
  LSP Head, Tunnel123, Admin: up, Oper: up
  Src 10.10.10.1, Dest 10.10.10.3, Instance 5
  Fast Reroute Protection: None
  Path Protection: 0 Common Link(s), 0 Common Node(s)
    Primary lsp path:192.168.1.1 192.168.1.2 
                     192.168.2.1 192.168.2.2 
                     10.10.10.3 
    Protect lsp path:192.168.3.1 192.168.3.2 
                     192.168.4.2 192.168.4.1 
                     10.10.10.3 
    Path Protect Parameters:
      Bandwidth: 150      kbps (Global)  Priority: 7  7   Affinity: 0x0/0xFFFF
      Metric Type: TE (default)
    InLabel  :  - 
    OutLabel : Ethernet0/1, 16
    RSVP Signalling Info:
         Src 10.10.10.1, Dst 10.10.10.3, Tun_Id 123, Tun_Instance 9
      RSVP Path Info:
        My Address: 192.168.3.1   
        Explicit Route: 192.168.3.2 192.168.4.2 192.168.4.1 10.10.10.3 
        Record   Route: 
        Tspec: ave rate=150 kbits, burst=1000 bytes, peak rate=150 kbits
      RSVP Resv Info:
        Record   Route:  192.168.4.2 192.168.4.1
        Fspec: ave rate=150 kbits, burst=1000 bytes, peak rate=150 kbits

Primary lspとProtect lspの経路情報が登録されている。

R1#show ip rsvp interface 
interface    rsvp  allocated  i/f max  flow max sub max  VRF            
Et0/0        ena   150K       300K     300K     0       
Et0/1        ena   150K       300K     300K     0     

R1#show ip rsvp reservation 
To            From          Pro DPort Sport Next Hop      I/F      Fi Serv BPS
10.10.10.3    10.10.10.1    0   123   5     192.168.1.2   Et0/0    SE LOAD 150K
10.10.10.3    10.10.10.1    0   123   9     192.168.3.2   Et0/1    SE LOAD 150K

Et0/1側も帯域がallocatedになった。セカンダリの分のreservationも登録されている。

R4#show mpls forwarding-table 
Local      Outgoing   Prefix           Bytes Label   Outgoing   Next Hop    
Label      Label      or Tunnel Id     Switched      interface              
16         Pop Label  10.10.10.1 123 [9]   \
                                       0             Et0/1      192.168.4.1 

R4のmpls forwarding-tableを確認。確かに登録されている。

動作確認

R2のEthernet0/1をshutしてみる。

R2(config)#int e 0/1
R2(config-if)#shut  
R2(config-if)#
*Jan 30 06:11:18.905: %OSPF-5-ADJCHG: Process 1, Nbr 192.168.4.1 on Ethernet0/
1 from FULL to DOWN, Neighbor Down: Interface down or detached
R2(config-if)#
*Jan 30 06:11:20.899: %LINK-5-CHANGED: Interface Ethernet0/1, changed state to
 administratively down
*Jan 30 06:11:21.908: %LINEPROTO-5-UPDOWN: Line protocol on Interface Ethernet
0/1, changed state to down

tunnel 123のプロテクション状態を確認

R1#show mpls traffic-eng tunnels tunnel 123 protection 
R1->R2->R3
  LSP Head, Tunnel123, Admin: up, Oper: up
  Src 10.10.10.1, Dest 10.10.10.3, Instance 9
  Fast Reroute Protection: None
  Path Protection: Backup lsp in use.

Path Protection: Backup lsp in use.となり、セカンダリが使われていることがわかる。

R1#traceroute 10.10.10.3
Type escape sequence to abort.
Tracing the route to 10.10.10.3
VRF info: (vrf in name/id, vrf out name/id)
  1 192.168.3.2 [MPLS: Label 16 Exp 0] 0 msec 2 msec 1 msec
  2 192.168.4.1 1 msec 1 msec * 

セカンダリパスを使って通信が行われている。

R1#show mpls traffic-eng tunnels
(中略)
InLabel  :  - 
  OutLabel : Ethernet0/1, 16
  RSVP Signalling Info:
       Src 10.10.10.1, Dst 10.10.10.3, Tun_Id 123, Tun_Instance 9
    RSVP Path Info:
      My Address: 192.168.3.1   
      Explicit Route: 192.168.3.2 192.168.4.2 192.168.4.1 10.10.10.3 
      Record   Route: 
      Tspec: ave rate=150 kbits, burst=1000 bytes, peak rate=150 kbits
    RSVP Resv Info:
      Record   Route:  192.168.4.2 192.168.4.1
      Fspec: ave rate=150 kbits, burst=1000 bytes, peak rate=150 kbits
  History:
    Tunnel:
      Time since created: 54 minutes, 58 seconds
      Time since path change: 5 minutes, 29 seconds
      Number of LSP IDs (Tun_Instances) used: 9
    Current LSP:
      Uptime: 27 minutes, 42 seconds
      Selection: 
    Prior LSP:
      ID: path option 10 [5]
      Removal Trigger: path error
      Last Error: PCALC:: No addresses to connect 10.10.10.2 to 10.10.10.3

Explicit Routeがセカンダリのものになっている。また、History欄に10.10.10.2-10.10.10.3間で疎通が取れなくなったことが記録されている。

R1#show ip rsvp int         
interface    rsvp  allocated  i/f max  flow max sub max  VRF            
Et0/0        ena   0          300K     300K     0       
Et0/1        ena   150K       300K     300K     0 

帯域割り当てもプライマリ側は解除されている。

参考

LinuxでHTTPS通信時にデフォルトで使用される証明書ファイルの場所

はじめに

HTTPS通信をする際、サーバ側からはサーバ証明書(+中間証明書)を提示し、クライアント側では予めインストールされた信頼済みルート証明書を使って、サーバから提示されたサーバ証明書を検証する(※)。

※片方向TLSの場合。双方向の場合はクライアント側からもクライアント証明書を提示する。

サーバ側で使うサーバ証明書はWebサーバとなるソフトウェアの設定ファイル等で指定されるが、クライアント側のルート証明書は何が使われるのだろうか。Windows/MacWebブラウザがクライアントとなる場合の情報はどこにでもあるが、Linuxでクライアントが非ブラウザの場合の情報があまりなさそうだったので、調べた範囲でまとめてみる。ブラウザだったら証明書を無視して表示することもできるが、独自アプリケーションやスクリプトLinux間REST通信をする際にHTTPSを使っていたりすると、証明書の検証エラーは通信断を意味するので・・・。

(参考までに)サーバ側

Webサーバとなるソフトウェアの設定ファイルで、使用するサーバ証明書(+中間証明書)を指定する。

Apache + OpenSSL (mod_ssl) の場合

Apacheの設定ファイル(CentOS系の場合、/etc/httpd/conf.d/ssl.conf)でサーバ証明書秘密鍵の場所を定義

# less /etc/httpd/conf.d/ssl.conf
...
SSLCertificateFile /etc/pki/tls/certs/server.crt
SSLCertificateKeyFile /etc/pki/tls/private/server.key
...

Tomcatの場合

Tomcatサーバの設定ファイル(server.xml)内のSSL設定部分でサーバ証明書秘密鍵が含まれるキーストアファイルを指定

# less /opt/apache-tomcat-9.0.34/conf/server.xml
...
<Connector port="8443" protocol="org.apache.coyote.http11.Http11NioProtocol"
               maxThreads="150" SSLEnabled="true">
        <SSLHostConfig>
            <Certificate certificateKeystoreFile="conf/keystore.jks"
                         certificationKeystorePassword="changeit"
                         certificationKeyAlias="tomcat"
                         type="RSA"
                           />
        </SSLHostConfig>
    </Connector>
...

クライアント側

クライアントとなるアプリケーションによって異なる。 アプリケーション内で設定しない場合や実行時オプション指定で明に指定しない場合は、デフォルト値が使用される。デフォルト値はアプリケーションと環境(ディストリビューションとそのバージョン、クライアントアプリケーションのビルド時の設定等)によって異なる。

以下、クライアントとしてcurl, openssl, Javaアプリケーションの場合について記載する。

curlの場合

以下、①②の設定値があり、①→②の順に参照する。

①CAfile:使用するCA証明書ファイル(PEM形式)。複数の証明書が含まれてもよい。--cacertオプションで指定可能。
②CApath:CA証明書が格納されたディレクトリ。CA証明書(もしくは証明書へのシンボリックリンク)は{hash}.0という名前になっている必要がある。 {hash}は証明書のsubjectをハッシュ化した値。openssl x509 -in <証明書ファイル> -hash -nooutで確認できる。--capathオプションで指定可能。

①②について、オプションで指定しなかった場合、デフォルト値が使用される。

デフォルト値はどこで決まるか

デフォルト値はビルド時のオプションで指定可能。

例えば下記のようにcurl 7.58.0のソースをダウンロードし、configure --helpで説明が表示される。

# curl -O https://curl.haxx.se/download/curl-7.58.0.tar.gz
# tar xvzf curl-7.58.0.tar.gz
# cd curl-7.58.0
# ./configure --help
...
  --with-ca-bundle=FILE   Path to a file containing CA certificates (example:
                          /etc/ca-bundle.crt)
  ...
  --with-ca-path=DIRECTORY
                          Path to a directory containing CA certificates
                          stored individually, with their filenames in a hash
                          format. This option can be used with OpenSSL, GnuTLS
                          and PolarSSL backends. Refer to OpenSSL c_rehash for
                          details. (example: /etc/certificates)
...

CentOS 7.6.1810で実際にconfigureを実施した結果は以下のようになった。

# ./configure --enable-libcurl-option
...
configure: Configured to build curl/libcurl:

  curl version:     7.58.0
  Host setup:       x86_64-pc-linux-gnu
  Install prefix:   /usr/local
  Compiler:         gcc
  SSL support:      no      (--with-{ssl,gnutls,nss,polarssl,mbedtls,cyassl,axtls,winssl,darwinssl} )
  SSH support:      no      (--with-libssh2)
  zlib support:     no      (--with-zlib)
  brotli support:   no      (--with-brotli)
  GSS-API support:  no      (--with-gssapi)
  TLS-SRP support:  no      (--enable-tls-srp)
  resolver:         POSIX threaded
  IPv6 support:     enabled
  Unix sockets support: enabled
  IDN support:      no      (--with-{libidn2,winidn})
  Build libcurl:    Shared=yes, Static=yes
  Built-in manual:  enabled
  --libcurl option: enabled (--disable-libcurl-option)
  Verbose errors:   enabled (--disable-verbose)
  SSPI support:     no      (--enable-sspi)
  ca cert bundle:   /etc/pki/tls/certs/ca-bundle.crt
  ca cert path:     no
  ca fallback:      no
  LDAP support:     no      (--enable-ldap / --with-ldap-lib / --with-lber-lib)
  LDAPS support:    no      (--enable-ldaps)
  RTSP support:     enabled
  RTMP support:     no      (--with-librtmp)
  metalink support: no      (--with-libmetalink)
  PSL support:      no      (libpsl not found)
  HTTP2 support:    disabled (--with-nghttp2)
  Protocols:        DICT FILE FTP GOPHER HTTP IMAP POP3 RTSP SMTP TELNET TFTP

①CAfileは/etc/pki/tls/certs/ca-bundle.crt、②CApathは設定なしとなった。また、ca fallbackについては、①②共にサーバ証明書の検証に失敗した場合に、OS標準の証明書ストアを使用するか、を設定するものと思われるが、これはまだ検証していない。

現在設定されているデフォルト値の確認方法

ではOSに最初から入っているcurlの場合や、yum/aptでインストールした場合はどうしたらいいのだろうか?探した限りでは、あまり標準的なやり方はない模様。

serverfault.com

こちらでも議論されており、様々な方法が提案されているが、個人的にはオプション-vをつけてhttpsサーバのURLにアクセスするのが一番シンプルだと思った(実際にping疎通可能なhttpsサーバがいることが前提だが...)。SSLセッション確立が成功しても失敗しても、CAfileとCApathのデフォルト値が表示される。

CentOS 7.6.1810, curl 7.29.0で実行した場合の例: 192.168.12.22(webserver)ではapache + mod_sslでWebサーバが動いており、オレオレ証明書(CN=webserver)が設定されている。

[root@localhost ~]# curl -v https://webserver/
* About to connect() to webserver port 443 (#0)
*   Trying 192.168.12.22...
* Connected to webserver (192.168.12.22) port 443 (#0)
* Initializing NSS with certpath: sql:/etc/pki/nssdb
*   CAfile: /etc/pki/tls/certs/ca-bundle.crt
  CApath: none
* Server certificate:
*       subject: CN=webserver,O=Default Company Ltd,L=Default City,ST=tokyo,C=JP
*       start date: Apr 29 09:15:04 2020 GMT
*       expire date: May 29 09:15:04 2020 GMT
*       common name: webserver
*       issuer: CN=webserver,O=Default Company Ltd,L=Default City,ST=tokyo,C=JP
* NSS error -8172 (SEC_ERROR_UNTRUSTED_ISSUER)
* Peer's certificate issuer has been marked as not trusted by the user.
* Closing connection 0
curl: (60) Peer's certificate issuer has been marked as not trusted by the user.
More details here: http://curl.haxx.se/docs/sslcerts.html

curl performs SSL certificate verification by default, using a "bundle"
 of Certificate Authority (CA) public keys (CA certs). If the default
 bundle file isn't adequate, you can specify an alternate file
 using the --cacert option.
If this HTTPS server uses a certificate signed by a CA represented in
 the bundle, the certificate verification probably failed due to a
 problem with the certificate (it might be expired, or the name might
 not match the domain name in the URL).
If you'd like to turn off curl's verification of the certificate, use
 the -k (or --insecure) option.

CAfile: /etc/pki/tls/certs/ca-bundle.crt
CApath: none (設定なし)
が確認できる。

実際のデフォルト値はどうなっているか

基本的にはディストリビューションにより異なっている模様。またバージョンによって証明書を配置するディレクトリの構成が異なっている。

CentOS 6, CentOS 7, Ubuntu 18.04にバンドルされているcurlについて調査した。結果を先に記載する。

OS アプリとバージョン デフォルトで参照するルート証明書の場所
CentOS 6.10 curl 7.19.7 CAfile: /etc/pki/tls/certs/ca-bundle.crt
CApath: none
CentOS 7.6.1810 curl 7.29.0 CAfile: /etc/pki/tls/certs/ca-bundle.crt -> /etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem
CApath: none
Ubuntu 18.04 curl 7.58.0 CAfile: /etc/ssl/certs/ca-certificates.crt
CApath: /etc/ssl/certs

curlSSL系のライブラリとして、NSSを使用する場合とOpenSSLを使う場合があり、それによっても違うのかとも思ったが、調べた限り上記デフォルト値の考え方自体は変わらないように見えた。使用ライブラリについては、curl -Vでバージョンと共に確認可能。

以下確認の詳細。

CentOS 6

[root@centos6 ~]# cat /etc/redhat-release
CentOS release 6.10 (Final)

[root@centos6 ~]# curl -V
curl 7.19.7 (x86_64-redhat-linux-gnu) libcurl/7.19.7 NSS/3.27.1 zlib/1.2.3 libidn/1.18 libssh2/1.4.2
Protocols: tftp ftp telnet dict ldap ldaps http file https ftps scp sftp
Features: GSS-Negotiate IDN IPv6 Largefile NTLM SSL libz

[root@centos6 ~]# curl -v https://webserver/
* About to connect() to webserver port 443 (#0)
*   Trying 192.168.12.22... connected
* Connected to webserver (192.168.12.22) port 443 (#0)
* Initializing NSS with certpath: sql:/etc/pki/nssdb
*   CAfile: /etc/pki/tls/certs/ca-bundle.crt
  CApath: none
* Certificate is signed by an untrusted issuer: 'CN=webserver,O=Default Company Ltd,L=Default City,ST=tokyo,C=JP'
* NSS error -8172
* Closing connection #0
* Peer certificate cannot be authenticated with known CA certificates
curl: (60) Peer certificate cannot be authenticated with known CA certificates
More details here: http://curl.haxx.se/docs/sslcerts.html

curl performs SSL certificate verification by default, using a "bundle"
 of Certificate Authority (CA) public keys (CA certs). If the default
 bundle file isn't adequate, you can specify an alternate file
 using the --cacert option.
If this HTTPS server uses a certificate signed by a CA represented in
 the bundle, the certificate verification probably failed due to a
 problem with the certificate (it might be expired, or the name might
 not match the domain name in the URL).
If you'd like to turn off curl's verification of the certificate, use
 the -k (or --insecure) option.

CAfile: /etc/pki/tls/certs/ca-bundle.crt
CApath: none
であることがわかる。

/etc/pki/tls/certsを実際に確認してみる。

[root@centos6 ~]# ll /etc/pki/tls/certs
total 1164
-rw-r--r--. 1 root root 755417 May  4 01:57 ca-bundle.crt
-rw-r--r--. 1 root root 418126 Feb 28  2018 ca-bundle.trust.crt
-rwxr-xr-x. 1 root root    610 Aug 14  2019 make-dummy-cert
-rw-r--r--. 1 root root   2242 Aug 14  2019 Makefile
-rwxr-xr-x. 1 root root    829 Aug 14  2019 renew-dummy-cert

ca-bundle.crtの実体が格納されている。

CentOS 7

[root@localhost ~]# cat /etc/redhat-release
CentOS Linux release 7.6.1810 (Core)

[root@localhost ~]# curl -V
curl 7.29.0 (x86_64-redhat-linux-gnu) libcurl/7.29.0 NSS/3.36 zlib/1.2.7 libidn/1.28 libssh2/1.4.3
Protocols: dict file ftp ftps gopher http https imap imaps ldap ldaps pop3 pop3s rtsp scp sftp smtp smtps telnet tftp
Features: AsynchDNS GSS-Negotiate IDN IPv6 Largefile NTLM NTLM_WB SSL libz unix-sockets

[root@localhost ~]# curl -v https://webserver/
* About to connect() to webserver port 443 (#0)
*   Trying 192.168.12.22...
* Connected to webserver (192.168.12.22) port 443 (#0)
* Initializing NSS with certpath: sql:/etc/pki/nssdb
*   CAfile: /etc/pki/tls/certs/ca-bundle.crt
  CApath: none
* Server certificate:
*       subject: CN=webserver,O=Default Company Ltd,L=Default City,ST=tokyo,C=JP
*       start date: Apr 29 09:15:04 2020 GMT
*       expire date: May 29 09:15:04 2020 GMT
*       common name: webserver
*       issuer: CN=webserver,O=Default Company Ltd,L=Default City,ST=tokyo,C=JP
* NSS error -8172 (SEC_ERROR_UNTRUSTED_ISSUER)
* Peer's certificate issuer has been marked as not trusted by the user.
* Closing connection 0
curl: (60) Peer's certificate issuer has been marked as not trusted by the user.
More details here: http://curl.haxx.se/docs/sslcerts.html

curl performs SSL certificate verification by default, using a "bundle"
 of Certificate Authority (CA) public keys (CA certs). If the default
 bundle file isn't adequate, you can specify an alternate file
 using the --cacert option.
If this HTTPS server uses a certificate signed by a CA represented in
 the bundle, the certificate verification probably failed due to a
 problem with the certificate (it might be expired, or the name might
 not match the domain name in the URL).
If you'd like to turn off curl's verification of the certificate, use
 the -k (or --insecure) option.

CAfile: /etc/pki/tls/certs/ca-bundle.crt
CApath: none
であることがわかる。(CentOS 6と同様)

/etc/pki/tls/certsを実際に確認してみる。

[root@localhost ~]# ll /etc/pki/tls/certs
total 16
lrwxrwxrwx. 1 root root   49 May  4 12:51 ca-bundle.crt -> /etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem
lrwxrwxrwx. 1 root root   55 Jun  1  2019 ca-bundle.trust.crt -> /etc/pki/ca-trust/extracted/openssl/ca-bundle.trust.crt
-rw-------. 1 root root 1468 Apr 11 08:28 localhost.crt
-rwxr-xr-x. 1 root root  610 Mar 12  2019 make-dummy-cert
-rw-r--r--. 1 root root 2516 Mar 12  2019 Makefile
-rwxr-xr-x. 1 root root  829 Mar 12  2019 renew-dummy-cert

ca-bundle.crtがシンボリックリンクになっている。

s-tajima.hateblo.jp

CentOS7から証明書の管理方針が変わり、update-ca-trustで管理するようになっている。

Ubuntu 18.04

バージョン確認

root@ubuntu-bionic:~# cat /etc/os-release
NAME="Ubuntu"
VERSION="18.04.3 LTS (Bionic Beaver)"
ID=ubuntu
ID_LIKE=debian
PRETTY_NAME="Ubuntu 18.04.3 LTS"
VERSION_ID="18.04"
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
VERSION_CODENAME=bionic
UBUNTU_CODENAME=bionic

root@ubuntu-bionic:~# curl -V
curl 7.58.0 (x86_64-pc-linux-gnu) libcurl/7.58.0 OpenSSL/1.1.1 zlib/1.2.11 libidn2/2.0.4 libpsl/0.19.1 (+libidn2/2.0.4) nghttp2/1.30.0 librtmp/2.3
Release-Date: 2018-01-24
Protocols: dict file ftp ftps gopher http https imap imaps ldap ldaps pop3 pop3s rtmp rtsp smb smbs smtp smtps telnet tftp
Features: AsynchDNS IDN IPv6 Largefile GSS-API Kerberos SPNEGO NTLM NTLM_WB SSL libz TLS-SRP HTTP2 UnixSockets HTTPS-proxy PSL

ライブラリとしてNSSではなくOpenSSLを使っている。

root@ubuntu-bionic:~# curl -v https://webserver/
*   Trying 192.168.12.22...
* TCP_NODELAY set
* Connected to webserver (192.168.12.22) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* successfully set certificate verify locations:
*   CAfile: /etc/ssl/certs/ca-certificates.crt
  CApath: /etc/ssl/certs
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.2 (IN), TLS handshake, Certificate (11):
* TLSv1.2 (OUT), TLS alert, Server hello (2):
* SSL certificate problem: self signed certificate
* stopped the pause stream!
* Closing connection 0
curl: (60) SSL certificate problem: self signed certificate
More details here: https://curl.haxx.se/docs/sslcerts.html

curl failed to verify the legitimacy of the server and therefore could not
establish a secure connection to it. To learn more about this situation and
how to fix it, please visit the web page mentioned above.

CAfile: /etc/ssl/certs/ca-certificates.crt
CApath: /etc/ssl/certs
であることがわかる。

/etc/ssl/certs/を確認する。

root@ubuntu-bionic:~# ll /etc/ssl/certs
total 632
drwxr-xr-x 3 root root  16384 May  4 09:12  ./
drwxr-xr-x 4 root root   4096 Dec 18 19:12  ../
lrwxrwxrwx 1 root root     45 May  2 13:35  02265526.0 -> Entrust_Root_Certification_Authority_-_G2.pem
lrwxrwxrwx 1 root root     36 May  2 13:35  03179a64.0 -> Staat_der_Nederlanden_EV_Root_CA.pem
lrwxrwxrwx 1 root root     27 May  2 13:35  062cdee6.0 -> GlobalSign_Root_CA_-_R3.pem
lrwxrwxrwx 1 root root     25 May  2 13:35  064e0aa9.0 -> QuoVadis_Root_CA_2_G3.pem
lrwxrwxrwx 1 root root     50 May  2 13:35  06dc52d5.0 -> SSL.com_EV_Root_Certification_Authority_RSA_R2.pem
...以下略。ルート証明書へのシンボリックリンクがずらっと入っている。

root@ubuntu-bionic:~# ll /etc/ssl/certs/ca-certificates.crt
-rw-r--r-- 1 root root 207436 May  4 09:12 /etc/ssl/certs/ca-certificates.crt

/etc/ssl/certsには、信頼済みルート証明書へのシンボリックリンクと、信頼済みルート証明書の実体がバンドルされたファイルca-certificates.crtが入っている。

ちなみに、下記の方法(update-ca-certificates)で独自のルート証明書を追加した場合、シンボリックリンクの作成とca-certificates.crtへの追加の両方が実行される。

qiita.com

opensslの場合

openssl s_client -connect ...でクライアントアプリとして使う場合を想定。

curlと同様、①CAfileと②CApathで設定する。 参照する順番はcurlと同様①→②。 本家のドキュメントにも記載があった。

https://www.openssl.org/docs/man1.1.1/man3/SSL_CTX_load_verify_locations.html

When looking up CA certificates, the OpenSSL library will first search the certificates in CAfile, then those in CApath.

デフォルト値はどこで決まるか

先ほどと同じ本家のページに記載があった。

The default CA certificates directory is called "certs" in the default OpenSSL directory.

(中略)

The default CA certificates file is called "cert.pem" in the default OpenSSL directory.

CAfile: "OpenSSL directory"配下のcert.pem
CApath: "OpenSSL directory"配下のcertsディレクト
となる。

こちらのページ によると、cryptlib.hで定義されている模様。

"OpenSSL directory"はビルド時に設定可能。また環境変数での定義も可能。OpenSSLのソースをダウンロードし、フォルダ配下のINSTALLファイルを確認すると記載がある。

下記、OpenSSL 1.1.1で確認:

# less INSTALL
...
--openssldir=DIR
                   Directory for OpenSSL configuration files, and also the
                   default certificate and key store.  Defaults are:

                   Unix:           /usr/local/ssl
                   Windows:        C:\Program Files\Common Files\SSL
                                or C:\Program Files (x86)\Common Files\SSL
                   OpenVMS:        SYS$COMMON:[OPENSSL-COMMON]

Unixではデフォルトは/usr/local/sslになるようだ。

現在設定されているデフォルト値の確認方法

デフォルトのCAfileとCApathのファイル名/ディレクトリ名は上記の通り固定値のようなので、"OPENSSLDIR"を確認すればよい。

stackoverflow.com

openssl version -aで確認できるようだ。

実際のデフォルト値はどうなっているか

CentOS 6, CentOS 7, Ubuntu 18.04にバンドルされているopensslについて調査した。先に結果を記載する。

OS アプリとバージョン デフォルトで参照するルート証明書の場所
CentOS 6.10 OpenSSL 1.0.1e-fips CAfile: /etc/pki/tls/cert.pem -> /etc/pki/tls/certs/ca-bundle.crt
CApath: /etc/pki/tls/certs
CentOS 7.6.1810 OpenSSL 1.0.2k-fips CAfile: /etc/pki/tls/cert.pem -> /etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem
CApath: /etc/pki/tls/certs
Ubuntu 18.04 OpenSSL 1.1.1 CAfile: なし?
CApath: /usr/lib/ssl/certs -> /etc/ssl/certs/

以下、確認の詳細。

CentOS 6

[root@centos6 ~]# openssl version -a
OpenSSL 1.0.1e-fips 11 Feb 2013
built on: Wed Aug 14 16:32:19 UTC 2019
platform: linux-x86_64
options:  bn(64,64) md2(int) rc4(16x,int) des(idx,cisc,16,int) idea(int) blowfish(idx)
compiler: gcc -fPIC -DOPENSSL_PIC -DZLIB -DOPENSSL_THREADS -D_REENTRANT -DDSO_DLFCN -DHAVE_DLFCN_H -DKRB5_MIT -m64 -DL_ENDIAN -DTERMIO -Wall -O2 -g -pipe -Wall -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector --param=ssp-buffer-size=4 -m64 -mtune=generic -Wa,--noexecstack -DPURIFY -DOPENSSL_IA32_SSE2 -DOPENSSL_BN_ASM_MONT -DOPENSSL_BN_ASM_MONT5 -DOPENSSL_BN_ASM_GF2m -DSHA1_ASM -DSHA256_ASM -DSHA512_ASM -DMD5_ASM -DAES_ASM -DVPAES_ASM -DBSAES_ASM -DWHIRLPOOL_ASM -DGHASH_ASM
OPENSSLDIR: "/etc/pki/tls"
engines:  rdrand dynamic

OPENSSLDIRは/etc/pki/tlsとなっている。 確認してみる。

[root@centos6 ~]# ll /etc/pki/tls
total 24
lrwxrwxrwx. 1 root root    19 Nov 21 16:58 cert.pem -> certs/ca-bundle.crt
drwxr-xr-x. 2 root root  4096 May  4 22:00 certs
drwxr-xr-x. 2 root root  4096 Nov 21 17:01 misc
-rw-r--r--. 1 root root 10906 Jul  2  2019 openssl.cnf
drwxr-xr-x. 2 root root  4096 Aug 14  2019 private

cert.pemはシンボリックリンクになっているので、certs配下を確認。

[root@centos6 ~]# ll /etc/pki/tls/certs
total 1164
-rw-r--r--. 1 root root 755417 May  4 01:57 ca-bundle.crt
-rw-r--r--. 1 root root 418126 Feb 28  2018 ca-bundle.trust.crt
-rwxr-xr-x. 1 root root    610 Aug 14  2019 make-dummy-cert
-rw-r--r--. 1 root root   2242 Aug 14  2019 Makefile
-rwxr-xr-x. 1 root root    829 Aug 14  2019 renew-dummy-cert

ca-bundle.crtの実体があった。

CentOS 7

[root@localhost ~]# openssl version -a
OpenSSL 1.0.2k-fips  26 Jan 2017
built on: reproducible build, date unspecified
platform: linux-x86_64
options:  bn(64,64) md2(int) rc4(16x,int) des(idx,cisc,16,int) idea(int) blowfish(idx)
compiler: gcc -I. -I.. -I../include  -fPIC -DOPENSSL_PIC -DZLIB -DOPENSSL_THREADS -D_REENTRANT -DDSO_DLFCN -DHAVE_DLFCN_H -DKRB5_MIT -m64 -DL_ENDIAN -Wall -O2 -g -pipe -Wall -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector-strong --param=ssp-buffer-size=4 -grecord-gcc-switches   -m64 -mtune=generic -Wa,--noexecstack -DPURIFY -DOPENSSL_IA32_SSE2 -DOPENSSL_BN_ASM_MONT -DOPENSSL_BN_ASM_MONT5 -DOPENSSL_BN_ASM_GF2m -DRC4_ASM -DSHA1_ASM -DSHA256_ASM -DSHA512_ASM -DMD5_ASM -DAES_ASM -DVPAES_ASM -DBSAES_ASM -DWHIRLPOOL_ASM -DGHASH_ASM -DECP_NISTZ256_ASM
OPENSSLDIR: "/etc/pki/tls"
engines:  rdrand dynamic

OPENSSLDIRはCentOS6同様、/etc/pki/tlsになっている。

[root@localhost ~]# ll /etc/pki/tls
total 12
lrwxrwxrwx. 1 root root    49 May  4 04:46 cert.pem -> /etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem
drwxr-xr-x. 2 root root   138 May  5 05:11 certs
drwxr-xr-x. 2 root root    74 Jun  1  2019 misc
-rw-r--r--. 1 root root 10923 May  4 04:50 openssl.cnf
drwxr-xr-x. 2 root root    45 Apr 11 12:26 private

cert.pemの実体はCentOS6と異なり、例のupdate-ca-trustによって管理されている場所にあるようだ。 また、certsディレクトリは以下のとおり。

[root@localhost ~]# ll /etc/pki/tls/certs
total 16
lrwxrwxrwx. 1 root root   49 May  4 12:51 ca-bundle.crt -> /etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem
lrwxrwxrwx. 1 root root   55 Jun  1  2019 ca-bundle.trust.crt -> /etc/pki/ca-trust/extracted/openssl/ca-bundle.trust.crt
-rw-------. 1 root root 1468 Apr 11 08:28 localhost.crt
-rwxr-xr-x. 1 root root  610 Mar 12  2019 make-dummy-cert
-rw-r--r--. 1 root root 2516 Mar 12  2019 Makefile
-rwxr-xr-x. 1 root root  829 Mar 12  2019 renew-dummy-cert

curl用と思われるca-bundle.crt(シンボリックリンク)等が入っているが、Ubuntuと違い、信頼済みルートCA証明書のシンボリックリンク一覧は入っていない。openssl s_clientは当ディレクトリ配下の{hash}.0ファイルのみしか見ないため、これらの.crtファイルは無視される模様。

Ubuntu 18.04

root@ubuntu-bionic:~# openssl version -a
OpenSSL 1.1.1  11 Sep 2018
built on: Tue Nov 12 16:58:35 2019 UTC
platform: debian-amd64
options:  bn(64,64) rc4(16x,int) des(int) blowfish(ptr)
compiler: gcc -fPIC -pthread -m64 -Wa,--noexecstack -Wall -Wa,--noexecstack -g -O2 -fdebug-prefix-map=/build/openssl-kxN_24/openssl-1.1.1=. -fstack-protector-strong -Wformat -Werror=format-security -DOPENSSL_USE_NODELETE -DL_ENDIAN -DOPENSSL_PIC -DOPENSSL_CPUID_OBJ -DOPENSSL_IA32_SSE2 -DOPENSSL_BN_ASM_MONT -DOPENSSL_BN_ASM_MONT5 -DOPENSSL_BN_ASM_GF2m -DSHA1_ASM -DSHA256_ASM -DSHA512_ASM -DKECCAK1600_ASM -DRC4_ASM -DMD5_ASM -DAES_ASM -DVPAES_ASM -DBSAES_ASM -DGHASH_ASM -DECP_NISTZ256_ASM -DX25519_ASM -DPADLOCK_ASM -DPOLY1305_ASM -DNDEBUG -Wdate-time -D_FORTIFY_SOURCE=2
OPENSSLDIR: "/usr/lib/ssl"
ENGINESDIR: "/usr/lib/x86_64-linux-gnu/engines-1.1"
Seeding source: os-specific

OPENSSLDIRは/usr/lib/ssl

root@ubuntu-bionic:~# ll /usr/lib/ssl
total 12
drwxr-xr-x  3 root root 4096 Dec 18 19:12 ./
drwxr-xr-x 68 root root 4096 May  3 11:50 ../
lrwxrwxrwx  1 root root   14 Apr 25  2018 certs -> /etc/ssl/certs/
drwxr-xr-x  2 root root 4096 Dec 18 19:12 misc/
lrwxrwxrwx  1 root root   20 Nov 12 16:58 openssl.cnf -> /etc/ssl/openssl.cnf
lrwxrwxrwx  1 root root   16 Apr 25  2018 private -> /etc/ssl/private/

certsディレクトリ (-> /etc/ssl/certs)はあるが、cert.pemは見当たらない・・・。CAfileの設定なしでビルドされたから?試しに/usr/lib/ssl配下にcert.pemを作ってみたが、CAfileとしては認識されなかった。

Javaアプリケーションの場合

${JAVA_HOME}/jre/lib/security/cacertsが参照される、というのが定説。

それ以外に、

といった方法があるようだ。

Javaアプリケーションでは、信頼するCAを「(JREディレクトリ)/lib/security/cacerts」キーストアか、「javax.net.ssl.trustStore」プロパティで指定されるキーストアによって管理しています。デフォルトではjavax.net.ssl.trustStoreプロパティは指定されませんので、「(JREディレクトリ)/lib/security/cacerts」キーストアに証明書が登録されている、CAを信頼します。

11. 認証方式 (5) | TECHSCORE(テックスコア)

システムプロパティの"javax.net.ssl.trustStore"を設定した場合、そちらが参照される。システムプロパティの設定方法としては、Javaの実行時オプションとして設定する方法と、Javaプログラムの中で指定する方法がある。

Javaの実行時オプションとして設定する方法

実行時オプションとして、システムプロパティ"javax.net.ssl.trustStore"を設定する。

# java -Djavax.net.ssl.trustStore=<cacertsへのフルパス> -Djavax.
net.ssl.trustStorePassword=changeit <class名>

Javaプログラムの中で指定する方法

a4dosanddos.hatenablog.com

上記サイトを参考にさせていただいてます。

システムプロパティ"javax.net.ssl.trustStore"を設定

System.setProperty("javax.net.ssl.trustStore", "キーストア(トラストストア)のパス");
System.setProperty("javax.net.ssl.trustStorePassword ", "パスワード(default: changeit)");

その他、SSLSocketFactoryを使って自力でゴリゴリと設定する方法もあるようだ。

まとめると、Javaアプリケーションがどのルート証明書を参照するかは「アプリの作り次第」である。アプリ内で設定していればその場所が参照されるし、していなければデフォルトの場所が参照される。

実際のデフォルト値はどうなっているか

アプリケーションや実行時オプションで指定しなかった場合のデフォルト参照先について、調査結果を先にまとめておく。

OS Java Javaが参照するルート証明書の場所
CentOS 6.10 OpenJDK 1.8.0 ${JAVA_HOME}/jre/lib/security/cacerts -> /etc/pki/java/cacerts
CentOS 7.6.1810 OpenJDK 1.8.0 /etc/pki/java/cacerts -> /etc/pki/ca-trust/extracted/java/cacerts
Ubuntu 18.04 OpenJDK 1.8.0 ${JAVA_HOME}/jre/lib/security/cacerts -> /etc/ssl/certs/java/cacerts
  • CentOS7だけ異なり、${JAVA_HOME}/jre/lib/security/cacertsではなく、OSの証明書ストア(/etc/pki/java/cacerts)が参照されていた。理由は不明。
  • 今回調査した環境ではいずれも${JAVA_HOME}/jre/lib/security/cacertsはOSの証明書ストアへのシンボリックリンクになっていたが、私が知っている某商用環境(RHEL6, 7 + Oracle JDK 1.8)では${JAVA_HOME}/jre/lib/security/cacertsに実体があった。これはOpenJDKとOracleJDKの違いかもしれない。この場合、curlやopensslでルート証明書のテストをして問題ないとなっても、javaだとエラーになるケースも想定されるため、注意が必要。
  • OpenJDK 1.8.0しか調べていないので、他のバージョンだと変わるかもしれない。

security.data-site.info

CentOS6 + OpenJDK 1.8.0

OpenJDKはyumでインストールしたもの。

[root@centos6 ~]# cat /etc/redhat-release
CentOS release 6.10 (Final)

[root@centos6 ~]# java -version
openjdk version "1.8.0_252"
OpenJDK Runtime Environment (build 1.8.0_252-b09)
OpenJDK 64-Bit Server VM (build 25.252-b09, mixed mode)

${JAVA_HOME}/jre/lib/security/cacertsを確認。

[root@centos6 ~]# ll /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.252.b09-2.el6_10.x86_64/jre/lib/security/
total 64
-rw-r--r--. 1 root root  1273 Apr 27 08:19 blacklisted.certs
lrwxrwxrwx. 1 root root    41 May  4 00:53 cacerts -> ../../../../../../../etc/pki/java/cacerts
-rw-r--r--. 1 root root  2567 Apr 27 08:19 java.policy
-rw-r--r--. 1 root root 45794 Apr 27 08:19 java.security
-rw-r--r--. 1 root root   141 Apr 27 08:24 nss.cfg
drwxr-xr-x. 4 root root  4096 May  2 00:24 policy

cacertsが/etc/pki/java/cacertsへのシンボリックリンクになっている。

[root@centos6 ~]# ll /etc/pki/java/cacerts
-rw-r--r--. 1 root root 158194 May  4 00:44 /etc/pki/java/cacerts

こちらはシンボリックリンクではなく、実体がここにあるようだ。

ここで、Javaアプリケーションが本当に${JAVA_HOME}/jre/lib/security/cacertsを使っているか、念のためテストしてみる。テスト用のJavaアプリケーションとして、先ほども引用した、下記ページのコードを使わせてもらった。

http://a4dosanddos.hatenablog.com/entry/2015/03/29/125111

TestCertClient.java

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.URL;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.List;
import java.util.Map;

import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.X509TrustManager;

public class TestCertClient {
        public static void main(String[] args) throws Exception {
                URL url = new URL("https://webserver");
                HttpsURLConnection con = (HttpsURLConnection) url.openConnection();

                System.out.println("----- Headers -----");
                Map<String, List<String>> headers = con.getHeaderFields();
                for (String key : headers.keySet()) {
                        System.out.println(key + ": " + headers.get(key));
                }
                System.out.println();

                System.out.println("----- Body -----");
                BufferedReader reader = new BufferedReader(new InputStreamReader(
                                con.getInputStream()));
                String body;
                while ((body = reader.readLine()) != null) {
                        System.out.println(body);
                }

                reader.close();
                con.disconnect();
        }
}

また、先ほどと同じように、Apache + mod_sslでオレオレサーバ証明書(CN=webserver)を設定したWebサーバが192.168.12.22(/etc/hostsにwebserverと登録)で動いている。webserver側では、下記のようなhtmlを返すよう設定しておく。

<html>
  <head>
  </head>
  <body>
    <p>Hello, World!</p>
  </body>
</html>

オレオレ証明書はまだクライアントに設定していないので、${JAVA_HOME}/jre/lib/security/cacerts -> /etc/pki/java/cacertsの中身をkeytoolで確認すると、

[root@centos6 ~]# keytool -v -list -keystore /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.252.b09-2.el6_10.x86_64/jre/lib/security/cacerts -storepass changeit | grep webserver
[root@centos6 ~]#

webserverは登録されていない。 この状態でJavaアプリから疎通確認してみる。

[root@centos6 ~]# javac TestCertClient.java
[root@centos6 ~]# java TestCertClient
----- Headers -----

----- Body -----
Exception in thread "main" javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
        at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
        at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
        at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
        at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
        at sun.net.www.protocol.http.HttpURLConnection$10.run(HttpURLConnection.java:1950)
        at sun.net.www.protocol.http.HttpURLConnection$10.run(HttpURLConnection.java:1945)
        at java.security.AccessController.doPrivileged(Native Method)
        at sun.net.www.protocol.http.HttpURLConnection.getChainedException(HttpURLConnection.java:1944)
        at sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:1514)
        at sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1498)
        at sun.net.www.protocol.https.HttpsURLConnectionImpl.getInputStream(HttpsURLConnectionImpl.java:268)
        at TestCertClient.main(TestCertClient.java:36)
Caused by: javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
        at sun.security.ssl.Alerts.getSSLException(Alerts.java:198)
        at sun.security.ssl.SSLSocketImpl.fatal(SSLSocketImpl.java:1967)
        at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:331)
        at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:325)
        at sun.security.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1688)
        at sun.security.ssl.ClientHandshaker.processMessage(ClientHandshaker.java:226)
        at sun.security.ssl.Handshaker.processLoop(Handshaker.java:1082)
        at sun.security.ssl.Handshaker.process_record(Handshaker.java:1010)
        at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:1079)
        at sun.security.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1388)
        at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1416)
        at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1400)
        at sun.net.www.protocol.https.HttpsClient.afterConnect(HttpsClient.java:559)
        at sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.connect(AbstractDelegateHttpsURLConnection.java:185)
        at sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:1570)
        at sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1498)
        at sun.net.www.protocol.http.HttpURLConnection.getHeaderFields(HttpURLConnection.java:3084)
        at sun.net.www.protocol.https.HttpsURLConnectionImpl.getHeaderFields(HttpsURLConnectionImpl.java:297)
        at TestCertClient.main(TestCertClient.java:28)
Caused by: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
        at sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:450)
        at sun.security.validator.PKIXValidator.engineValidate(PKIXValidator.java:317)
        at sun.security.validator.Validator.validate(Validator.java:262)
        at sun.security.ssl.X509TrustManagerImpl.validate(X509TrustManagerImpl.java:330)
        at sun.security.ssl.X509TrustManagerImpl.checkTrusted(X509TrustManagerImpl.java:237)
        at sun.security.ssl.X509TrustManagerImpl.checkServerTrusted(X509TrustManagerImpl.java:132)
        at sun.security.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1670)
        ... 14 more
Caused by: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
        at sun.security.provider.certpath.SunCertPathBuilder.build(SunCertPathBuilder.java:141)
        at sun.security.provider.certpath.SunCertPathBuilder.engineBuild(SunCertPathBuilder.java:126)
        at java.security.cert.CertPathBuilder.build(CertPathBuilder.java:280)
        at sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:445)
        ... 20 more

当然ながら、unable to find valid certification path to requested targetが出る。 今度はオレオレ証明書(カレントディレクトリにあるserver.crt)をcacertsに登録する。

[root@centos6 ~]# keytool -importcert -file server.crt -keystore /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.252.b09-2.el6_10.x86_64/jre/lib/security/cacerts -storepass changeit -alias webserver
Owner: CN=webserver, O=Default Company Ltd, L=Default City, ST=tokyo, C=JP
Issuer: CN=webserver, O=Default Company Ltd, L=Default City, ST=tokyo, C=JP
Serial number: 9a6640c6cb0cb258
Valid from: Wed Apr 29 02:15:04 PDT 2020 until: Fri May 29 02:15:04 PDT 2020
Certificate fingerprints:
         MD5:  3A:B6:25:B4:F0:E2:C3:54:41:B8:0F:1B:2A:F9:76:EB
         SHA1: C9:C3:50:95:88:51:56:CB:28:7F:D0:E8:D5:56:9B:55:40:90:29:B5
         SHA256: 3E:47:A0:0F:47:77:1C:F3:E6:0D:22:F2:D5:B7:2F:94:DE:AD:16:1E:75:83:78:0B:DE:27:57:7D:44:BD:DB:16
Signature algorithm name: SHA256withRSA
Subject Public Key Algorithm: 2048-bit RSA key
Version: 1
Trust this certificate? [no]:  yes
Certificate was added to keystore

cacertsの中身を確認してみる。

[root@centos6 ~]# keytool -v -list -keystore /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.252.b09-2.el6_10.x86_64/jre/lib/security/cacerts -storepass changeit | grep webserver
Alias name: webserver
Owner: CN=webserver, O=Default Company Ltd, L=Default City, ST=tokyo, C=JP
Issuer: CN=webserver, O=Default Company Ltd, L=Default City, ST=tokyo, C=JP

webserverが登録されたので、Javaアプリから疎通確認。

[root@centos6 ~]# java TestCertClient
----- Headers -----
Keep-Alive: [timeout=5, max=100]
Accept-Ranges: [bytes]
null: [HTTP/1.1 200 OK]
Server: [Apache/2.4.6 (CentOS) OpenSSL/1.0.2k-fips]
ETag: ["54-5a46d692b208a"]
Connection: [Keep-Alive]
Last-Modified: [Wed, 29 Apr 2020 12:51:46 GMT]
Content-Length: [84]
Date: [Tue, 05 May 2020 12:35:27 GMT]
Content-Type: [text/html; charset=UTF-8]

----- Body -----
<html>
  <head>
  </head>
  <body>
    <p>Hello, World!</p>
  </body>
</html>

無事接続できている。 この状態で、試しにcacertsのシンボリックリンクをリネームしてみる。

[root@centos6 ~]# cd /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.252.b09-2.el6_10.x86_64/jre/lib/security/
[root@centos6 security]# ll
total 64
-rw-r--r--. 1 root root  1273 Apr 27 08:19 blacklisted.certs
lrwxrwxrwx. 1 root root    41 May  2 00:24 cacerts -> ../../../../../../../etc/pki/java/cacerts
-rw-r--r--. 1 root root  2567 Apr 27 08:19 java.policy
-rw-r--r--. 1 root root 45794 Apr 27 08:19 java.security
-rw-r--r--. 1 root root   141 Apr 27 08:24 nss.cfg
drwxr-xr-x. 4 root root  4096 May  2 00:24 policy

[root@centos6 security]# ln -nfs ../../../../../../../etc/pki/java/cacerts cacerts2
[root@centos6 security]# ll
total 64
-rw-r--r--. 1 root root  1273 Apr 27 08:19 blacklisted.certs
lrwxrwxrwx. 1 root root    41 May  2 00:24 cacerts -> ../../../../../../../etc/pki/java/cacerts
lrwxrwxrwx. 1 root root    41 May  4 00:46 cacerts2 -> ../../../../../../../etc/pki/java/cacerts
-rw-r--r--. 1 root root  2567 Apr 27 08:19 java.policy
-rw-r--r--. 1 root root 45794 Apr 27 08:19 java.security
-rw-r--r--. 1 root root   141 Apr 27 08:24 nss.cfg
drwxr-xr-x. 4 root root  4096 May  2 00:24 policy

[root@centos6 security]# unlink cacerts
[root@centos6 security]# ll
total 64
-rw-r--r--. 1 root root  1273 Apr 27 08:19 blacklisted.certs
lrwxrwxrwx. 1 root root    41 May  4 00:46 cacerts2 -> ../../../../../../../etc/pki/java/cacerts
-rw-r--r--. 1 root root  2567 Apr 27 08:19 java.policy
-rw-r--r--. 1 root root 45794 Apr 27 08:19 java.security
-rw-r--r--. 1 root root   141 Apr 27 08:24 nss.cfg
drwxr-xr-x. 4 root root  4096 May  2 00:24 policy

この状態で再度疎通確認してみる。

[root@centos6 ~]# java TestCertClient
----- Headers -----

----- Body -----
Exception in thread "main" javax.net.ssl.SSLException: java.lang.RuntimeException: Unexpected error: java.security.InvalidAlgorithmParameterException: the trustAnchors parameter must be non-empty
        at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
        at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
        at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
        at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
        at sun.net.www.protocol.http.HttpURLConnection$10.run(HttpURLConnection.java:1950)
        at sun.net.www.protocol.http.HttpURLConnection$10.run(HttpURLConnection.java:1945)
        at java.security.AccessController.doPrivileged(Native Method)
        at sun.net.www.protocol.http.HttpURLConnection.getChainedException(HttpURLConnection.java:1944)
        at sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:1514)
        at sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1498)
        at sun.net.www.protocol.https.HttpsURLConnectionImpl.getInputStream(HttpsURLConnectionImpl.java:268)
        at TestCertClient.main(TestCertClient.java:36)
Caused by: javax.net.ssl.SSLException: java.lang.RuntimeException: Unexpected error: java.security.InvalidAlgorithmParameterException: the trustAnchors parameter must be non-empty
        at sun.security.ssl.Alerts.getSSLException(Alerts.java:214)
        at sun.security.ssl.SSLSocketImpl.fatal(SSLSocketImpl.java:1967)
        at sun.security.ssl.SSLSocketImpl.fatal(SSLSocketImpl.java:1924)
        at sun.security.ssl.SSLSocketImpl.handleException(SSLSocketImpl.java:1907)
        at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1423)
        at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1400)
        at sun.net.www.protocol.https.HttpsClient.afterConnect(HttpsClient.java:559)
        at sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.connect(AbstractDelegateHttpsURLConnection.java:185)
        at sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:1570)
        at sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1498)
        at sun.net.www.protocol.http.HttpURLConnection.getHeaderFields(HttpURLConnection.java:3084)
        at sun.net.www.protocol.https.HttpsURLConnectionImpl.getHeaderFields(HttpsURLConnectionImpl.java:297)
        at TestCertClient.main(TestCertClient.java:28)
Caused by: java.lang.RuntimeException: Unexpected error: java.security.InvalidAlgorithmParameterException: the trustAnchors parameter must be non-empty
        at sun.security.validator.PKIXValidator.<init>(PKIXValidator.java:104)
        at sun.security.validator.Validator.getInstance(Validator.java:181)
        at sun.security.ssl.X509TrustManagerImpl.getValidator(X509TrustManagerImpl.java:318)
        at sun.security.ssl.X509TrustManagerImpl.checkTrustedInit(X509TrustManagerImpl.java:179)
        at sun.security.ssl.X509TrustManagerImpl.checkTrusted(X509TrustManagerImpl.java:193)
        at sun.security.ssl.X509TrustManagerImpl.checkServerTrusted(X509TrustManagerImpl.java:132)
        at sun.security.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1670)
        at sun.security.ssl.ClientHandshaker.processMessage(ClientHandshaker.java:226)
        at sun.security.ssl.Handshaker.processLoop(Handshaker.java:1082)
        at sun.security.ssl.Handshaker.process_record(Handshaker.java:1010)
        at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:1079)
        at sun.security.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1388)
        at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1416)
        ... 8 more
Caused by: java.security.InvalidAlgorithmParameterException: the trustAnchors parameter must be non-empty
        at java.security.cert.PKIXParameters.setTrustAnchors(PKIXParameters.java:200)
        at java.security.cert.PKIXParameters.<init>(PKIXParameters.java:120)
        at java.security.cert.PKIXBuilderParameters.<init>(PKIXBuilderParameters.java:104)
        at sun.security.validator.PKIXValidator.<init>(PKIXValidator.java:102)
        ... 20 more

java.security.InvalidAlgorithmParameterException: the trustAnchors parameter must be non-emptyが出たので、やはりjavaは${JAVA_HOME}/jre/lib/security/cacerts -> /etc/pki/java/cacertsを使っているようだ。

CentOS7 + OpenJDK 1.8.0

OpenJDKはyumでインストールしたもの。

[root@localhost ~]# cat /etc/redhat-release
CentOS Linux release 7.6.1810 (Core)

[root@localhost ~]# java -version
openjdk version "1.8.0_242"
OpenJDK Runtime Environment (build 1.8.0_242-b08)
OpenJDK 64-Bit Server VM (build 25.242-b08, mixed mode)

${JAVA_HOME}/jre/lib/security/cacertsを確認。

[root@localhost ~]# ll /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.242.b08-1.el7.x86_64/jre/lib/security/
total 56
-rw-r--r--. 1 root root  1273 Apr  2 16:02 blacklisted.certs
lrwxrwxrwx. 1 root root    41 May  4 07:19 cacerts -> ../../../../../../../etc/pki/java/cacerts
-rw-r--r--. 1 root root  2567 Apr  2 16:02 java.policy
-rw-r--r--. 1 root root 44792 Jan  1  2014 java.security
-rw-r--r--. 1 root root   139 Apr  2 16:07 nss.cfg
drwxr-xr-x. 4 root root    38 Apr  2 16:02 policy

CentOS6同様、/etc/pki/java/cacertsへのシンボリックリンクになっている。

[root@localhost ~]# ll /etc/pki/java/cacerts
lrwxrwxrwx. 1 root root 40 May  4 07:03 /etc/pki/java/cacerts -> /etc/pki/ca-trust/extracted/java/cacerts

/etc/pki/java/cacertsはさらにシンボリックリンクとなっており、実体はupdate-ca-trustの管理下にあるようだ。

先ほどと同じように、${JAVA_HOME}/jre/lib/security/cacertsをリネームしてみる。

[root@localhost security]# ll 
total 56
-rw-r--r--. 1 root root  1273 Apr  2 16:02 blacklisted.certs
lrwxrwxrwx. 1 root root    41 May  4 07:16 cacerts2 -> ../../../../../../../etc/pki/java/cacerts
-rw-r--r--. 1 root root  2567 Apr  2 16:02 java.policy
-rw-r--r--. 1 root root 44792 Jan  1  2014 java.security
-rw-r--r--. 1 root root   139 Apr  2 16:07 nss.cfg
drwxr-xr-x. 4 root root    38 Apr  2 16:02 policy

疎通確認。

[root@localhost ~]# java TestCertClient
----- Headers -----
Keep-Alive: [timeout=5, max=100]
Accept-Ranges: [bytes]
null: [HTTP/1.1 200 OK]
Server: [Apache/2.4.6 (CentOS) OpenSSL/1.0.2k-fips]
ETag: ["54-5a46d692b208a"]
Connection: [Keep-Alive]
Last-Modified: [Wed, 29 Apr 2020 12:51:46 GMT]
Content-Length: [84]
Date: [Mon, 04 May 2020 07:17:01 GMT]
Content-Type: [text/html; charset=UTF-8]

----- Body -----
<html>
  <head>
  </head>
  <body>
    <p>Hello, World!</p>
  </body>
</html>

・・・なぜか通ってしまった。${JAVA_HOME}/jre/lib/security/cacertsは見ていない?

ちなみに、シンボリックリンク/etc/pki/java/cacertsをリネームしたらNG(java.security.InvalidAlgorithmParameterException: the trustAnchors parameter must be non-empty)になった。どこかでOSの方の証明書ストアを見に行くように設定がされたのだろうか?

Ubuntu 18.04 + OpenJDK 1.8.0

OpenJDKはaptでインストールしたもの。

root@ubuntu-bionic:~# cat /etc/os-release
NAME="Ubuntu"
VERSION="18.04.3 LTS (Bionic Beaver)"
ID=ubuntu
ID_LIKE=debian
PRETTY_NAME="Ubuntu 18.04.3 LTS"
VERSION_ID="18.04"
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
VERSION_CODENAME=bionic
UBUNTU_CODENAME=bionic

root@ubuntu-bionic:~# java -version
openjdk version "1.8.0_252"
OpenJDK Runtime Environment (build 1.8.0_252-8u252-b09-1~18.04-b09)
OpenJDK 64-Bit Server VM (build 25.252-b09, mixed mode)

${JAVA_HOME}/jre/lib/security/cacertsを確認。

root@ubuntu-bionic:~# ll /usr/lib/jvm/java-8-openjdk-amd64/jre/lib/security/
total 12
drwxr-xr-x 3 root root 4096 May  4 07:08 ./
drwxr-xr-x 8 root root 4096 May  2 08:02 ../
lrwxrwxrwx 1 root root   46 Apr 15 18:48 blacklisted.certs -> /etc/java-8-openjdk/security/blacklisted.certs
lrwxrwxrwx 1 root root   27 May  4 07:08 cacerts -> /etc/ssl/certs/java/cacerts
lrwxrwxrwx 1 root root   40 Apr 15 18:48 java.policy -> /etc/java-8-openjdk/security/java.policy
lrwxrwxrwx 1 root root   42 Apr 15 18:48 java.security -> /etc/java-8-openjdk/security/java.security
lrwxrwxrwx 1 root root   36 Apr 15 18:48 nss.cfg -> /etc/java-8-openjdk/security/nss.cfg
drwxr-xr-x 4 root root 4096 May  2 08:02 policy/

/etc/ssl/certs/java/cacertsへのシンボリックリンクになっている。

root@ubuntu-bionic:~# ll /etc/ssl/certs/java/cacerts
-rw-r--r-- 1 root root 156228 May  4 09:12 /etc/ssl/certs/java/cacerts

実体はこちらにある。ちなみにこのファイルはupdate-ca-certificatesの管理下であり、update-ca-certificatesを実行して信頼済みCA証明書を追加する際に、/etc/ssl/certs配下のca-certificates.crtおよびシンボリックリンク{hash}.0と共に更新されるようだ。

${JAVA_HOME}/jre/lib/security/cacertsをリネームしてみる。

root@ubuntu-bionic:/usr/lib/jvm/java-8-openjdk-amd64/jre/lib/security# ll
total 12
drwxr-xr-x 3 root root 4096 May  5 13:38 ./
drwxr-xr-x 8 root root 4096 May  2 08:02 ../
lrwxrwxrwx 1 root root   46 Apr 15 18:48 blacklisted.certs -> /etc/java-8-openjdk/security/blacklisted.certs
lrwxrwxrwx 1 root root   27 May  5 13:38 cacerts2 -> /etc/ssl/certs/java/cacerts
lrwxrwxrwx 1 root root   40 Apr 15 18:48 java.policy -> /etc/java-8-openjdk/security/java.policy
lrwxrwxrwx 1 root root   42 Apr 15 18:48 java.security -> /etc/java-8-openjdk/security/java.security
lrwxrwxrwx 1 root root   36 Apr 15 18:48 nss.cfg -> /etc/java-8-openjdk/security/nss.cfg
drwxr-xr-x 4 root root 4096 May  2 08:02 policy/

疎通確認。

root@ubuntu-bionic:~# java TestCertClient
----- Headers -----

----- Body -----
Exception in thread "main" javax.net.ssl.SSLException: java.lang.RuntimeException: Unexpected error: java.security.InvalidAlgorithmParameterException: the trustAnchors parameter must be non-empty
...以下略

the trustAnchors parameter must be non-emptyが出たので、Javaは${JAVA_HOME}/jre/lib/security/cacertsを参照しているようだ。

まとめ

結論的に、調べた範囲だとOS標準の場所を参照しているように見える(ただしOS標準の場所はディストリビューションとバージョンによって異なる)。アプリの作りとビルド時の設定にも依存するので、証明書の有効性を確認する時はそこまで確認する必要がある。

curl

OS アプリとバージョン デフォルトで参照するルート証明書の場所
CentOS 6.10 curl 7.19.7 CAfile: /etc/pki/tls/certs/ca-bundle.crt
CApath: none
CentOS 7.6.1810 curl 7.29.0 CAfile: /etc/pki/tls/certs/ca-bundle.crt -> /etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem
CApath: none
Ubuntu 18.04 curl 7.58.0 CAfile: /etc/ssl/certs/ca-certificates.crt
CApath: /etc/ssl/certs

【openssl】

OS アプリとバージョン デフォルトで参照するルート証明書の場所
CentOS 6.10 OpenSSL 1.0.1e-fips CAfile: /etc/pki/tls/cert.pem -> /etc/pki/tls/certs/ca-bundle.crt
CApath: /etc/pki/tls/certs
CentOS 7.6.1810 OpenSSL 1.0.2k-fips CAfile: /etc/pki/tls/cert.pem -> /etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem
CApath: /etc/pki/tls/certs
Ubuntu 18.04 OpenSSL 1.1.1 CAfile: なし?
CApath: /usr/lib/ssl/certs -> /etc/ssl/certs/

Java

OS アプリとバージョン デフォルトで参照するルート証明書の場所
CentOS 6.10 OpenJDK 1.8.0 ${JAVA_HOME}/jre/lib/security/cacerts -> /etc/pki/java/cacerts
CentOS 7.6.1810 OpenJDK 1.8.0 /etc/pki/java/cacerts -> /etc/pki/ca-trust/extracted/java/cacerts
Ubuntu 18.04 OpenJDK 1.8.0 ${JAVA_HOME}/jre/lib/security/cacerts -> /etc/ssl/certs/java/cacerts

参考