Netskopeからのアウトバウンド通信における送信元グローバルIPアドレスを固定化したい

# まえがき

みなさまお疲れ様です。Gzockです。

先日、[Japan Okta User Group Communityの2023忘年会](https://okta.connpass.com/event/303066/)にLT登壇させていただきました。

やっぱりオフラインの場は楽しいですね。たくさんの人に興味持ってもらえたようで本当に嬉しく思います。

今回、[ビットキー 情シス アドカレ](https://qiita.com/advent-calendar/2023/bitkey-corp-eng)18日の記事ということで何を書くか迷ったのですが、LTの場でも少しご紹介したNetskopeのグローバルIP固定化の話にしたいと思います。

`注意` : 本記事のネタ自体は昨年作ったものになるので、正直2023年末の今においては古い情報が一部含まれているかもしれない

# そもそもの話

- 私は情シス所属ではありません
- 普段はIoTの開発やってます
    - [2023 AWS Summit Tokyoで弊社が登壇](https://speakerdeck.com/bitkey/yuzati-yan-woxiang-shang-saserukagiha-fen-duan-nojie-xiao-jue-dui-nizhi-merarenaisumatorotukuwoxi-usisutemunoli-ce)させてもらいましたが、その中で言及しているプラットフォームを担当しています
    - その他、登壇資料を見てもらえるとどんなヤツか大体わかると思います
        - [ビットキーのIoT基盤におけるAWS IoT Rule Action活用](https://www.slideshare.net/ryosasaki376695/iotaws-iot-rule-action)
        - [Okta Workflowsによるノーコード業務改善](https://www.slideshare.net/ryosasaki376695/okta-x-jamf-okta-workflows-jamf-apimacpdf)
        - [たまにはオンプレもいいぞ？GKEとArmadillo IoTで実現するハイブリッドクラウド](https://www.slideshare.net/ryosasaki376695/cloud-native-days-tokyo-2022-gke-armadillo-iot)
        - [Datadog Agent on CloudRunによるGCPトレーサビリティの向上](https://www.slideshare.net/ryosasaki376695/datadog-agent-on-cloudrungcp)
- なのになんで情シスアドベントカレンダーで割と記事書いてるのって？
- わかりません, ボス(@h_r_w_t_r)に聞いてください
    - ちょっとした経緯は上述のOkta WFsの記事で言及しています
- ちなみに[まえがき](https://qiita.com/67eca6e53a531695e809#%E6%9C%AC%E7%92%B0%E5%A2%83%E3%82%92%E5%88%A9%E7%94%A8%E3%81%99%E3%82%8B%E3%83%A6%E3%83%BC%E3%82%B9%E3%82%B1%E3%83%BC%E3%82%B9%E4%BE%8B)でも言及したLT記事は以下です
    - [ゼロトラスト三銃士〜Okta x Jamf x Netskopeネタ10連発〜](https://www.slideshare.net/ryosasaki376695/okta-x-jamf-x-netskope10pdf)

# 概要

ビットキーがゼロトラスト実現のために導入しているNetskopeが使用する送信元グローバルIPアドレスを固定化したい。

# Netskopeとは

いわゆる `SASE（Secure Access Service Edge)`を実現するプラットフォーム。
* https://www.netskope.com/jp/

ビットキーにおいてはゼロトラスト実現のためこちらのNetskopeを導入している。
このツールにより、各社員が使用するMac端末から生まれる通信は全てNetskopeを経由し、どの宛先にどんな通信を行なっているか？を丸裸にしている。その上で通信内容の可視化を行うことで、本来その端末がその通信先に対して行って良いものなのかどうかを動的に判断、場合によっては通信遮断なども行える。

# 背景

ゼロトラスト実現という文脈においては、このNetskopeはうまく稼働しているのだが、大きな問題が一つある。

各端末からの通信はNetskopeを経由してインターネットに出ていくことになるが、NetskopeのSaaSはさまざまなデータセンターに跨った形で存在しているため、送信元グローバルIPアドレスが一定しない。

この問題のため、各端末からの通信時の送信元があっちこっちになってしまう。
大抵はそれでも問題は起きないが、一部の社内利用システムやSaaSでは送信元IPアドレス制限をしているケースがあり、その時にかなり困ったことになる。

Netskopeが利用するグローバルIPアドレスは公開されているが、IPアドレス範囲が広すぎて設定が面倒くさくしんどい。

というか、そもそもサイレントに範囲が変わるので、気づいたら広がる/狭まるっていうのが数ヶ月おきに発生し得る。ユースケースによるが”それじゃ困る！”場合も多いだろう。というか、うちがそれ。

そのため、何かしらの仕組みを持ってNetskopeからの送信元グローバルIPアドレスを公開させてあげたい、というわけ。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80163/9ee37882-a613-8baf-c553-678beb6c7925.png)


# 本環境を利用するユースケース例

1. 弊社のお客様環境に対するメンテナンス用途のアクセス
2. 一部のセキュリティレベルの高いSaaSへのアクセス
3. (↑を重複しているが) セキュリティルーム運用
    - “セキュリティルームのみからしかアクセスさせない”ために、そこ部屋からのアクセス時のみグローバルIPアドレスを特定のものに固定化させたい

# 実現したいこと

- Netskopeの機能をフル利用しつつ送信元グローバルIPアドレスを固定化したい
    - 特にSkope ITという機能は必須

# 実現案の検討

## Netskope Private Access の導入

### 結果

`没案`

### 概要

- https://www.netskope.com/jp/products/private-access
- ビットキー用のNetskopeインスタンスが立ち上がるようで、結果的にGIPを固定化あるいは非常に狭い範囲で運用できる。

### 問題点

- これを使うと、ゼロトラストのメイン機能であるSkope ITが使用できなくなってしまう
- つまりビットキーがNetskopeを使っている理由を自ら潰すことになってしまう

## Netskope Dedicated Egress IP Addresses の導入

### 結果

`没案`

### 概要

- https://www.netskope.com/jp/resources/data-sheets/netskope-dedicated-egress-ip-addresses
- これぞまさに！という機能であり、我々がやりたいグローバルIPアドレスの固定化を完全にサポートしている

### 問題点

- 詳しいことは自社のNetskope担当営業さんと会話して欲しいのだが、とりあえず有象無象の企業では導入ハードルが非常に高い

## Netskopeの上位にフォワードプロキシを噛ませる

## 結果

`採用`

### 概要

Netskopeの標準機能の一つである[forward to proxy](https://docs.netskope.com/en/forward-to-proxy-integration.html)という機能を使い、Netskopeから上位のフォワードプロキシに転送させる。
プロキシが中間に噛むことになるので、最終的な宛先からみた送信元GIPはプロキシのものになる。
つまり、プロキシを自前のクラウド環境に用意し、そこでGIP固定化しておけば、こちらのやりたいことを満たせる

### 問題点

- プロキシは全てこちらで管理する必要がある。
    - プロキシへの転送機能はサポートされているが、その先のどうこうは当たり前だけどサポートしてくれない
- 理論上、間違いなくレスポンスタイムが伸びるはず
    - まぁ体感はまだしも、恐らく回線速度を計測するとかなり落ちることになるはず
    - Netskopeから出てきた通信全てがインターネットを経由してAWSに入ってきて、そこからさらに出てくる構成なのでこれはどうしようもない
    - NetskopeがAWSにいるなら、インターコネクト通ることでこの懸念を多少は減らせるかもだが・・・
    - ただし今回の想定されるユースケースのように特定時間における特定の端末からの特定通信のみに限定されるなら、多少の体感の悪さは許容できるはず
- 従量課金がエグいことになるかもしれない
    - 何も考えずに実装して、全社員/全端末の通信に対して適用させると、アウトバウンドのトラフィックが恐ろしいことになる
    - 超ざっくり計算で、30GB/h流れると想定して月10万前後ぐらい
    - ただし今回の想定されるユースケースのように特定時間における特定の端末からの特定通信のみに限定されるならここはほぼ無視できる金額に抑えられるはず

## 結論

プロキシ案で進める。

そもそも他の案の場合、Netskopeの機能を使えなくなるので採用することはできない。

# プロキシ案の具体実装

![SWED Team (11).jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80163/ce9b426d-1ced-4789-2824-800ded12c3f1.jpeg)


## ポイント

社内からのインターネット出口を一手に引き受けることになるため、下手するとかなりのトラフィックが流れることが予想される。
この部分で障害が起きた場合、社内業務を止めることにも繋がり、影響が非常に大きい。
パフォーマンスを発揮しつつも可能な限り可用性を高めていきたい。

また、こんなビジネスの本質からは離れた部分であまりリソースを割きたくはないため、可能な限り運用コストを下げたい。

## ざっくりまとめ

- 枯れたOSSであるSquidを使う
    - TLSを仲介すべく、 `HTTP CONNECT メソッドをサポート` させる
    - 逆に言えばそれ以外は無視してもOK
- AWSで動かす
    - 後述するがGCPでは動かない, Azureは不明
- Squidコンテナを `ECS + Fargateで稼働` させる
    - コンテナ化してフルマネージドなオーケストレーション環境で動かすことで運用保守の手間を可能な限り小さくさせる
    - マルチAZ対応をさせ、ある程度障害には強くさせる
        - 場合によっては緊急時のセキュリティルームでの操作が必要になるかもしれず、そのときに本環境が障害で動かないから対応できない！なんて話にならないので

## 採用プロキシ

- 上述の通り枯れに枯れまくった`Squid`を使う
    - 他にも軽量なプロキシなどは存在するが、こんな何かあった時の影響が大きい部分で新しいプロキシソフトウェアを使うべきではないと考える
- Squidをコンテナ化して、何かしらのオーケストレーション環境で稼働させる
- コンテナ自体は有名どころが既に作ってくれているのでそれらを使い自前ではビルドしない
- ただしSquidはコンフィグ次第でパフォーマンスが変わるので、そこはこちらで用意し、コンテナに渡してSquidを動かしてもらう
- その過程で、コンフィグだけを仕込んだSquidを用意するために簡易的なビルドを行うのは許容する
    - ※ゴリゴリにカスタマイズはしない, そういう部分でのメンテナンスコストは増やさない

Squidの設定として以下を組み込む。

- `キャッシュ無効化`
    - Netskopeなどを経由しておりキャッシュの意義が薄く、下手にキャッシュを持つことによる不具合を防ぎたい
- `アクセスログフォーマットの変更`
    - デフォルトのログフォーマットではUAなどやクライアントIPの情報が載らないため
- `アクセスリストの全開放`
    - 上段のネットワークACLにて厳格な送信元制限を行うためSquidの設定としては全開放状態とする
- `ファイルディスクリプタの上限調整`
    - デフォの1024では多量のコネクションが確立された際に負荷は大したことがないのに実際のプロキシとしてのパフォーマンスが著しく下がる可能性があるため

- サンプルコンフィグ
    
    ```squid.conf
    # 必要とするTCPポートの定義
    acl SSL_ports port 443		# https
    acl Safe_ports port 80		# http
    acl Safe_ports port 21		# ftp
    acl Safe_ports port 443		# https
    acl Safe_ports port 70		# gopher
    acl Safe_ports port 210		# wais
    acl Safe_ports port 1025-65535	# unregistered ports
    acl Safe_ports port 280		# http-mgmt
    acl Safe_ports port 488		# gss-http
    acl Safe_ports port 591		# filemaker
    acl Safe_ports port 777		# multiling http
    acl CONNECT method CONNECT
    acl NOCACHE src all
    
    # 不要なポートでの接続を拒否
    http_access deny !Safe_ports
    http_access deny CONNECT !SSL_ports
    http_access allow all
    
    # キャッシュの無効化
    cache deny NOCACHE
    cache_dir null /dev/null
    
    # TCP:3128でのリッスン
    http_port 3128
    
    # ロギング設定
    cache_store_log none
    logfile_rotate 0
    access_log daemon:/var/log/squid/access.log combined
    
    # その他もろもろ
    coredump_dir /var/spool/squid
    forwarded_for on
    ipcache_size 5120
    max_filedescriptors 65535
    ```
    

## 環境

* そもそもビットキーはオンプレ環境を有していないので選択肢として除外 (そもそもメンテめんどいし)
* クラウド環境としては`AWS`を採用する
  * ビットキーはGCPをメインに利用しているが、とはいえマルチクラウドでもあるためAWS自体は細々と利用していた
  * その中でどのクラウドプラットフォームを使っても良かったが、後述の理由によりAWSを使うことにした

### 理由

* GCPのロードバランサー(LB)では`HTTP CONNECTメソッドを通さない`から
  * そのため、Netskopeから出てきたHTTPS通信をプロキシが受け付けてくれない
  * 現代ではほぼ全ての通信がHTTPSである以上、このデメリットは受け入れられない
* 一方、AWSのLBでは、L3ロードバランサーになれるためHTTP CONNECTを捌ける
  * つまりTCPをそのまま扱うことができ、それ以上のレイヤーには関与しない
  * これでHTTP CONNECTをそのままスルーさせてバックエンド側につないでいくことができる

### NW

* AWSのデフォルトVPCとは別に`専用のVPCを1つ用意`する
* `パブリックサブネットとプライベートサブネットを分離して用意`し、かつ、`各AZにそのサブネットが配置`されるような構成とする
* 後述のFargateなどで生まれたコンテナインスタンスが各AZに存在することができるような構成とする
* パブリックサブネットも各AZに用意することで、LBなどを冗長化して配置できるようにする
* パブリックサブネットはほぼリソースが存在しないことから/28を割り当て、プライベートサブネット側は/24を割り当てる
* サンプル
    - public
        - netskope-proxy-subnw-public-1a: 192.168.200.0/28
        - netskope-proxy-subnw-public-1c: 192.168.200.16/28
        - netskope-proxy-subnw-public-1d: 192.168.200.32/28
    - private
        - netskope-proxy-subnw-private-1a: 192.168.201.0/24
        - netskope-proxy-subnw-private-1c: 192.168.202.0/24
        - netskope-proxy-subnw-private-1d: 192.168.203.0/24

* VPCにアタッチする形でインターネットゲートウェイを一つ用意する
* 本構成にて、プライベートサブネットに属するインスタンスが`アウトバウンド方向の通信を行うためにNATゲートウェイを複数用意`する
* 先述の通り各AZにサブネットが存在するため、それぞれにNATゲートウェイを配置させAZ障害時の通信不可に備える
* プライベートサブネットからは不特定多数への通信がパブリックサブネット側にルーティングされるようにルートを構成する

[](https://www.notion.so/d843bef1bfe3410a83309f13ea459ade?pvs=21)

* 後述のコンテナやLBは上記のプライベートサブネットを必ず3つ指定するように構成する
  * この設定により、それぞれのリソースがマルチAZ構成となり高い可用性を発揮できるようになる

### CaaS / PaaS

* `ECS + Fargateによるフルマネージドなコンテナオーケストレーション環境`とする
* 可能な限り運用コストを下げ、オートスケーリングなどによる可溶性やパフォーマンス向上も手軽に実装する

- サンプルDockerfile
    
    ```docker:Dockerfile
    from ubuntu/squid
    
    ADD configs/squid.conf /etc/squid/
    
    ENV TZ Asia/Tokyo
    EXPOSE 3128
    
    ENTRYPOINT ["entrypoint.sh"]
    CMD ["-f", "/etc/squid/squid.conf", "-NYC"]
    ```

ちなみに・・・・App Runnerを使えたらもっと楽だったんだけどね。LBの用意やVPC、セキュリティグループの構成などがかなり簡単になる。ただし、App Runnerは元々Webアプリを前提にしたサービスであるため、Squidを利用するときにうまく動かない可能性があった。App Runnerで自動的に構築されるLBはALBであり、これではGCPのLBのようにHTTP CONNECTを通さない可能性がかなり高かった。
当時は検証やトラブルシュートをしている時間がほとんどなかったため、まず間違いなく動作できるようであろうECS + Fargateの環境で進めることにした次第。

よって、2023年の今現在ではまた少し事情は違うと思う。きっと。

### ドメイン

* Route53にて管理する (この辺の具体は割愛
* もうちょい話すと、元々弊社のドメイン管理はAWSでほとんど行っていたため、その中から本兼用のサブドメインを切り出した感じ

### 通信経路

* インバウンドはLB経由、アウトバウンドはNATさせる
* サブネット分離によるルーティングについては、[NW](https://qiita.com/gzock/items/67eca6e53a531695e809#nw)を参照すること

### スケーラビリティ

* `NLBを利用してバックエンドにコンテナクラスタを配置する`。
* 必ずオートスケールをさせる, 閾値としてCPU70%, Mem: 70%とする
  * これは一般的なプロキシとして、相手先からのレスポンスを待つ間、そのSquidのスレッドがブロッキング状態になってしまうため、見かけ上CPU使用率が嵩みやすいと推測しているためである
* また、HTTPコンテンツをメモリにキャッシュする都合上、メモリも食いやすい
  * そのため、どちらか一方をメトリクスとするのではなく、どちらもトリガーとする

### 可用性向上

* Fargateでのデプロイの際にコンテナインスタンスを複数台稼働させておく
* [NW](https://qiita.com/gzock/items/67eca6e53a531695e809#nw)で示した通り複数サブネットで予め構成しているため、自動的にFargateがマルチAZなコンテナ配置を行ってくれる

ただしこれでは同リージョンに影響を及ぼす障害発生時に問題がある。
リージョン丸ごと死んだときは`Netskopeを無効化して乗り切る`。

* NLBはクロスゾーン負荷分散を有効化し、3つのAZを常に有効活用できるようにしておく
  * 障害時にもAZの迂回を自動的に行ってくれるようになるため可用性向上にも寄与する
* AZをまたぐトラフィックに対する従量課金が増えてしまうことになるが、金額的に無視できる程度($0.010/GB)であり、システム的なメリットの方が明らかに大きい。

### セキュリティ

* パブリックサブネットにネットワークACLをアタッチしてNetskopeが保有するGIP + 弊社オフィスのGIPからの通信を許可させる

* NLBにはセキュリティグループをアタッチすることができない。Fargateに対してしかできない
  * この場合、通信を殺すのはプライベートサブネットまで入り込んでから行われる
* 本来不正な通信をプライベート側まで流入させるのはよろしくないので、パブリックサブネット側に対するネットワークACLによって最前面で意図しない不正な通信を遮断する仕組みとする

これにより、オフィスからの直接的な検証を可能にしつつ、本来必要なNetskopeからのプロキシ転送だけをさせる。
これをやっておかないと、オープンなプロキシとなってしまってマジでやばい。

参考程度にNetskopeの今現在のグローバルIPアドレス一覧を確認する方法を載せておく。

- Settings > Security Cloud Platform > Enforcement > NETSKOPE IP RANGES
    
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80163/5aac875b-26df-43ca-d035-9497b3641b44.png)


### モニタリング

一旦、CloudWatchにメトリクス集約とロギングを全部任せる。後程Datadogと連携させて、そちら側でも参照できるようにしておく。

### アラーティング

- Personal Health DashboardによるAWSそのものの障害検知
- LB配下のホスト数変化
    - 最小数:3を下回ると通知
- 請求額
    - 月予算$1000として、予測値が`80% = $800`を超える試算がなされた場合通知
    - 試算結果での算出であるため、"このままだとヤバそう"にいち早く気づける
- アウトバウンド方向のトラフィック量
    - 1時間で2.5GB以上、AWS外へのトラフィックが流れるようであれば通知

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80163/65f8700b-1b97-5a88-88ac-88774134a396.png)


# あとがき

- あとで詳細を誰かが書いてくれるかと思うが、 [本環境を利用するユースケース例](https://qiita.com/gzock/items/67eca6e53a531695e809#%E6%9C%AC%E7%92%B0%E5%A2%83%E3%82%92%E5%88%A9%E7%94%A8%E3%81%99%E3%82%8B%E3%83%A6%E3%83%BC%E3%82%B9%E3%82%B1%E3%83%BC%E3%82%B9%E4%BE%8B)で言及したユースケースをすべて満たすことができるようになった
- 元々はマルチAZ環境で動かしていたが、正直まぁそんなに障害起きるものでもないしっていうことで今は1AZ環境にしてある
    - 万が一のことが起きたら起きたで別にすぐ環境をマルチAZに戻せばよいだけなので
    - 詳細はここで述べていなかったが、本環境はすべてTerraformでコード化しているため、すごい簡単に環境を作ったり壊したり、増やしたり減らしたりできる
- 結構、力技ではあるのでこれを使って全社員のトラフィックを捌く・・・みたいなことは多分やめたほうがいい
    - Squid + Fargateは全然耐えられるだろうけど、コストが結構なものになる・・・はず
- これ作ったのもう1.5年以上前の話になるので正直今ならもう少し違うやり方があるかも・・・しれない