CRDとReconcileでテトリスするKubernetesのCustom Controllerを作った
テトリスするCustom Controllerを作りました。(正確に言うと「テトリスに似た何か」です)
単にテトリスをアプリとして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 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% / (略)
環境
- VirtualBox 6.1
- Vagrant 2.2.9
- Guest OS: Ubuntu 18.04(bionic)
- DiskはVMDK形式。10GB→20GBに拡張したい。
事前準備
VMDK→VDIへの変換、ディスク拡張、アタッチ
※VirtualBoxのコマンドでもできそうだったが、下記はGUIからの手順
VMDKをVDIに変換
VMDK形式だと拡張できないため、VDIに変換
- VirtualBoxのGUIから、画面上部「ツール」 の右側のアイコン ->「メディア」を選択
- 対象のディスク(.vmdk)を選択して右クリック -> 「コピー」
- ディスクイメージのファイルタイプを聞かれるので、VDIを選択 -> 「次へ」
- 適当に名前をつけて「コピー」
ディスクの拡張
仮想化環境のディスク容量を拡張する - Avintonジャパン株式会社
こちらを参考に、VDIに変換済みのディスクを拡張。
拡張したディスクイメージ(VDI)をVMにアタッチ
- VirtualBox GUI上で対象のVMの「設定」 -> 「ストレージ」からもともとのディスクをデタッチする。
※vagrantを使った場合
ubuntu-bionic-18.04-cloudimg.vmdk
とubuntu-bionic-18.04-cloudimg-configdrive.vmdk
がアタッチされているはず。両方とも「割り当てを除去」 - 「ハードディスクの追加」から、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できるようになりたい。
なるほど、色々と情報ありがとうございます!勉強になりました。
— tkn (@tk_n_) January 3, 2021
その後は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 AWS Provider
— tkn (@tk_n_) April 12, 2021
その他、何かとTerraformにはお世話になった1年だった。
機械学習/深層学習/自然言語処理
個人的にはAIより仮想化とかミドルウェアとかインフラ系の技術の方が多少馴染みがあるのだが、今後避けては通れないだろうし直近でも必要になりそうだったので、GWに有名なNLP本を買ってひととおり手を動かしてみた。
はじめました。 pic.twitter.com/2dJ19kPt98
— tkn (@tk_n_) April 29, 2021
今までほぼブラックボックスとして使っていたWord2Vecのしくみや、RNN, LSTM, Seq2seq, Attentionのアイデアについて理解できた(気になる)良い本だった。
あとはPyTorch, BERT関連で以下のUdemyもやった。
- 【Hands Onで学ぶ】PyTorchによる深層学習入門 | Udemy
- BERTによる自然言語処理を学ぼう! -Attention、TransformerからBERTへとつながるNLP技術- | Udemy
お仕事の方では、インターンのテーマ設定とサポートをやっていた。ざっくり言うとTerraform×機械学習/深層学習といったテーマで、Amazon Sagemakerを使って色々試行錯誤してもらった。Sagemakerのお作法に慣れる必要があり、(今までGoogle Colab等で自前で全て書いていた所からすると)少し学習コストが高く、今回のようなアドホックな研究用途には余り適していなかったかもしれないが、AutoML機能等は便利に使えたのかなと思う。特にビジネスの現場において最速でAIを適用していくために、よくデザインされたフレームワークだと感じた。
Go言語と静的解析
上記のTerraform×機械学習/深層学習をやるにあたって、学習データを作成する必要があった。既存の学習データはなく、その元となる情報はTerraform provider (AWS) の中にしかない状況。しかも単純なgrep等では抽出できず、変数の定義を辿っていくような深い解析が必要。
購入しました / 逆引きGoによる静的解析入門 | tenntenn https://t.co/jpeuqlcpSZ #booth_pm
— tkn (@tk_n_) July 22, 2021
そこで静的解析。上記サイトと本を参考に、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のソースコードを読んでみたりしていた。
TCP難しい... 受信側がACK返すタイミングってどうなってるんだろ 遅延ACKだとすると2回に1回、もしくは200-500ms経過したらACKらしいけど、そうなってなさそうなんだよな。
— tkn (@tk_n_) September 15, 2021
当初はブログかQiita等にまとめようと思っていたが、最終的にLinuxのiperfクライアントでやったら普通に思った通りの結果になったので、急に自明に思えてしまって頓挫中。もう少し面白い感じにして何かしらアウトプットできるようにしたい。
マイクロサービス
テトリスを無駄にマイクロサービスにしたら面白いのでは?と急に思い立って作り始めた。
「テトリスはどこまでマイクロサービスになれるのか」をテーマに作り始めてしまった。どこに向かっているのか自分でもよくわかりません。 pic.twitter.com/vlehAV7xDY
— tkn (@tk_n_) October 5, 2021
正確に言うと急にではなく、以下のような思惑があった。
- 普通分割しないようなものを分割するとどこかで性能限界が来るはずで、それを目の当たりにしたい(リソース限界的な興味)
- マイクロサービス(アーキテクチャのシステム)を作る人向けのフレームワークについて考えており、フレームワーク側にどのような機能があると嬉しいのか、イメージを掴みたい。
とりあえずゲームの基本部分を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間のコンフリクト? -
- 暗渠
運動不足解消のために散歩を始めた結果、街中に怪しげな小径があることに気付き、それが暗渠と呼ばれる川の跡であることを知り、東京の地形と川と暗渠の沼に片足を踏み入れることになった。
今日朝から下水道台帳しか見てない、、これ面白いなー。
— tkn (@tk_n_) August 8, 2021
flexmatcherを試してみる
諸々の事情でSchema Matchingの現状を調査中。あまり実装が見当たらないが、python実装の1つ、flexmatcherを試してみる。 (2017年から活動がないようなのが残念)
ドキュメントに従ってインストール
$ 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以上)ことが原因のようなので、ダウングレードしてみる。
参考:
- pandas で 'DataFrame' object has no attribute 'ix' が出てきたとき - Qiita
- What’s new in 1.0.0 (January 29, 2020) — pandas 1.0.0 documentation
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を使えばうまくいくのかもしれない。
環境
- VirtualBox 6.1
- Ubuntu 18.04
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)とは異なる。
参考:
- https://www.janog.gr.jp/meeting/janog14/src/JANOG14-mpls.pdf
- https://www.nic.ad.jp/ja/materials/iw/2003/proceedings/T19-5.pdf
環境
- Eve-NG Community Edition Version 2.0.3-112
- IOL Version 15.5(2)T
検証構成
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で帯域使用率等の情報が広報されるのが見える。
プライマリパスの作成(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
帯域割り当てもプライマリ側は解除されている。
参考
- https://www.janog.gr.jp/meeting/janog14/src/JANOG14-mpls.pdf
- https://www.nic.ad.jp/ja/materials/iw/2003/proceedings/T19-5.pdf
- https://www.janog.gr.jp/meeting/janog33/doc/janog33-mpls-tsuchiya-1.pdf
- Cisco Content Hub - MPLS Traffic Engineering (TE) Path Protection
- Without haste, but without rest.
- MPLS-TEの設定 その1 | ♪メモのページ♪
LinuxでHTTPS通信時にデフォルトで使用される証明書ファイルの場所
はじめに
HTTPS通信をする際、サーバ側からはサーバ証明書(+中間証明書)を提示し、クライアント側では予めインストールされた信頼済みルート証明書を使って、サーバから提示されたサーバ証明書を検証する(※)。
※片方向TLSの場合。双方向の場合はクライアント側からもクライアント証明書を提示する。
サーバ側で使うサーバ証明書はWebサーバとなるソフトウェアの設定ファイル等で指定されるが、クライアント側のルート証明書は何が使われるのだろうか。Windows/MacでWebブラウザがクライアントとなる場合の情報はどこにでもあるが、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でインストールした場合はどうしたらいいのだろうか?探した限りでは、あまり標準的なやり方はない模様。
こちらでも議論されており、様々な方法が提案されているが、個人的にはオプション-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 |
※curlはSSL系のライブラリとして、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
であることがわかる。
[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と同様)
[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がシンボリックリンクになっている。
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への追加の両方が実行される。
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"を確認すればよい。
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の実行時オプションでシステムプロパティ(javax.net.ssl.trustStore)を設定する
- プログラム内で設定する (システムプロパティやSSLSocketFactoryを使う方法等あり)
といった方法があるようだ。
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プログラムの中で指定する方法
上記サイトを参考にさせていただいてます。
システムプロパティ"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しか調べていないので、他のバージョンだと変わるかもしれない。
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 |