OCIのZPRを知っていますか

少しだけ不思議な、セキュリティ寄りのネットワークのお話。

(2025.06.17) all-endpointsの結果が一部間違っていた（Compute3→localPCは○）のを修正

# 書きたいこと

昨年のOracle Cloud Worldで大々的に発表のあったZero Trust Packet Routing。略称ZPR。
すでに存在するNetwork Security Group（以下、NSG）やSecurity Listと比べてどうなのか、本気で考えてみた自分なりの結論を、書いていこうと思います。

いや、去年のOCWでこの話を最初に聞いたときからずっと気になってたんですが、~~ゲームにハマってて~~なかなかまとまった時間が取れずに検証できなくて。やっと終わらせることができました。


# まずはZPRを理解しよう

そもそもZPRがどんなものか分かってないと何も書けないので、いろいろ調べてみました。


## ドキュメントを読んでみよう

[ZPRの日本語ドキュメントはこれ](https://docs.oracle.com/ja-jp/iaas/Content/zero-trust-packet-routing/zpr-policy-syntax.htm)。

…日本語で読んじゃダメですね。翻訳なので仕方ないのですが、例えば日本語だとこう書かれてます。

> * `<command>`が許可されている必要があります。

あれ？`<command>`に指定できるコマンドってどこに説明あるの？
[英語ドキュメント](https://docs.oracle.com/en-us/iaas/Content/zero-trust-packet-routing/zpr-policy-syntax.htm)を読んでみましょう。

> * `<command>` must be allow.

allowとしか書けないんかい！

こんなのがちょこちょこあります。そのうち改善されることを信じて、今は英語で読みましょう。日本語で理解しようとすると脳がバグります。

まあ、OracleDBやってる人なら見慣れているであろう構文図が下のほうにあるので、それを信じるのが一番良いかもしれません。

…`connection-state`の説明が見当たらないー。[Policy Examples](https://docs.oracle.com/en-us/iaas/Content/zero-trust-packet-routing/zpr-policy-syntax.htm)を見ると、以下の例が載っています。

> in finance.network:prod VCN allow app:frontend endpoints to connect to database:server endpoints with protocol = 'tcp/1521', connection-state = 'stateless'

ちなみに説明は「with a stateless connection」。stateless connectionってなんだろう？ `'stateless'`の部分は`'stateful'`でもエラーになりませんでしたが、`'hoge'`は怒られました。


## ポリシーステートメントを作ってみよう

IAMと同じようにポリシービルダーがあります。
そして、画面下のほうにサンプルが載ってます。親切ですね。

> in vcn-network:db VCN allow app:front-end endpoints **with protocol = 'tcp/999-11199'** to connect to app:back-end endpoints

脳がバグるポイント再び。

前に書くのは送信元の認識なのですが、ポート指定があります。
いや、送信元でポート指定をすることもありますよ？でも、それってどちらかといえばレアケースですよね。あれ？もしかしたら先に書くのは送信元ではない？？

ポリシービルダーで「Allow compute instance to connect via SSH to another compute instance.」を選ぶとこうなります。

> in `<security attribute of VCN>` VCN allow `<security attribute of source-compute>` endpoints to connect to `<security attribute of target-compute>` endpoints **with protocol='tcp/22'**

ですよね。フロントエンドサーバからバックエンドサーバへの通信の送信元ポートが999/tcp～11199/tcpというのは良くある方式なんでしょうかねえ。軽く調べた感じではそんなことは無さそうですが。


## ほかのセキュリティの代替になるか考えてみよう

ZPRを使えば、NSGやSecurity Listは不要になるのか。

最初、[この図](https://docs.oracle.com/en-us/iaas/Content/zero-trust-packet-routing/overview.htm#zpr-other-comparison__zpr-other-use)の意味がよく分からなかったのですが、どうも、こういうことのようです。

ZPRを使った通信許可設定は以下の３つ：
* ZPRとSecurity Listで許可する
* ZPRとNSGで許可する
* ZPRとNSGとSecurity Listで許可する

つまり、ZPR単体で通信させることはできないということですね。まあ、Security Listで、全プロトコル、0.0.0.0/0で許可しておいて、ZPRのみで制御という構成にはできそうです。




## ステートレスを検証してみよう

やっぱ気になるのはステートレスですね。NSGやSecurity Listのステートレスと同じなのかなーと検証してみました。検証環境の構成は以下で、Security Listで全通しの上で、ZPRで制御です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3781934/27044790-cf9c-44f7-bfef-21fca2a483ef.png)

まずは、以下のZPRポリシーを設定。ネームスペースやセキュリティ属性の説明はないのかって？だいたい分かるっしょ^-^

```
in ZPR-TEST.VCN:ZPR VCN allow ZPR-TEST.COMPUTE:CLIENT endpoints to connect to ZPR-TEST.COMPUTE:SERVER endpoints
```

で、ping実行。通ります。

```shell-session
[opc@zpr-compute1 ~]$ ping 10.0.1.10
PING 10.0.1.10 (10.0.1.10) 56(84) bytes of data.
64 bytes from 10.0.1.10: icmp_seq=1 ttl=64 time=0.387 ms
64 bytes from 10.0.1.10: icmp_seq=2 ttl=64 time=0.423 ms
64 bytes from 10.0.1.10: icmp_seq=3 ttl=64 time=0.425 ms
^C
--- 10.0.1.10 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2047ms
rtt min/avg/max/mdev = 0.
[opc@zpr-compute1 ~]$
```

では、stateless付けてみましょう。

```
in ZPR-TEST.VCN:ZPR VCN allow ZPR-TEST.COMPUTE:CLIENT endpoints to connect to ZPR-TEST.COMPUTE:SERVER endpoints with connection-state = 'stateless'
```

通ります（汗

```shell-session
[opc@zpr-compute1 ~]$ ping 10.0.1.10
PING 10.0.1.10 (10.0.1.10) 56(84) bytes of data.
64 bytes from 10.0.1.10: icmp_seq=1 ttl=64 time=0.416 ms
64 bytes from 10.0.1.10: icmp_seq=2 ttl=64 time=0.440 ms
64 bytes from 10.0.1.10: icmp_seq=3 ttl=64 time=0.379 ms
^C
--- 10.0.1.10 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2036ms
rtt min/avg/max/mdev = 0.379/0.411/0.440/0.025 ms
[opc@zpr-compute1 ~]$
```

[この辺のタイムアウト時間](https://docs.oracle.com/en-us/iaas/Content/Network/Concepts/securityrules.htm#stateful__cxn_tracking)が関係するのかなと、時間を空けたりしていろいろ試してみましたが、`connection-state`を付けても何も動作は変わらないので、調査は断念。zpr.org見てもなんも情報ないし、Oracle Universityのコンテンツ見ても説明ないし。SRかなー。

そもそも、プロトコルと同列にステートフル/ステートレスがあるのがめっちゃ違和感あるんですよね。なんか全然違う用途のような気がします。もしかして本当に「ステートレス接続」というものが存在する？？


## IPアドレス指定を検証してみよう

次はIPアドレス。送信元、送信先、どちらもIPアドレス指定ができますが、[両方ともIPアドレスは不可です](https://docs.oracle.com/en-us/iaas/Content/zero-trust-packet-routing/zpr-policy-syntax.htm)。

> `ip-address` or `osn-services-ip-addresses` can be a target or a source. However, you can't use `ip-address` and `osn-services-ip-addresses` on both the source and target endpoints; `ip-address` and `osn-services-ip-addresses` must be either the source or the target. 

ということで、以下の環境を作って検証してみました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3781934/ed50e4b5-7b7b-4ab0-bcfd-6d6686126bf2.png)


1. オンプレからインターネット経由の接続
1. オンプレからIP-VPN経由の接続
1. 別VCNからリモート・ピアリング経由の接続
1. 同一VCN内からの接続
1. (おまけ)Yumレポジトリへの接続

あ、図にオンプレのネットワークアドレスが書いてないですね。192.168.1.0/24です。
5がSubnet1からなのは、必ずサービス・ゲートウェイになっていることを担保するため。
自宅とIP-VPN繋いでると、こういう環境がサクっと作れて便利です。作り方は[こちら](https://qiita.com/sazumi/items/c04b2a98629c51602476)を参考にしてください。

書いたZPRポリシーはこちら。上から順に上記番号と連動したポリシーになっています。

```
in ZPR-TEST.VCN:ZPR VCN allow '<自宅のIPアドレス>/32' to connect to ZPR-TEST.COMPUTE:SERVER endpoints with protocol='tcp/22'
in ZPR-TEST.VCN:ZPR VCN allow '192.168.1.0/24' to connect to ZPR-TEST.COMPUTE:SERVER endpoints with protocol='tcp/22'
in ZPR-TEST.VCN:ZPR VCN allow '172.16.0.0/24' to connect to ZPR-TEST.COMPUTE:SERVER endpoints with protocol='tcp/22'
in ZPR-TEST.VCN:ZPR VCN allow '10.0.0.0/24' to connect to ZPR-TEST.COMPUTE:SERVER endpoints with protocol='tcp/22'
in ZPR-TEST.VCN:ZPR VCN allow ZPR-TEST.COMPUTE:CLIENT endpoints to connect to 'osn-services-ip-addresses'
```

結果、4の同一VCNからの接続以外はOKでした。
まあ、検証前から4は繋がらないだろうと思ってたので、特に違和感なく。同一VCNならセキュリティ属性を指定しろということなんだと思います。


## VCNを繋いでみよう

先ほどの検証、1と2のケースはともかく、3のケースは、OCI上のリソースからの通信なので、IPアドレスではなくセキュリティ属性を指定して通信許可できるんじゃないかと思いませんか？

と、右のVCN2内のCompute3にセキュリティ属性を付与したところ、以下のようになりました。

* Compute3からCompute2にSSH接続不可
* Local PCからCompute3にSSH接続不可
* **すでにCompute3に接続していたターミナルは普通に操作できる**

なるほど。セキュリティ属性を付与した時点で、ZPRの管理下に置かれ、VCN2にはセキュリティ属性を付与していないので、該当するポリシーがなく、Compute3発および着の新規通信がすべてブロックされるわけですね。しかし、すでに成立している通信には影響がないと。最後のは地味に混乱しそうな気がします。

ということで、VCN2にもセキュリティ属性を付与して、繋がるようにしてみました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3781934/db554344-1e1d-4067-ab06-19a61b7e93ce.png)

書いたZPRポリシーは以下。

```
in ZPR-TEST.VCN:ZPR VCN allow '172.16.0.0/24' to connect to ZPR-TEST.COMPUTE:SERVER endpoints with protocol='tcp/22'
in ZPR-TEST.VCN:ZPR VCN allow ZPR-TEST.COMPUTE:CLIENT endpoints to connect to '10.0.1.0/24' with protocol='tcp/22'
```

…美しくない。
結局、ZPRポリシーで、allow _XXX endpoints_ to connect to _YYY endpoints_ と書けるのは同一VCN内の場合のみで、別VCNと通信させるときは、IPで書く必要があるわけですね。[ZPRポリシーの概要](https://docs.oracle.com/en-us/iaas/Content/zero-trust-packet-routing/zpr-policy-overview.htm)にも書いてありました。

> * Allow communication between two endpoints **within a VCN**

DRG経由とか、別リージョンはどうなるかとかも検証しようと思ってましたが、ローカル・ピアリングの時点でこうなので、これ以上の検証は不要でしょう。


## all-endpointsを確認しよう

ZPRポリシーの指定方法は、`<Security Attribute> endpoints`、`<IP Address>`ともう一つ、`all-endpoints`があります。

念のために挙動を確認しましょう。構成は以下。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3781934/bcc6829a-30a3-483c-8b95-8d04bc8df677.png)

書いたZPRポリシーは以下の通り。

```
in ZPR-TEST.VCN:ZPR VCN allow ZPR-TEST.COMPUTE:CLIENT endpoints to connect to all-endpoints with protocol='tcp/22'
in ZPR-TEST.VCN:ZPR VCN allow '192.168.1.0/24' to connect to ZPR-TEST.COMPUTE:CLIENT endpoints with protocol='tcp/22'
in ZPR-TEST.VCN:ZPR VCN allow '192.168.1.0/24' to connect to ZPR-TEST.COMPUTE:SERVER endpoints with protocol='tcp/22'
```

構文図を見る限りは、接続元に`all-endpoints`指定できるように見えるんですけど、実際はダメでした。また、接続元が`<IP Address>`の場合もダメでした。何か嫌な予感がする。

上記ZPRポリシーのssh接続結果は以下。

接続元|接続先|結果
-|-|:-:
LocalPC|Compute1|○
LocalPC|Compute2|○
LocalPC|Compute3|○
Compute1|Compute1|○
Compute1|Compute2|×
Compute1|Compute3|×
**Compute1**|**LocalPC**|**○**
Compute2|Compute1|×
Compute2|Compute2|○
Compute2|Compute3|×
Compute3|LocalPC|×
Compute3|Compute1|×
Compute3|Compute2|×
Compute3|Compute3|○
Compute3|LocalPC|○

VCN内は自分自身のみ接続可。まあ、これはサーバ内で完結してるからですね。そして、Compute1からLocalPCにssh接続できました。

**…もしかして、`all-endpoints`って、'0.0.0.0/0'のことですか？**


# ZPRは使えるのか

いろいろ試してみた雑感です。

## ZPRが適しているところ

ZPRでやろうとしていることは、NSGで**宛先や送信元の指定に他NSGを指定する**使い方と似ていると感じます。個人的にはNSGのこの使い方ってかなり難解だと思っていて、これがZPRだと**セキュリティ属性**と**ZPRポリシー**に分離されるので、分かりやすいんじゃないかなと思います。

それ以外の点に関しては、Secrity List/NSGの置き換えになるというよりは、ネットワーク担当とセキュリティ担当の役割分担の話になるかなと感じます。

ネットワーク担当がセキュリティ担当も兼任している場合は、ZPRも使うようになると、運用負荷が高くなるデメリットの方が高くなると思います。Security List、NSGは点(Subnet/VNIC)でセキュリティを担保する方式に対して、ZPRは面(VCN)で担保する方式です。考え方が違う二つを同一担当が運用するはかなり大変です。

きっちり役割分担できているところに関しては、ネットワーク担当はSecurity ListやNSG、ルート表などを使った「通信可能にする運用」に注力することができます。通信できないところを制御するのはセキュリティ担当に任せればよく、それはZPRだけで対応可能なので、同じリソースを複数の担当で変更することもありません。
まあ、そこまでできる大規模な運用チームは、そうそうない気もしますけど。


## ZPRの難しいところ

ドキュメントが足りなすぎる、という点は置いておいて。

トラブルシュートがしにくいです。挙動から推測するぐらいしかできません。参考になるログもありません。VCNフローログも取ってみましたが、REJECTが記録されるのみのように見えました。なんで通信不可なのか、追えないんです。

SELinuxのPermissiveモードみたいなのがあって、ログで挙動が確認してからポリシー有効化、みたいなことができたら良いんですけどね。今の仕様だと、VCN全体の挙動に影響があるので、本番稼働中の環境に適用は怖いかなと感じます。


# 検証を終えて

いやー、楽しかった。けど、こんな重いネタはしばらく無理ですわｗ