TECH PLAY

NTTドコモビジネス

NTTドコモビジネス の技術ブログ

613

はじめに こんにちは、データプラットフォームサービス部の下公、真山、イノベーションセンターの原田です。 NTTコミュニケーションズ株式会社 (以下、NTT Com) は、世界最大級のイベントネットワークである 「Interop Tokyo 2022(会場:幕張メッセ、会期:2022年6月15日〜17日)」 において構築されるShowNet *1 に対して、400G-ZR *2 を用いたイーサネット回線を提供し、実運用することに成功しました。 また、今年のShowNetでは多種多様なローカル5Gシステムが集合し、マルチベンダーでの5Gコア/RAN端末の相互接続や多様なローカル5G実現方法について提案します。NTT Comはコントリビューターとしてスライシングの検証と、ローカル5Gの低遅延通信を活用したアプリケーションについて紹介します。 400G-ZRを用いたイーサネット回線 NTT Comは、長距離での400ギガビットイーサネット接続を可能とする400G-ZRを用いて、今年初めてShowNetへ400Gアクセス回線を提供しました。 本回線はトランジット・IXに接続し、Interop会場の出展社・来場者のインターネットアクセスに利用されています。 400G-ZRは、コヒーレント伝送技術を利用した光トランシーバをスイッチ/ルータに直接搭載し、接続する規格です。 専用の伝送装置を複数用意する必要があった従来の構成に比べ、よりシンプルな構成を実現できるため消費電力が少なく、また標準化により異なるメーカー同士であっても柔軟な長距離接続が可能となっており、DCIなどの拠点間通信に適用する際、特に注目されている技術です。 次世代の拠点間通信のプロトタイプとして、今回は光増幅器(アンプ)にあたる光トランシーバ(OSFP-LS)と8チャネルのMux/Demuxを行えるブレークアウトケーブル(Colorless Fiber Mux/Demux)、OSFP 400G-ZRをすべて1Uのスイッチに搭載し、省スペースで大容量の長距離伝送を実現しました。 また、本回線の構築にあたり、ShowNetを構成する機器の回線に対する試験を事前に実施しました。 スループットや遅延、ゆらぎなどの一般的な試験項目に加えて、400G-ZRの評価に必要となる光トランシーバ内部で実施されているエラー訂正の状況についても確認し、拠点間で送受信される通信の品質が実運用に耐えるものであることを確認しました。 本回線の試験についてはArista Networksとアンリツ株式会社の協力のもと、実施しました。 ローカル 5G の技術検証・展示内容 今年のShowNetでは5G/ローカル5Gを取り入れ、下記のテーマで多数のベンダーがコントリビューターとして参加し、技術検証・アプリケーションを展示しています。 マルチベンダーでの5Gコア/RAN/端末の相互接続への挑戦 多様なローカル5G実現方式 ローカル5Gを利用したデモンストレーション マルチベンダーによるEnd to Endスライシング 私たちは、ユーザーの要求に応じてネットワークやエッジコンピューティング環境といったリソースを柔軟に提供することを目標としています。 それを達成するには、ユーザーやアプリケーション単位での細かい粒度でネットワークスライシング技術が必要になります。 今回、NTT ComではShowNetにて、マルチベンダーによる基地局(gNB)から5Gコアの先のData Network (N6) 含めたEnd to Endスライシングの実現にチャレンジしました。 5Gではユースケースに合わせて異なる性能や機能のネットワークを提供できることが特徴とされており、「大容量(eMBB)」「超低遅延(URLLC)」「大量接続(mMTC)」の3つの要件が定義されています。 ネットワークスライシングとは、単一のネットワークを複数の論理的なネットワークに分割し、それぞれの論理ネットワークに対してユースケースに合わせた異なる経路や品質制御を提供する仕組みのことです。 ネットワークスライシングにより、高精細カメラの映像を流すスライスは大容量(eMBB)の経路を選択したり、自動運転車のような低遅延を要求するスライスは遅延の少ない経路(URLLC)を選択したりすることが可能となります。 5GCのアーキテクチャの大きな特徴として、マイクロサービス志向のサービス分割がなされ、コンテナプラットフォームでの運用を想定している点が挙げられます。 コンテナプラットフォームを採用することで、機能単位での迅速なデプロイや負荷に応じたスケーリング、故障時のオートヒーリングなどが可能となります。 また、商用使用において特に期待される機能として、E2EでのネットワークスライシングやAPIを介したオーケストレーション、トラフィック制御の容易性が挙げられます。 本検証では、コンテナ型5GCとしてCapgemini Engineering ViNGCを、コンテナ型UPFとしてKaloom Cloud Edge Fabricを用いて構築します。 ユーザー端末(UE)と基地局(gNB)は、スパイレントコミュニケーションズ社のシミュレータLandslideを使用します。 Capgemini Engineering ViNGCは、Kubernetes/OpenShiftで動作する5GCであり、各Functionは1つ以上のPodとして構成されます。 C/U分離が可能であることから、UPFはKaloom UPFを使用します。Kaloom UPFはOpenShift上で動作するUPFであり、Edgecore Networks社のホワイトボックススイッチ上で動作します。 LeafとUPFからなる1つのクラスタで、複数の仮想UPFを動作させることが可能です。 N3/N6でのネットワークスライシングを実現できることから、Capgemini Engineering ViNGCとの異ベンダー構成として検証していきます。 今回は、5Gの3要件として挙げられている”eMBB(高速大容量)”、”URLLC(超低遅延)”、”mMTC(超大量端末)”を想定した3つのスライスを作成し、N6でのスライシングによるそれぞれ異なる経路でネットワークに抜けていくような構成を構築しました。 UPFはBGPでShowNetのバックボーンに接続されます。 上述したCapgemini Engineering ViNGCおよびKaloom UPFを用いた構成において、スライシングの分割はgNBでのDNN及びS-NSSAIとVLAN mappingによりN3/N6区間において実現し、N6から先のネットワークはFlex-Algoによりそれぞれ異なる経路を通って上流ネットワークに抜けていくという、gNBからData Network間のEnd to Endでのネットワークスライシングにチャレンジしました。 検証結果についてはShowNet会場にて展示します。ぜひお越しいただければと思います。 ローカル5Gの低遅延通信を利用した遠隔操作アプリケーション NTT Comは、スライシング技術などの最先端な技術検証だけでなく、現在黎明期であるローカル5Gの様々なユースケースを創造することも、自社のミッションだと考えています。 従来技術では解決が困難であったお客様の課題をローカル 5G を用いることでいかに解決できるのか?といった観点のもと、Smart Data Platformを含む様々なアプリケーションとの相互検証を実施しています。 今回のShowNetでは、NTT Comのローカル5G環境が提供する低遅延通信を活用し、ソリトンシステムズ社のSmart-telecaster Zao-X *3 を搭載した遠隔操作型ラジコンカーを用いたアプリケーションを展示します。 Smart-telecaster Zao-Xは4Kの映像伝送が可能で高精細映像をモバイル回線で伝送できます。 H.265/HEVC コーデックにより転送データ量を抑え、ローカル5GやMECといった低遅延通信と親和性の高いアプリケーションです。 ラジコンカーにカメラを搭載し、Zao-Xを通じて走行中の映像をローカル5Gシステムを経由してリアルタイムに伝送します。 またローカル5Gシステムを経由してラジコンカーの走行を制御し、遠隔でも操縦性に違和感なく低遅延通信を実感いただけます。 ShowNet会場にて設置しているシールドテント内にローカル5G端末およびZao-Xを搭載したラジコンカーを配置し、Data Network (N6) に接続しているサーバーにてラジコンカー視点での映像およびラジコンカーの操縦をご覧いただけます。 おわりに この記事では、Interop Tokyo 2022(会場:幕張メッセ、会期:2022年6月15日〜17日)において構築されたShowNetについて、400G-ZRを用いたイーサネット回線の提供、およびローカル5GのマルチベンダーによるEnd to Endでのスライシングとアプリケーションの検証を紹介しました。今後は、今回のフィールドテストの成果を踏まえ、NTT Comのネットワークサービスへの応用について検討していきます。 *1 : ShowNet:Interop Tokyoにおいて、様々な機器・サービスの相互接続性検証を実施するとともに、近未来のサービスアーキテクチャを実際に動かしている形で見ることができる世界最大規模のライブネットワークデモンストレーション。Interop Tokyoへの出展社がインターネットへの接続性を利用して製品の動態展示のほか、来場者のインターネットへのアクセスとしても利用されるネットワーク。 https://www.interop.jp/2022/shownet/ *2 : 400G-ZR:業界団体であるOIF(Optical Internetworking Forum)が標準化し、単一波長により400ギガビットイーサネットを最大40kmまで接続可能とする光トランシーバの規格。光増幅器(アンプ)を組み合わせることで最大120kmまでの長距離接続にも対応する。 *3 : Zao-X:モバイル回線を利用して高画質の動画をライブ中継する”Smart-telecaster™”シリーズの最新モデルです。 モバイル回線に最適化されたSolitonオリジナルプロトコル「RASCOW2」を採用。映像伝送の課題だった遅延を大幅に短縮し、解像度を4Kに拡大。 https://www.soliton.co.jp/lp/zao-x_h/
サマリ Single-AS なネットワークにおいて、 SR-MPLS + VPNv4 による L3VPN を実現 IOS XR + Junos の Multi-vendor 環境で動作検証に成功 この記事は Multi-AS Segment Routing 検証連載の1つです。目次は こちら 概要 イノベーションセンターの三島です。普段の業務では Multi-AS Segment Routing に関する control plane/data plane の技術検証や、運用効率化のためのコントローラー開発などを行なっています。 本記事では SR-MPLS を用いた L3VPN の Multi-vendor 環境における検証例を、サンプルトポロジーやコンフィグ例を添えながら紹介します。 今回の記事ではまず Single-AS での L3VPN 検証結果を紹介し、 Multi-AS 構成については次回以降の記事で扱っていきます。 L3VPN とは L3VPN とは、複数のネットワーク同士をレイヤー3で接続する仮想的な専用回線を構築する技術です。 こちらの図に L3VPN の概要を示しました。 この例では、 A、B の2種類の顧客を1つのコア網に収容しています。 VPN は仮想的な専用回線であり、図の通り同一の顧客同士は通信可能にしつつ、異なる顧客同士は通信不可になるよう構成します。 L3VPN は レイヤー3 での IP の相互接続を担保する VPN であり、異なる拠点同士は別の Prefix で構成します。 L3VPN には、顧客を VRF 単位で識別する per-VRF L3VPN と、 CE 単位で識別する per-CE L3VPN があります。 VRF とは、1つのルーターを複数のルーティング面に多重化する技術です。 per-VRF L3VPN は、複数の顧客を各々の VRF で分離し識別する方式です。 一方、 per-CE L3VPN では顧客の CE ルーターを単一の default-VRF に収容し、 nexthop で識別する方式です。 今回の検証では、顧客側に専用のゲートウェイとなる CE ルーターが不要となる利点や、 VRF 単位で経路表を分離できる利点を考慮し、 per-VRF L3VPN を扱います。 ある PE における、収容対象のセグメント、当該 PE のアドレス、顧客識別子、その他メタデータの組などの L3VPN の情報は MP-BGP の VPNv4/VPNv6 で伝達します。 VRF の識別子を Route Distinguisher(RD)、経路の識別子を Route Target(RT)と呼び、これらの組み合わせで収容する顧客を識別します。 Segment Routing とは Segment Routing(SR)とは、 RFC8402 で標準化された次世代の経路制御技術です。 SR では転送対象や処理を Segment と呼ばれる単位で扱い、ソースルーティングのパラダイムに基づき転送します。 ソースルーティングとは、現在最も一般的であるパケットの宛先アドレスを基にした転送先決定ではなく、送信元があらかじめパケットに埋め込んだ情報によって転送先を決定するルーティング手法です。 各パケットに Segment ID(SID)のリストである Segment List を付与することで、そのパケットに対する網内での経路や処理などを指定します。 こちらの図に SR-MPLS による転送例を示しました。この例では顧客 A、B の通信に対して VPN を提供するとともに、 PE1 → P1 → P2 → PE2 となるような Traffic Engineering(TE)を提供しています。 PE1 は顧客 A からパケットが送られた際、顧客 A を示す VPN ラベル(SID: 24001)を付与するとともに、事前に定義した SR Policy に従い、 P1 → P2 → PE2 という経路を示す TE ラベルとなる Segment List (16004/16003)を付与し、 P1 へと転送します。 P1 は パケットに付与された Segment List の先頭要素(Active Segment)を参照し、処理します。ここでは P2 を示す SID(16004)が付与されているため、ラベルを Pop することで Active Segment を進めるとともに、 P2 へと転送します。 P2 は Active Segment を参照し、 Active Segment を進めると共に、 PE2 へとパケットを転送します。 PE2 は Active Segment を参照し、 Segment が示す L3VPN 動作を実施し、顧客 A へとパケットを転送します。 これらの動作により、本記事の目的である VPN が実現できます。 また、送信元 PE が付与する Segment List を変更することにより、柔軟に経路を変更する TE も実現可能となります。 このように、 SR では Segment List に格納された SID により、 TE や VPN など様々な動作を実現できます。 検証例の紹介 ここまでの話で、 SR による TE や VPN の実現手法について説明しました。 以降は L3VPN の検証例を実際のコンフィグ例を添えつつ紹介します。 サンプルトポロジー&想定するユースケース 検証のため、下記のようなトポロジーを作成します。 4つのルーターからなる SR domain をコア網として構成し、顧客 A、B を収容します。 検証環境は Multi-vendor に構成します。使用する機器は下記の通りです。 PE1、P1: Cisco IOS XR 7.4.1 PE2、P2: Junos OS 21.3R1.9 基本設定 ここからは機器設定と動作確認を行います。 基本的なインターフェース設定等は既に実施されているものとし、 SR の SID を広告する IS-IS と、 L3VPN を実現する BGP VPNv4 の設定・動作確認を実施します。 IS-IS - 設定 IS-IS の SR 拡張を利用し、 SR-MPLS の SID 情報を広告します。 また、運用効率化のため MPLS OAM の設定もしておきます。 IOS XR router isis 1 is-type level-2-only net 49.0000.0000.0aff.0001.00 segment-routing global-block 16000 23999 address-family ipv4 unicast metric-style wide segment-routing mpls ! interface Loopback0 address-family ipv4 unicast prefix-sid index 0 ! ! interface GigabitEthernet0/0/0/0 point-to-point address-family ipv4 unicast ! ! interface GigabitEthernet0/0/0/1 point-to-point address-family ipv4 unicast ! ! ! mpls oam ! segment-routing Junos set protocols isis interface ge-0/0/0.0 level 2 metric 10 set protocols isis interface ge-0/0/0.0 point-to-point set protocols isis interface ge-0/0/1.0 level 2 metric 10 set protocols isis interface ge-0/0/1.0 point-to-point set protocols isis interface lo0.0 passive set protocols isis source-packet-routing srgb start-label 16000 set protocols isis source-packet-routing srgb index-range 8000 set protocols isis source-packet-routing node-segment ipv4-index 2 set protocols isis level 1 disable set protocols isis level 2 wide-metrics-only set protocols mpls icmp-tunneling set protocols mpls interface ge-0/0/0.0 set protocols mpls interface ge-0/0/1.0 set interfaces ge-0/0/0 unit 0 family inet address 10.0.2.2/30 set interfaces ge-0/0/0 unit 0 family iso set interfaces ge-0/0/0 unit 0 family mpls set interfaces ge-0/0/1 unit 0 family inet address 10.0.3.2/30 set interfaces ge-0/0/1 unit 0 family iso set interfaces ge-0/0/1 unit 0 family mpls set interfaces lo0 unit 0 family inet address 10.255.0.3/32 set interfaces lo0 unit 0 family iso address 49.0000.0000.0aff.0003.00 SR Global Block(SRGB)の変更後は再起動が必要です。 IS-IS - 動作確認 IS-IS ネイバーの状態と SID Table を確認します。 IOS XR RP/0/RP0/CPU0:pe01#show isis neighbors Mon Jun 6 18:48:40.985 JST IS-IS 1 neighbors: System Id Interface SNPA State Holdtime Type IETF-NSF p01 Gi0/0/0/0 *PtoP* Up 22 L2 Capable p02 Gi0/0/0/1 *PtoP* Up 21 L2 Capable Total neighbor count: 2 RP/0/RP0/CPU0:pe01#show mpls forwarding Mon Jun 6 18:53:26.322 JST Local Outgoing Prefix Outgoing Next Hop Bytes Label Label or ID Interface Switched ------ ----------- ------------------ ------------ --------------- ------------ 16001 Pop SR Pfx (idx 1) Gi0/0/0/0 10.0.0.2 2529 16002 16002 SR Pfx (idx 2) Gi0/0/0/0 10.0.0.2 5446 16002 SR Pfx (idx 2) Gi0/0/0/1 10.0.1.2 0 16003 Pop SR Pfx (idx 3) Gi0/0/0/1 10.0.1.2 5366 24000 Pop SR Adj (idx 1) Gi0/0/0/1 10.0.1.2 0 24001 Pop SR Adj (idx 3) Gi0/0/0/1 10.0.1.2 0 24002 Pop SR Adj (idx 1) Gi0/0/0/0 10.0.0.2 0 24003 Pop SR Adj (idx 3) Gi0/0/0/0 10.0.0.2 0 Junos user@pe02> show isis adjacency Interface System L State Hold (secs) SNPA ge-0/0/0.0 p01 2 Up 25 ge-0/0/1.0 p02 2 Up 24 user@pe02> show route protocol isis table mpls.0 mpls.0: 15 destinations, 15 routes (15 active, 0 holddown, 0 hidden) + = Active Route, - = Last Active, * = Both 16 *[L-ISIS/14] 07:36:19, metric 0 > to 10.0.3.1 via ge-0/0/1.0, Pop 16(S=0) *[L-ISIS/14] 07:36:19, metric 0 > to 10.0.3.1 via ge-0/0/1.0, Pop 17 *[L-ISIS/14] 00:22:25, metric 0 > to 10.0.2.1 via ge-0/0/0.0, Pop 17(S=0) *[L-ISIS/14] 00:22:25, metric 0 > to 10.0.2.1 via ge-0/0/0.0, Pop 16000 *[L-ISIS/14] 00:21:54, metric 30 to 10.0.2.1 via ge-0/0/0.0, Swap 16000 > to 10.0.3.1 via ge-0/0/1.0, Swap 16000 16001 *[L-ISIS/14] 00:22:07, metric 20 > to 10.0.2.1 via ge-0/0/0.0, Pop 16001(S=0) *[L-ISIS/14] 00:22:07, metric 20 > to 10.0.2.1 via ge-0/0/0.0, Pop 16003 *[L-ISIS/14] 07:36:19, metric 10 > to 10.0.3.1 via ge-0/0/1.0, Pop 16003(S=0) *[L-ISIS/14] 07:36:19, metric 10 > to 10.0.3.1 via ge-0/0/1.0, Pop これらの結果より、 IOS XR、Junos 共に LinkState が正しく広告され、 SID Table が生成されたことが確認できました。 VRF、MP-BGP VPNv4 - 機器設定 顧客 A、B の VRF 設定と、経路を広告する MP-BGP L3VPN を設定します。 VPN の各パラメータは下記の通りです。 AS 番号: 65000 VRF 100: RD/RT 65000:100 VRF 200: RD/RT 65000:200 IOS XR vrf 100 rd 65000:100 address-family ipv4 unicast import route-target 65000:100 ! export route-target 65000:100 ! ! ! vrf 200 rd 65000:200 address-family ipv4 unicast import route-target 65000:200 ! export route-target 65000:200 ! ! ! interface GigabitEthernet0/0/0/2 vrf 100 ipv4 address 192.168.0.254 255.255.255.0 ! interface GigabitEthernet0/0/0/3 vrf 200 ipv4 address 192.168.0.254 255.255.255.0 ! router bgp 65000 bgp router-id 10.255.0.1 address-family vpnv4 unicast ! neighbor-group ibgp remote-as 65000 update-source Loopback0 address-family vpnv4 unicast ! ! neighbor 10.255.0.2 use neighbor-group ibgp ! neighbor 10.255.0.3 use neighbor-group ibgp ! neighbor 10.255.0.4 use neighbor-group ibgp ! vrf 100 rd 65000:100 address-family ipv4 unicast label mode per-vrf redistribute connected ! ! vrf 200 rd 65000:200 address-family ipv4 unicast label mode per-vrf redistribute connected ! ! ! Junos set policy-options policy-statement EXPORT-POLICY-100 term ROUTE-TARGET then community add VRF100-65000-RT set policy-options policy-statement EXPORT-POLICY-100 term REDIST-DIRECT from protocol direct set policy-options policy-statement EXPORT-POLICY-100 term REDIST-DIRECT then accept set policy-options policy-statement IMPORT-POLICY-100 term ROUTE-TARGET-65000 from community VRF100-65000-RT set policy-options policy-statement IMPORT-POLICY-100 term ROUTE-TARGET-65000 then accept set policy-options community VRF100-65000-RT members target:65000:100 set policy-options policy-statement EXPORT-POLICY-200 term ROUTE-TARGET then community add VRF200-65000-RT set policy-options policy-statement EXPORT-POLICY-200 term REDIST-DIRECT from protocol direct set policy-options policy-statement EXPORT-POLICY-200 term REDIST-DIRECT then accept set policy-options policy-statement IMPORT-POLICY-200 term ROUTE-TARGET-65000 from community VRF200-65000-RT set policy-options policy-statement IMPORT-POLICY-200 term ROUTE-TARGET-65000 then accept set policy-options community VRF200-65000-RT members target:65000:200 set routing-instances 100 instance-type vrf set routing-instances 100 protocols bgp family inet unicast set routing-instances 100 interface ge-0/0/2.0 set interface ge-0/0/2 unit 0 family inet address 192.168.1.254/24 set routing-instances 100 route-distinguisher 65000:100 set routing-instances 100 vrf-import IMPORT-POLICY-100 set routing-instances 100 vrf-export EXPORT-POLICY-100 set routing-instances 100 vrf-table-label set routing-instances 200 instance-type vrf set routing-instances 200 protocols bgp family inet unicast set routing-instances 200 interface ge-0/0/3.0 set interface ge-0/0/3 unit 0 family inet address 192.168.1.254/24 set routing-instances 200 route-distinguisher 65000:200 set routing-instances 200 vrf-import IMPORT-POLICY-200 set routing-instances 200 vrf-export EXPORT-POLICY-200 set routing-instances 200 vrf-table-label set routing-options router-id 10.255.0.3 set routing-options autonomous-system 65000 set protocols bgp family inet-vpn unicast set protocols bgp group ibgp type internal set protocols bgp group ibgp local-address 10.255.0.3 set protocols bgp group ibgp neighbor 10.255.0.1 MP-BGP VPNv4 - 動作確認 下記2ステップで L3VPN の経路学習と転送動作を確認します。 BGP から受信した経路を確認 traceroute + MPLS OAM による Segment List の確認 IOS XR → Junos Junos 側の VPN ラベルを確認します。 mpls.0: 17 destinations, 17 routes (17 active, 0 holddown, 0 hidden) 18 (1 entry, 1 announced) *VPN Preference: 0 Next hop type: Router, Next hop index: 638 Address: 0x704e02c Next-hop reference count: 2 Next hop: via lsi.0 (100), selected Label operation: Pop Load balance label: None; Label element ptr: 0x780ffb0 Label parent element ptr: 0x0 Label element references: 2 Label element child references: 0 Label element lsp id: 0 Session Id: 0x143 State: <Active Int Ext LsiL3> Age: 51:18 Validation State: unverified Task: RT Announcement bits (2): 1-KRT 3-Resolve tree 3 AS path: I Ref Cnt: 0 (Stale Peers: 16104) Stale - Will time out in -32224 seconds Thread: junos-main user@pe02> show route table mpls.0 label 19 detail mpls.0: 17 destinations, 17 routes (17 active, 0 holddown, 0 hidden) 19 (1 entry, 1 announced) *VPN Preference: 0 Next hop type: Router, Next hop index: 628 Address: 0x704def4 Next-hop reference count: 2 Next hop: via lsi.1 (200), selected Label operation: Pop Load balance label: None; Label element ptr: 0x780ffb0 Label parent element ptr: 0x0 Label element references: 2 Label element child references: 0 Label element lsp id: 0 Session Id: 0x142 State: <Active Int Ext LsiL3> Age: 51:24 Validation State: unverified Task: RT Announcement bits (2): 1-KRT 3-Resolve tree 3 AS path: I Ref Cnt: 0 (Stale Peers: 16104) Stale - Will time out in -32228 seconds Thread: junos-main ラベル 18 が VRF 100(顧客 A)、19が VRF 200(顧客 B)に対応する VPN ラベルであることが確認できます。 IOS XR が受信した経路と、 traceroute の結果を確認します。 RP/0/RP0/CPU0:pe01#show route vrf 100 Mon Jun 6 19:13:33.372 JST Codes: C - connected, S - static, R - RIP, B - BGP, (>) - Diversion path D - EIGRP, EX - EIGRP external, O - OSPF, IA - OSPF inter area N1 - OSPF NSSA external type 1, N2 - OSPF NSSA external type 2 E1 - OSPF external type 1, E2 - OSPF external type 2, E - EGP i - ISIS, L1 - IS-IS level-1, L2 - IS-IS level-2 ia - IS-IS inter area, su - IS-IS summary null, * - candidate default U - per-user static route, o - ODR, L - local, G - DAGR, l - LISP A - access/subscriber, a - Application route M - mobile route, r - RPL, t - Traffic Engineering, (!) - FRR Backup path Gateway of last resort is not set C 192.168.0.0/24 is directly connected, 00:44:47, GigabitEthernet0/0/0/2 L 192.168.0.254/32 is directly connected, 00:44:47, GigabitEthernet0/0/0/2 B 192.168.1.0/24 [200/0] via 10.255.0.3 (nexthop in vrf default), 00:02:06 RP/0/RP0/CPU0:pe01#traceroute 192.168.1.254 vrf 100 Mon Jun 6 19:14:06.584 JST Type escape sequence to abort. Tracing the route to 192.168.1.254 1 10.0.0.2 [MPLS: Labels 16002/18 Exp 0] 54 msec 4 msec 14 msec 2 192.168.1.254 4 msec 5 msec 2 msec RP/0/RP0/CPU0:pe01#traceroute 192.168.1.254 vrf 200 Mon Jun 6 19:15:22.568 JST Type escape sequence to abort. Tracing the route to 192.168.1.254 1 10.0.1.2 [MPLS: Labels 16002/19 Exp 0] 7 msec 10.0.0.2 15 msec 17 msec 2 192.168.1.254 3 msec 9 msec 3 msec Junos → IOS XR IOS XR 側の VPN ラベルを確認します。 RP/0/RP0/CPU0:pe01#show mpls forwarding labels 24004 detail Mon Jun 6 19:52:54.388 JST Local Outgoing Prefix Outgoing Next Hop Bytes Label Label or ID Interface Switched ------ ----------- ------------------ ------------ --------------- ------------ 24004 Aggregate 100: Per-VRF Aggr[V] \ 100 2576 Updated: Jun 6 18:30:03.767 Label Stack (Top -> Bottom): { } MAC/Encaps: 0/0, MTU: 0 Packets Switched: 23 RP/0/RP0/CPU0:pe01#show mpls forwarding labels 24005 detail Mon Jun 6 19:53:02.618 JST Local Outgoing Prefix Outgoing Next Hop Bytes Label Label or ID Interface Switched ------ ----------- ------------------ ------------ --------------- ------------ 24005 Aggregate 200: Per-VRF Aggr[V] \ 200 2076 Updated: Jun 6 18:30:03.768 Label Stack (Top -> Bottom): { } MAC/Encaps: 0/0, MTU: 0 Packets Switched: 18 ラベル 24004 が VRF 100(顧客 A)、24005が VRF 200(顧客 B)に対応する VPN ラベルであることが確認できます。 Junos が受信した経路と、 traceroute の結果を確認します。 user@pe02> show route advertising-protocol bgp 10.255.0.1 100.inet.0: 3 destinations, 3 routes (3 active, 0 holddown, 0 hidden) Prefix Nexthop MED Lclpref AS path * 192.168.1.0/24 Self 100 I 200.inet.0: 3 destinations, 3 routes (3 active, 0 holddown, 0 hidden) Prefix Nexthop MED Lclpref AS path * 192.168.1.0/24 Self 100 I user@pe02> show route receive-protocol bgp 10.255.0.1 table bgp.l3vpn.0 bgp.l3vpn.0: 2 destinations, 2 routes (2 active, 0 holddown, 0 hidden) Prefix Nexthop MED Lclpref AS path 65000:100:192.168.0.0/24 * 10.255.0.1 0 100 ? 65000:200:192.168.0.0/24 * 10.255.0.1 0 100 ? user@pe02> traceroute 192.168.0.254 routing-instance 100 traceroute to 192.168.0.254 (192.168.0.254), 30 hops max, 52 byte packets 1 10.0.3.1 (10.0.3.1) 3.768 ms 1.947 ms 2.121 ms MPLS Label=16000 CoS=0 TTL=1 S=0 MPLS Label=24004 CoS=0 TTL=1 S=1 2 10.0.1.1 (10.0.1.1) 23.648 ms * 3.814 ms user@pe02> traceroute 192.168.0.254 routing-instance 200 traceroute to 192.168.0.254 (192.168.0.254), 30 hops max, 52 byte packets 1 10.0.3.1 (10.0.3.1) 6.866 ms 3.196 ms 9.220 ms MPLS Label=16000 CoS=0 TTL=1 S=0 MPLS Label=24005 CoS=0 TTL=1 S=1 2 10.0.1.1 (10.0.1.1) 7.612 ms * 12.066 ms これにより、目的であった Multi-vendor 環境における L3VPN を実現できました! まとめ 本記事では、 L3VPN や SR の概要を紹介するとともに、 Multi-vendor 環境における Single-AS での L3VPN の検証結果を紹介しました。 次回の記事では、我々がメインターゲットとして取り組んでいる、 Multi-AS での L3VPN について紹介します。 (2022/06/27 追記) 公開しました: [Multi-AS Segment Routing 検証連載 #2] SR-MPLS L3VPN in Multi-AS 参考資料 Segment Routing 関連技術の解説資料を Segment Routing 入門 として公開しています。 TE、VPN 双方の基礎知識から SR の応用的な技術まで解説していますので、併せてご覧ください。
サマリ Multi-AS での Segment Routing 活用について検証しています。 検証結果についてこれから連載します。 この記事では検証の概要(Multi-AS、Multi-vendor、SR で VPN と TE を実現)について述べます。 はじめに イノベーションセンターの田島です。主にサービスプロバイダー網の技術検証から検証用 AS の設計・構築・運用まで担当しています。 我々が検証に取り組む Multi-AS での Segment Routing 活用について、今回から複数回にわたって解説します。 記事一覧 概要説明(この記事) #1 SR-MPLS L3VPN in Single-AS #2 SR-MPLS L3VPN in Multi-AS #3 SR-MPLS EVPN in Single/Multi-AS #4 Color-Based Steering in Single-AS #5 TE in Multi-AS #6 IGP Flexible Algorithm #7 Delay-based TE #8 SR Policy の適用方法と活用 (Per-Flow Steering) #9 TI-LFA を用いた障害時の高速迂回 #10 PCE を用いた SR-TE の一元管理 #11 PCE 実装の検証 #12 SR-MPLS L3VPN in Multi-AS using IOS XR, Junos and SR OS #13 SR-MPLS L2VPN(EVPN) in Multi-AS using IOS XR, Junos and SR OS #14 Color-Based Steering using IOS XR, Junos and SR OS #15 IGP Flexible Algorithm Interoperability Updates #16 Delay Measurement using IOS XR, Junos and SR OS #17 Per-Flow Steering using SR OS #18 TI-LFA を用いた障害時の高速迂回 と Microloop の回避 (using SR OS with IOS XR / Junos) #19 SR OS での PCE を用いた LSP Provisioning #20 Multi-AS の SR-MPLS + VPNv4 環境における AS 間での TE ※記事が公開され次第、リンクを追加します。 検証の背景と記事のねらい 我々サービスプロバイダーが拠点間通信の需要に応えるときは、専用の機器や回線を用意する場合ばかりではなく、共用の IP バックボーンを用意したうえで VPN などのサービスを実装し提供する場合もあります。 この取り組みは、その IP バックボーンと VPN を構築する技術の話です。 IP バックボーンは 単一の IGP で作られることが多いですが、中には単一の IGP で管理するのが難しいネットワークもあります。 例えば単純にノード数が多かったり、構築経緯の異なるネットワーク同士を相互接続している、などの理由が考えられます。 我々の取り組みはそういった場合においても、次のような利点をいかにして実現するのか実証するものです。 ネットワークを分割しそれぞれの独立性を保ちつつも同じ end-to-end の通信を提供する 単に通信できる以上の付加価値を付与する この検証では AS による IGP 分割でネットワーク同士の疎結合を保ちつつ、それぞれの AS で Segment Routing(SR)を用いて end-to-end の通信をより詳細にコントロールする方式を実際に試しています。 SR は比較的新しい分野で情報が限定的になりがちなため、実証した内容をこうして記事として執筆することで情報提供と SR のさらなる発展を期待しています。 我々の記事が SR の VPN と TE の技術に興味を持つきっかけとなり、技術理解や各キャリアルーターでの実装の一助となれば幸いです。 お約束ごとではありますが、一連の内容は NTT Com の現在および未来にわたるネットワークサービスの仕様を定めるものではありません。 また記載内容は我々の検証環境で実際に行った結果を基にしており、再現性のある記述に努めますが、言及する各ルーターメーカーの実装内容や動作を保証するものではありません。 検証の概要 まずネットワークサービスの模式図を例示しネットワークの概念と用語を説明します。 サービスプロバイダー側が用意する中央のネットワークに、顧客のルーター Customer Edge(CE)が接続されます。 CE を収容するプロバイダー側のルーターを Provider Edge(PE)といい、特にパケットが CE から PE に入る方を ingress PE とよび、出る方を egress PE とよびます。 図に示すように実際のルーターとリンクで構成されるアンダーレイ(Underlay)の上に ingress PE から egress PE までの仮想的なオーバーレイ(Overlay)経路があるというのが VPN の実装です。この経路を制御することを Traffic Engineering(TE)とよびます。 台数が増えるなどにより、アンダーレイを分割した場合を考えます。図には多くのルーターを例示できませんが次の図のように変わります。 検証では、このような状態でもオーバーレイの機能を維持するためには何が必要なのかを考えます。 オーバーレイの機能とは複数のアンダーレイ AS を経由した VPN の実現と、そのような VPN 経路の TE の実現です。 その際に各アンダーレイの実装自由度を確保するため、構成する機器のメーカー制約は導入しません。 つまり、異機種間の相互接続ということになります。 まとめますと、これから紹介する我々の取り組みは IGP ドメインが分割されていて Multi-vendor で構成されるアンダーレイの上に SR を用いて VPN を実装し TE を実装する 取り組みです。 おわりに 本記事では Multi-AS SR の構築を目指す我々の取り組みの概要と、今後の記事の目次を記載しました。現在リストにある項目以外にも執筆予定ですのでご期待ください。 参考資料 この検証の一部は MPLS JAPAN 2021 にて発表しました。 発表資料がイベントページにありますので、併せてご覧ください。
はじめに こんにちは。プラットフォームサービス本部 データプラットフォームサービス部でSmart Data Platform(SDPF)のサービス企画を行っている小野・砂田です。 ご覧頂きありがとうございます。我々は当社が提供しているSDPFサービスを組み合わせた具体的な事例紹介をしております。 前回の 第3回 では、Flexible Remote Accessを利用しながら、合わせて適切なログ保存を行って運用していくユースケースを説明させて頂きました。 今回は、現在も利用拡大が続いているIoT関連サービスを使った事例をご紹介いたします。 IoTが組み込まれた環境は当たり前になってきており、IoTシステムの効率的な環境構築・運用が求められています。以下を目標としながら、お客様が気軽に利用できる環境を作成してみました。 <実現したいこと> モバイルデータ通信サービスを使って、データの収集や各種機器の管理をしたい。 IoTのセンサーデバイスや各種機器から収集したデータを、クラウド側へセキュアに転送したい。 多数のデバイス・機器の状態を統合的に可視化、分析、管理したい。 上記を実現する各種サービスの構築を素早く、かつ簡易に行いたい。 背景 増加するIoTデバイスを簡易に組み込んで管理することが必要 お客様の課題解決のために、IoTユースケースでは以下の機能が求められています。 IoTデバイスにeSIMを組み込み、モバイルデータ通信サービスを利用してクラウドへ簡単にセキュアに接続する機能 IoTデバイスの状態の可視化、分析、管理などを統合的に行う機能 上記を以下の3つのサービスを利用して実現します。 IoT Connect Mobile® Type S : eSIM採用のモバイルデータ通信サービス IoT Connect Gateway : IoTのセンサーデバイスなどから収集したデータの転送を簡単・セキュアに実現 Things Cloud® : 多様なセンサー/デバイス接続からのデータ収集、可視化、分析、管理などのIoTに必要な機能をパッケージ化 機能実現するには複数のサービスを組合せて構築することが必要 3つのサービスを申し込んだ後、IoT機能を実現するには複数の手順を実施する必要があります。今回の取り組みでは、速やかに機能実現するために手順をなるべく効率化し、簡易に機能実現できる仕組み作りを行いました。 IaCツールを活用して、各種IoTサービスの設定を自動構築する TerraformやAnsible、各サービスのAPIを活用して自動構築に必要な設定ファイル、コードを作成する 上記の実現のため、以下のサービスやツールを利用しています。 Azure DevOps : Microsoft Azureサービスの1つで、開発を計画的かつ効率的に行うためのツール群を提供 Terraform : HashiCorp社が開発する、インフラ環境をコードとして管理・自動構築できるツール Ansible : レッドハット社により開発されている、構成管理やOS・ソフトウェアの設定を自動化するツール Azure CLI : Azureリソースを作成・管理するためのコマンドセット 検証を通じた確認 実際に検証を行った構成 上記背景を基に、IoT機能実現の効率化を図る環境として以下2つの構成について検証しました。 なお、検証ではIoTデバイスとしてRaspberry Piを利用しました。 構成(IoTデバイス→IoT Connect Gateway→Things Cloud) 室内の温湿度データをセンサで計測し、IoTデバイスでデータを受け取ります。 NTT CommunicationsのモバイルサービスであるIoT Connect Mobile® Type Sのモバイルネットワークを利用し、IoTデバイスからIoT Connect Gatewayに温湿度データを送信します。 IoT Connect Gatewayのプロトコル変換機能を利用してMQTTからMQTTSにプロトコルを変換し、Things Cloudに暗号化された温湿度データを転送します。 Things Cloudのデータ表示機能を活用し、温湿度データをグラフに表示します。 構成(IoTデバイス→IoT Connect Gateway→Azure IoT Hub) 室内の温湿度データをセンサで計測し、IoTデバイスでデータを受け取ります。 NTT CommunicationsのモバイルサービスであるIoT Connect Mobile® Type Sのモバイルネットワークを利用し、IoTデバイスからIoT Connect Gatewayに温湿度データを送信します。 IoT Connect Gatewayのプロトコル変換機能を利用してMQTTからMQTTSにプロトコルを変換し、Azure IoT Hubに暗号化された温湿度データを転送します。 実際に検証してみた Azure DevOpsを活用したThings Cloudの環境構築と検証 Azure DevOpsを用いてIoT Connect GatewayとThings Cloudの設定を自動化した上で、温湿度センサで取得したデータをIoTデバイスからIoT Connect Gateway経由でThings Cloudにデータ転送し、そのデータをThings Cloud上で可視化する検証を実施しました。 Azure DevOpsを活用したAzure IoT Hubの環境構築と検証 Azure DevOpsを用いてIoT Connect GatewayとAzure IoT Hubの設定を自動化した上で、温湿度センサで取得したデータをIoTデバイスからIoT Connect Gateway経由でAzure IoT Hubにデータを転送する検証を実施しました。 確認後の具体的な気付きポイント IoT Connect Gateway 今回の構成ではIoTデバイスからIoT Connect Gateway経由で各種クラウドサービスにデータの転送をしましたが、その間の通信プロトコル変換(MQTT→MQTTS)をIoT Connect Gatewayの設定のみで簡単に行うことができました。IoTデバイス側でSSL設定をせずに、プロトコル変換によりSSL通信を実現できる点において有用性が高いと感じました。また、プロトコル変換によりIoTデバイス側のSSL通信分のトラフィックが削減されるため、通信量やIoTデバイスの負荷軽減にもなると考えております。 Things Cloud 今回の検証でThings Cloudを用いて温湿度データの可視化を実現しましたが、他にも受け取ったデータで異常値が発生した場合、任意のメールアドレスに自動的にアラートメールを送信することも可能です。 また、本記事では温湿度センサを用いた環境について紹介しておりますが、他にもGPSセンサから取得したデータをThings Cloudに転送することで、Things CloudのマップからIoTデバイスの位置情報の確認もできます。以下の図はIoTデバイスの位置情報をThings Cloudで確認している画面です。 具体的な構成例や設定例の見える化 具体的な構成例や設定例、注意点等の知見についてはKnowledge Centerにて記載しておりますのでご確認ください。記載している情報は2022年5月時点の情報となります。 具体的な構成例・設定例 IoT機器導入ソリューション【手動構築】 IoT機器導入ソリューション【自動構築】 各種サービス IoT Connect Mobile® Type S IoT Connect Gateway Things Cloud® Hybrid Cloud with Microsoft Azure Azure DevOps 最後に 今回の検証ではIoTデバイスをクラウドサービスへ登録する作業や、利用するクラウドサービスの設定を自動化しました。今後は、IoTデバイスの追加・変更・削除等の運用を想定した作業を自動化する検証を進めていきます。 「ここの記載がわかりにくい」等のご意見や、「SDPFでこんなことができないか」等の疑問がありましたら、以下メールアドレスに対してお気軽にコメント等ご連絡ください。 sdpf-testbed-02@ntt.comにメールを送る ※お手数ですが@を全角文字から半角文字に置き換えてください。
はじめに イノベーションセンターの神田です。 みなさんはVisionalistというサービスをご存じでしょうか。 VisionalistはNTTコミュニケーションズのグループ会社であるNTTコム オンライン・マーケティング・ソリューション(以下 NTTコム オンライン)が提供していたアクセスログ解析サービスです。 2020年7月にサービスを終了しましたが、2022年5月にこのサービスで利用していたドメインを第三者が再登録し、セキュリティ上問題があるスクリプトを設置している可能性が確認されました(以下 本件)。 本件について、NTTコム オンラインは 広く注意を呼びかけています 。 本件に対して、イノベーションセンターでは事象が確認された直後からそのリスクの有無や程度について詳細な調査を実施してきました *1 。 本記事は、本件におけるリスクがこれ以上拡大しないようにこれまでに我々が確認した事実とそれに基づく分析の結果を社外に広く共有するものです。 また、今後同様の事象が起きないように本件を教訓としてドメインのライフサイクル管理を今一度見直していただくきっかけとなることも期待しています。 社内に向けても先日開催された勉強会において情報共有・啓発しており、本記事はその発表内容をベースとして再編集しています。 目次 アクセスログ解析サービス(Visionalist)の仕組み 経緯 脅威分析 関連事例 影響範囲 おわりに 謝辞 IoC アクセスログ解析サービス(Visionalist)の仕組み アクセスログ解析サービスに詳しくない方のために、Visionalistがどのような仕組みでアクセスを計測していたかを簡単に説明します。 (すでにご存じの方は読み飛ばしてください) Visionalistでは、サービス利用者が予め自分のWebサイトにタグ(tracer[.]jpへのリンク)を埋め込んでおきます。 訪問者がWebサイトにアクセスすると(上図①)、タグを介して計測用スクリプトがWebブラウザにダウンロードされ(上図②)、Webブラウザ上で計測用スクリプトが実行されることでログが生成、収集されます(上図③)。 図のようなアクセス解析サービスの形態は「ビーコン型」と呼ばれます。 Visionalistに限らずビーコン型のアクセス解析サービスは基本的に同様の仕組みでアクセスを計測しています。 経緯 発生日 イベント 2020年7月31日 Visionalistのサービス終了 2022年4月30日 tracer[.]jpが完全に失効 2022年5月5日 第三者がtracer[.]jpを再登録 〜2022年5月17日 不審なスクリプト配置 2022年5月18日 NTTコム オンラインによる注意喚起 Visionalistでは、アクセスログ解析に用いるタグとしてtracer[.]jpを利用していました。 このドメインはサービス終了後、2022年4月30日に期限を迎え、先願による登録が可能な状態となりました。 2022年5月5日になってNTTコム オンラインではない第三者がtracer[.]jpを再度登録しました。 その後、不審なスクリプトが配置されていることが発見され、報告されています。 (ご無沙汰しております、、、) 2020年にサービス提供を終えたVisionalist ASPで使用されていたドメイン名 ( tracer[.]jp ) が数日前に第三者に再登録され、不審なスクリプトが配置されています。 タグの消し忘れが結構ありそうです。 ご注意ください! pic.twitter.com/QWMd6SI6PD — tike (@tiketiketikeke) 2022年5月10日 tracer[.]jpに不審なスクリプトが配置されたということは、タグが残置されたままのWebサイトにアクセスしたWebブラウザ上で不審なスクリプトが実行される状態にあることを意味します。 不審なスクリプトが配置された状況はしばらく続きましたが、2022年5月17日に権威DNSサーバが切り替わったタイミングでDNSレコードが削除され、tracer[.]jpは名前解決のできない、どこにもつながらない状態となりました。 以後、2022年6月7日現在までその状況が続いています。 2022年5月18日にはNTTコム オンラインが注意を喚起する ニュースリリース を公開しました。 脅威分析 事象を認識した2022年5月10日頃から2022年5月17日にDNSレコードが削除されるまでの間tracer[.]jpに不審なスクリプトが配置されていた事実を確認しています。 スクリプトが実行されると、2種類のハッシュ値やタイムスタンプ値などがWebブラウザからtracer[.]jpに送信されます。 スクリプト上ではその後tracer[.]jpからの応答に応じてWebブラウザのページを別URLに遷移させるようなコードとなっています。 具体的には、JSON形式の応答を受け取った場合に応答データ内の情報を元に parent.top.window.location.href の値を書き換える動作をします。 観測の範囲内ではJSON形式の応答は確認されませんでしたが、応答によって任意のURLに遷移させるリダイレクタとして動作することから、潜在的に悪意のあるURLに誘導される可能性が考えられます。 不審なスクリプトが配置されていたインフラではtracer[.]jpの他に少なくとも数百のドメインが紐づけられていることを確認しています。 これらのドメインの中には2022年5月18日以降も引き続き名前解決できる状態のものがあり、tracer[.]jpで確認されたスクリプトと同一のスクリプトが配置されているドメインも複数存在しています。 ドメインに紐づくIPアドレスの追加も行われていることから、当該スクリプトが配置されているインフラは依然として稼働状態にあると考えられます。 関連事例 本件と関連する事例が過去にセキュリティベンダから報告されています。 2021年にESETは中東をターゲットとした水飲み場攻撃 *2 を報告しています *3 。 この事例で水飲み場攻撃としてWebサイトに埋め込まれたリンクの先に配置されていたスクリプトの1つはtracer[.]jpで確認されたスクリプトと同一のものでした。 当該スクリプトが配置されていたインフラも共通していることから、高い確度で同一のグループであると考えられます。 2020年にはPalo Alto NetworksがCOVID-19関連ドメインについて報告しています *4 。 この事例で複数のコロナウイルス関連ドメインが多数ホスティングされていた不審なパーキングページ *5 に関連するスクリプトとして、tracer[.]jpで確認されたスクリプトと類似のスクリプトが報告されています。 確度はあまり高くありませんが、関数名やリダイレクトロジックの共通性から、何らか関係している可能性が考えられます。 影響範囲 2022年6月7日現在、調査した範囲ではおよそ800のWebサイトにtracer[.]jpへのリンクが残存している可能性があります。 現時点ではtracer[.]jpはどこにもつながっていないため、当該Webサイトにアクセスしても不審なスクリプトが実行されることはありません。 しかし、再び不審なスクリプトが配置された際には、当該WebサイトにアクセスしたWebブラウザ上で任意のスクリプトが実行されうる状態であることに変わりはありません。 おわりに 本記事では、Visionalistで利用していたタグドメインtracer[.]jpが第三者によって再登録された件について確認できている事実とそれに基づく分析の結果を共有しました。 今回の事例がドメインの取得やライフサイクル管理を改めて考えるきっかけとなれば幸いです。 また、tracer[.]jpタグが残置されているWebサイトの管理者さまにおかれましては、リスク回避のためにも当該タグをサイトより削除していただけますよう改めてお願いします。 謝辞 本件を代表するような立場ではありませんが、まがりなりにもセキュリティに携わる一人の人間として、本件について警鐘を鳴らし、各方面に注意を呼びかけてくださった皆さまに感謝をお伝えしたいと思います。 特に、本件を発見し初めに広く注意を呼びかけてくださったtikeさん( @tiketiketikeke )、各方面に積極的に働きかけてくださった坂本一仁さん( @_taka_sakamoto )に心より感謝します。 ありがとうございました。 IoC *.tracer[.]jp 45[.]77.192.33 68[.]183.47.155 165[.]232.142.149 206[.]81.5.96 *1 : 調査結果については社内関連部署にも報告し、対応に活用してもらっています。 *2 : ターゲットが日頃アクセスしているWebサイトを改ざんし不正なコードを設置することで、ターゲットのWebアクセスをトリガーにして攻撃を仕掛ける手法。ライオンが水飲み場の近くで獲物を待ち伏せて狩る様子になぞらえて名付けられました。 *3 : ESET, " Strategic web compromises in the Middle East with a pinch of Candiru " *4 : Palo Alto Networks, " 新型コロナウイルス感染症につけこむサイバー攻撃者たち:関心の高いドメイン名登録で収益化 " *5 : ドメインパーキングサービスに用いられるWebページ。ドメインパーキングサービスとは使用していないドメインを管理するサービスのことで、当該ドメインへのアクセスを「現在ドメインが使用されていない」旨を伝えるページに誘導したり、広告を掲載したページに誘導したりします。
はじめに イノベーションセンターの神田です。 みなさんはVisionalistというサービスをご存じでしょうか。 VisionalistはNTTコミュニケーションズのグループ会社であるNTTコム オンライン・マーケティング・ソリューション(以下 NTTコム オンライン)が提供していたアクセスログ解析サービスです。 2020年7月にサービスを終了しましたが、2022年5月にこのサービスで利用していたドメインを第三者が再登録し、セキュリティ上問題があるスクリプトを設置している可能性が確認されました(以下 本件)。 本件について、NTTコム オンラインは 広く注意を呼びかけています 。 本件に対して、イノベーションセンターでは事象が確認された直後からそのリスクの有無や程度について詳細な調査を実施してきました *1 。 本記事は、本件におけるリスクがこれ以上拡大しないようにこれまでに我々が確認した事実とそれに基づく分析の結果を社外に広く共有するものです。 また、今後同様の事象が起きないように本件を教訓としてドメインのライフサイクル管理を今一度見直していただくきっかけとなることも期待しています。 社内に向けても先日開催された勉強会において情報共有・啓発しており、本記事はその発表内容をベースとして再編集しています。 目次 アクセスログ解析サービス(Visionalist)の仕組み 経緯 脅威分析 関連事例 影響範囲 おわりに 謝辞 IoC アクセスログ解析サービス(Visionalist)の仕組み アクセスログ解析サービスに詳しくない方のために、Visionalistがどのような仕組みでアクセスを計測していたかを簡単に説明します。 (すでにご存じの方は読み飛ばしてください) Visionalistでは、サービス利用者が予め自分のWebサイトにタグ(tracer[.]jpへのリンク)を埋め込んでおきます。 訪問者がWebサイトにアクセスすると(上図①)、タグを介して計測用スクリプトがWebブラウザにダウンロードされ(上図②)、Webブラウザ上で計測用スクリプトが実行されることでログが生成、収集されます(上図③)。 図のようなアクセス解析サービスの形態は「ビーコン型」と呼ばれます。 Visionalistに限らずビーコン型のアクセス解析サービスは基本的に同様の仕組みでアクセスを計測しています。 経緯 発生日 イベント 2020年7月31日 Visionalistのサービス終了 2022年4月30日 tracer[.]jpが完全に失効 2022年5月5日 第三者がtracer[.]jpを再登録 〜2022年5月17日 不審なスクリプト配置 2022年5月18日 NTTコム オンラインによる注意喚起 Visionalistでは、アクセスログ解析に用いるタグとしてtracer[.]jpを利用していました。 このドメインはサービス終了後、2022年4月30日に期限を迎え、先願による登録が可能な状態となりました。 2022年5月5日になってNTTコム オンラインではない第三者がtracer[.]jpを再度登録しました。 その後、不審なスクリプトが配置されていることが発見され、報告されています。 (ご無沙汰しております、、、) 2020年にサービス提供を終えたVisionalist ASPで使用されていたドメイン名 ( tracer[.]jp ) が数日前に第三者に再登録され、不審なスクリプトが配置されています。 タグの消し忘れが結構ありそうです。 ご注意ください! pic.twitter.com/QWMd6SI6PD — tike (@tiketiketikeke) 2022年5月10日 tracer[.]jpに不審なスクリプトが配置されたということは、タグが残置されたままのWebサイトにアクセスしたWebブラウザ上で不審なスクリプトが実行される状態にあることを意味します。 不審なスクリプトが配置された状況はしばらく続きましたが、2022年5月17日に権威DNSサーバが切り替わったタイミングでDNSレコードが削除され、tracer[.]jpは名前解決のできない、どこにもつながらない状態となりました。 以後、2022年6月7日現在までその状況が続いています。 2022年5月18日にはNTTコム オンラインが注意を喚起する ニュースリリース を公開しました。 脅威分析 事象を認識した2022年5月10日頃から2022年5月17日にDNSレコードが削除されるまでの間tracer[.]jpに不審なスクリプトが配置されていた事実を確認しています。 スクリプトが実行されると、2種類のハッシュ値やタイムスタンプ値などがWebブラウザからtracer[.]jpに送信されます。 スクリプト上ではその後tracer[.]jpからの応答に応じてWebブラウザのページを別URLに遷移させるようなコードとなっています。 具体的には、JSON形式の応答を受け取った場合に応答データ内の情報を元に parent.top.window.location.href の値を書き換える動作をします。 観測の範囲内ではJSON形式の応答は確認されませんでしたが、応答によって任意のURLに遷移させるリダイレクタとして動作することから、潜在的に悪意のあるURLに誘導される可能性が考えられます。 不審なスクリプトが配置されていたインフラではtracer[.]jpの他に少なくとも数百のドメインが紐づけられていることを確認しています。 これらのドメインの中には2022年5月18日以降も引き続き名前解決できる状態のものがあり、tracer[.]jpで確認されたスクリプトと同一のスクリプトが配置されているドメインも複数存在しています。 ドメインに紐づくIPアドレスの追加も行われていることから、当該スクリプトが配置されているインフラは依然として稼働状態にあると考えられます。 関連事例 本件と関連する事例が過去にセキュリティベンダから報告されています。 2021年にESETは中東をターゲットとした水飲み場攻撃 *2 を報告しています *3 。 この事例で水飲み場攻撃としてWebサイトに埋め込まれたリンクの先に配置されていたスクリプトの1つはtracer[.]jpで確認されたスクリプトと同一のものでした。 当該スクリプトが配置されていたインフラも共通していることから、高い確度で同一のグループであると考えられます。 2020年にはPalo Alto NetworksがCOVID-19関連ドメインについて報告しています *4 。 この事例で複数のコロナウイルス関連ドメインが多数ホスティングされていた不審なパーキングページ *5 に関連するスクリプトとして、tracer[.]jpで確認されたスクリプトと類似のスクリプトが報告されています。 確度はあまり高くありませんが、関数名やリダイレクトロジックの共通性から、何らか関係している可能性が考えられます。 影響範囲 2022年6月7日現在、調査した範囲ではおよそ800のWebサイトにtracer[.]jpへのリンクが残存している可能性があります。 現時点ではtracer[.]jpはどこにもつながっていないため、当該Webサイトにアクセスしても不審なスクリプトが実行されることはありません。 しかし、再び不審なスクリプトが配置された際には、当該WebサイトにアクセスしたWebブラウザ上で任意のスクリプトが実行されうる状態であることに変わりはありません。 おわりに 本記事では、Visionalistで利用していたタグドメインtracer[.]jpが第三者によって再登録された件について確認できている事実とそれに基づく分析の結果を共有しました。 今回の事例がドメインの取得やライフサイクル管理を改めて考えるきっかけとなれば幸いです。 また、tracer[.]jpタグが残置されているWebサイトの管理者さまにおかれましては、リスク回避のためにも当該タグをサイトより削除していただけますよう改めてお願いします。 謝辞 本件を代表するような立場ではありませんが、まがりなりにもセキュリティに携わる一人の人間として、本件について警鐘を鳴らし、各方面に注意を呼びかけてくださった皆さまに感謝をお伝えしたいと思います。 特に、本件を発見し初めに広く注意を呼びかけてくださったtikeさん( @tiketiketikeke )、各方面に積極的に働きかけてくださった坂本一仁さん( @_taka_sakamoto )に心より感謝します。 ありがとうございました。 IoC *.tracer[.]jp 45[.]77.192.33 68[.]183.47.155 165[.]232.142.149 206[.]81.5.96 *1 : 調査結果については社内関連部署にも報告し、対応に活用してもらっています。 *2 : ターゲットが日頃アクセスしているWebサイトを改ざんし不正なコードを設置することで、ターゲットのWebアクセスをトリガーにして攻撃を仕掛ける手法。ライオンが水飲み場の近くで獲物を待ち伏せて狩る様子になぞらえて名付けられました。 *3 : ESET, " Strategic web compromises in the Middle East with a pinch of Candiru " *4 : Palo Alto Networks, " 新型コロナウイルス感染症につけこむサイバー攻撃者たち:関心の高いドメイン名登録で収益化 " *5 : ドメインパーキングサービスに用いられるWebページ。ドメインパーキングサービスとは使用していないドメインを管理するサービスのことで、当該ドメインへのアクセスを「現在ドメインが使用されていない」旨を伝えるページに誘導したり、広告を掲載したページに誘導したりします。
はじめに スタンフォード大学の John Ousterhout 教授が執筆された “ A Philosophy of Software Design ”(以下 APoSD と略す) という書籍をご存じでしょうか? 書籍のタイトルを直訳すると、「ソフトウェア設計の哲学」となります。書籍の内容はまさに、ソフトウェア設計について扱っています。 本書籍をベースに、「A Philosophy of Software Design を30分でざっと理解する」というお題で社内ランチ勉強会が開催されました。本記事執筆者である岩瀬( @iwashi86 )が発表者であり、勉強会資料は以下のとおりです。 スライド P.4 に記載したとおり、本書籍は John Ousterhout 教授の意見が強く反映されており、ソフトウェアエンジニアであれば、議論を呼ぶ箇所があります。実際、勉強会の実況Slackでは、「これはどうなんだろう?」といった疑問があがっていました。 本記事では勉強会であがっていた疑問について、 twada 顧問と対話した内容を紹介 1 していきます。読者の皆様のソフトウェア設計に関する知見の拡大が記事のゴールです。 なお、事前の免責が1点あります。対話の起点は APoSD ですが途中からソフトウェア開発全般のトピックに派生していきます。「お、APoSD から外れて、ソフトウェア設計全般のトピックに派生していったな」という気持ちで読んでいただけるとありがたいです。 さて以降は、大きく2部構成となります。前半では、 APoSD での主張概要を紹介します。後半を読むためのベースラインを合わせるのが狙いです。SpeakerDeckの資料をお読みの場合は飛ばしていただいて構いません。後半は、本題となるtwada 顧問との議論パートです。早速いってみましょう! APoSD の概要 APoSD の主題は「複雑性」です。この複雑性はソフトウェアの構造に関するものであり、システムの理解や修正を難しくするものです。システムの理解や修正が容易であれば、そのシステムは「複雑ではない」ということになります。仮にどんなに大規模なシステムだったとしても、理解や修正が容易であれば、APoSDの定義では、複雑ではないシステムになります。 複雑性が増大すると、以下の3つが起こります。 Change Amplification (変更の増大) Cognitive Load (認知的負荷) Unknown Unknowns (未知の未知) たとえば、一見単純に見えても変更箇所が多い場合が 「変更の増大」 に該当します。また、覚えるべきAPIや、気にしないといけない変数が多い、といった場合は「認知的負荷」の増大につながります。さらに、あるタスクを完了させたいとして、何を変更すればいいかわからない状況が未知の未知となります。APoSDでは、未知の未知が3つの中で最悪と述べられています。なぜならば、何らかの変更を加えても、バグが出るまで発見できないためです。 このような複雑性はどこから生まれてきてしまうのでしょうか?APoSDではその要因として、 Dependency (依存性) Obscurity (不明瞭性) の2点が挙げられています。たとえば、何らかのモジュールを操作する場合に、1つのモジュールで完結できない場合は、依存性がある状態になります。また、コードを読み解かないとわからない場合は不明瞭性がある状態となります。一例として、 “time” という変数があった場合、単位などの補足がなければどのように理解していいかわかりません。 この複雑性に、ソフトウェアエンジニアはどのように立ち向かえばいいのでしょうか? APoSDでは、その方法として以下2点が挙げられています。 複雑性の排除(たとえば、特別なケースを排除する) 複雑性の隠蔽(たとえば、難解な部分が見えなくても使えるようにカプセル化する) 書籍では、この1と2を中心に、全22章でより具体的に説明されていきます。詳細は原著に譲るとして、ここから一部のトピックについて当社技術顧問のtwadaさんと議論していきます。 技術顧問とAPoSDについて話してみた 小クラス主義 と 大クラス主義 APoSD では4章の中で、以下のソースコードが記載されています。 FileInputStream fileStream = new FileInputStream(fileName); BufferedInputStream bufferedStream = new BufferedInputStream(fileStream); ObjectInputStream objectStream = new ObjectInputStream(bufferedStream); やりたいのは、ファイルを開いてシリアライズ化されたオブジェクトを読み込むことです。クラス設計としては、 Gang of Four デザインパターンでいう、Decorator パターンが活用されています。結果として、小さいクラスを組み合わせて実装を進めるコードになっています。 本件を勉強会で話していた際に、Slackでtwada顧問からコメントが上がっています。 これについて、以下で深堀りしていきます。 iwashi: 小クラス主義ってことは大クラス主義があります? twada: はい、小クラス主義と大クラス主義があります。私のこれまでの言語キャリアから考えると、小クラス主義の代表はJavaで、大クラス主義の代表はRubyです。 小クラス主義の例としては、Javaの先ほどの File I/O がわかりやすいですね。大クラス主義におけるRubyの例としては、 Array とか String のクラスに多くの機能や責務がありますね。少ないクラスが、それぞれ多くの機能を持つ。必然的にクラスが大きくなります。 iwashi: この辺りは、APoSDの主張とどう関連がありますか? twada: APoSDの著者が述べているのは、Deep Moduleが良いということですよね。Deep Module(深いモジュール)とは、インターフェースが狭くて実装が深いことです。反対に、Shallow Module(浅いモジュール)はインタフェースが広いわりに、実装が浅いことを意味します。 これ自体は小クラス主義とも、大クラス主義ともちょっと違います。ただ、小クラス主義は必然的にShallowなModuleに近づきがちです。 iwashi: Shallow ModuleのようにIFが広いのを避けたいのは、認知負荷の増大でしょうか? twada: そうです。よく似た機能がいくつもあって、その中から選ばないといけない、利用者としては認知的負荷が高い。 よく似ていて、ちょっとずつ機能が違うクラスがたくさんあり、その中から選んで使ってください、みたいなパターンはありますよね。あるいは、よく似ていて、ちょっとずつ違うメソッドがあるというパターン。これも認知負荷が高いですよね。 Javaの設計思想 iwashi: 小クラス主義にも一定のメリットがあるのかと思います。たとえば、Javaのもともとの設計思想だと何を意識されていたのでしょうか? twada: これから述べる課題は最近のJavaでは解決されていますが、歴史からお話します。 もともとのJavaの設計の背景にあったのは、すべての状況を破綻なく扱える設計を提供しようという価値観です。 たとえば巨大なファイルを開こうとすると、out of memoryになり得ますよね。だから、たとえば、ArrayとかListにファイルの全行を簡単に読み込むのは、意図的にできないようにしていました。それよりは、どのような場合でも適切に動く汎用的な組み合わせを提供しています。たとえば、InputStreamを開いて、InputStreamReaderでCharレベルに変換して、BufferedReaderでバッファリングするという例のコードになります。 「これは理屈は分かるんだが、めんどくさいよね」「正しいけど、面倒だよね」という話になるんです。ちなみにこの問題はJavaの進化と共にだんだん解決されていき、現在では単純な問題は短く解決できるようになりました。この正しさと面倒くささの問題に対してRubyも大クラス主義の観点からアプローチしています。 Rubyでの設計例 - utc_offset Rubyの標準ライブラリの設計者はMatzさんを中心に何人かいます。その中の1人の田中 哲(akr)さんは、正しさと使いやすさの関係をとても敏感に考えています。正しいやり方が簡単でないと、みんな正しいやり方を使ってくれない。だから、正しいやり方がもっとも簡単であるべきというAPI設計をしたんです。 簡単だけど若干間違っているコードと、正しいけどすごく面倒なコードがあるときに、人は簡単だけど若干まちがっているコードに引き寄せられがちです。その典型例は、UTCからの時差の求め方。時刻処理は正確にやろうとするとかなり面倒な部分があります。具体的には、うるう秒があると一秒ずれるとか。この部分を正しく実装しようとすると、2行だったものが8行ぐらいになるわけです。すると、誤差があっても簡単な方に開発者は寄っていきます。 そんなとき、田中さんはRubyのTimeクラスに utc_offset を定義して、正しいけど面倒なやり方を1回のメソッド呼び出しで済むように設計しました。Timeクラスのutc_offsetメソッドを呼び出すほうが明らかに短くてかつ正しいやり方というわけです。「一番正しいやり方が、一番短くて簡単であるべき」という考え方によって正しい方向に利用者を誘導する設計をしたわけです。この辺りの詳細は『 APIデザインケーススタディ ――Rubyの実例から学ぶ。問題に即したデザインと普遍の考え方 』に載っています。 良いインタフェースとは? 実は同じ主旨の内容が、 プログラマが知るべき97のことの1つ にも載っています。 良いインタフェースとは次の2つの条件を満たすインタフェースのことです。 正しく使用する方が操作ミスをするより簡単 誤った使い方をすることが困難 このような考え方に照らし合わせると、小クラス主義で、利用者に適切な組み合わせ方や責務の選び方を委ねるのもなかなか難しいな、という話になります。もちろん、言うは易く行うは難し、という話でもあります。 iwashi: なるほど、たしかに設計のもっとも難しいポイントですね。 1つのことを実現するために1つの正しいやり方がある、というのはシンプルでとても素晴らしいと思うのですが、現実として、1つのことを実現するために複数のやり方がある例って、これまでどのようなものがあったのでしょうか? twada: 古典である『 プログラミング作法 』から1つ紹介します。この中では、Cの標準ライブラリが紹介されていますね。たとえば、アウトプットストリームに1文字書きこむ場合に、putc、fputc、fprintf、fwriteがありますね。 iwashi: たしかに利用者側は混乱しますね。ライブラリを作るなり、ビジネスロジックを作る場合には、そういう設計を避けるのが大事ということですね。 twada: そうです。たとえば、『プログラミング作法』から引用すると 少なくとも、どうしても関数を増やさなければならない明確な根拠が 生まれるまでは、広いインターフェイスよりも狭いインターフェイスのほうが 望ましい。1つのことだけ実行し、それをうまく実行すること。 可能だからというだけでインターフェイスに追加してはならないし、問題があるのは実装のほうなのにインターフェイスに手直ししたりしないこと。 たとえば、速度面で有利なmemcpyと安全面で有利なmemmoveがあるよりも、常に安全に利用でき、できれば高速に動作する関数が1種類存在する方がいい。 とあります。 iwashi: なるほど。 似た機能でちょっと違うものの実装ってどうする? iwashi: 少し話を戻して、「似た機能でちょっと動作が違う場合」というのは現実の開発現場でよく出会う例かと思います。これって、どうすればいいんでしょうか? twada: たとえば、動作をオプションで変えるかどうか等で悩むことになりますよね。APoSDでも言及されています。 この点は、私だったらリファクタリングをしていきます。 iwashi: リファクタリングの方向性は? twada: 基本戦略としては、よく似ていてちょっとずつ違う箇所があるときに、ちょっとずつ違う部分をくくりだしていきます。そうすると、完全に一致する部分と、それぞれ違う部分に分かれていきます。完全に一致する部分は外部に抽出して共通化できます。昔は親クラスに抽出して継承による差分プログラミングをしていた時期がありましたが、現代では推奨されません。オブジェクトを組み合わせ、共通部分を委譲していきます。 その際にインタフェースを広げないように、バリエーションを追加するのにポリモーフィズムを使います。ここは、APoSDの著者とは流派が違うかもしれませんね。 iwashi: ポリモーフィズムの箇所の実装イメージをもう少し具体的にお話しいただくとどうなりますか? twada: まず、よく似ていてちょっとずつ違う部分で共通を抽出すると、共通部分の大部分は利用者が直接触らない部分に移動します。共通部分の多くは利用者に露出していないので、設計はある程度自由になります。 次に、新しいバリエーションが増えるときに、それをどう扱うか、という話になります。1つ目のユースケース、2つ目のユースケース、3つ目のユースケースで似たような抽象が見て取れるのであれば、共通部分との界面にインタフェースを導入して、汎用的な実装として組み合わせられるようにします。リファクタリングが結果的にドメインモデリングに近づいていきます。 iwashi: これは共通部分の抽出とは違うってことですか? twada: そうです。具体例を出します。 たとえば、私が受託して開発していたある学内システムがあります。最初はあるレポートを出すという要件が1つあり、機能は一枚岩で作られていました。しばらくして新たな連携先がでてきて、ちょっとずつ要件が違うレポートを出す必要が増えました。 学生向けはその学生だけの情報が出る 教員向けレポートならクラス全員の情報が出る 担当クラスの教員向けには全情報を出すが、担当外の教員が見る場合は、いくつかの情報がマスクされる 今後は連携している他大学の教員もレポートが見えるようになる予定。その際にはさらに情報がマスクされる このようによく似ているけど、ちょっとずつ違う要件が出てくるんですよね。単純にやるならば、if文で条件分岐します。閲覧するユーザも引数で渡すとか。 iwashi: 一般ユーザーと管理者ロールで分けるべきケースとかですよね。 twada: そうです。 新しいロジックが入るたびに、if文が増えることになります。ただ、リファクタリングしていくと、次のような気づきがあります。 たとえば「何を見せる」「見せない」というロジックは、データレベルの認可、データ可視性のロジックになっているわけですよね。リファクタリングしていく中で、完全に同じ部分と違う部分を寄せる・集めていくと、違う部分というのはだいたいにおいてこのようなロジックであることに気づきました。 そうなると、何をやればいいかというと、ユーザーが誰かではなく、レポートの元データの抽出機能とコンテクスト毎に異なるデータのフィルターがあって、2つの組み合わせで動けば要件を満たせる、という話になります。 たとえば、ログインユーザーがその授業の担当教員の場合は全部見えるフィルターを渡します。フィルターの中身は実質的にはNull Objectパターンで良いわけです。学生だったら、担任以外だったら、学外だったら、という形で各々のフィルターインスタンスを作れるようにします。GoFのStrategyパターンですね。それを状況に応じて共通のレポーターに渡せばいいわけです。 (学生の場合は全員検索してからフィルターするよりもそもそも自分のデータだけを抽出する方が効率が良いので、まずは動くもののパフォーマンス改善の余地が大きいバージョンでリリースし、後にデータ抽出部分のフィルタリングも含んだ複合的なインターフェイスになりました) iwashi: Javaで実装するならフィルターの interface を定義することになりますか? twada: Javaでいえばそんな感じですね。 このレポートの場合はいくつかのユースケースがありますが、総じて何を見せるか・見せないかのロジックの差異が多かったので、ロジックの差異を表現したクラスを作って渡していく。そうすることで、レポート機能のコードはほぼ変更無く、新たなフィルタリングが提供できます。たとえば今後は連携している他の大学の教員もレポートが見えるようになり、そのときはマスクする項目がさらに増える予定です。この場合も、他大学の教員用のStrategyクラスを用意すれば良いことになります。 これは、継承に頼ると出てこないレベルの抽象なんです。継承でやるなら、AbstractReporterが出てきて、if文が抽象メソッドになってて穴埋めする実装になるでしょう。具象クラスに穴埋め部分を実装してフィルタリングするわけですね。つまり、親クラスが抽出を行い、サブクラスがフィルタリングをする。この方向性だと、データ抽出とデータレベルの認可が異なることに気づきにくい。 iwashi: これは差分クラスに近い考え方ですね。GoFパターンでいえば、Template Methodパターンに近いですね。 iwashi: Template Methodパターンって、現代だと筋が悪いのでしょうか? twada: 筋がいいのは、現代ではあまりありませんが、処理の順序を示すパターンですね。たとえば、ユニットテストにありますよね。 iwashi: テスト前のsetUp、テスト後のtearDownですね。 twada: そうです。ただそれも、Template Methodパターンが必須というわけではありません。 たとえばObserverパターンでも実装できますよね。現代においては、そもそも継承をあまり使わなくなってきています。 APoSD の内容で継承を考えると? iwashi: なるほど。この辺で、もう一度APoSDに戻りたいのですが、APoSDで述べられていた次の3つの複雑性から招かれる事象と、継承の関連は何なのでしょうか? Change Amplification (変更の増大) Cognitive Load (認知的負荷) Unknown Unknowns (未知の未知) twada: 継承は親クラスとの間に強い依存関係が生まれますよね。継承の使い方を誤ってしまうと、継承階層が深くなります。また処理を読み解くときにサブクラスを読んで、次に親クラスを読んで、親の中身を見たと思ったら、またサブクラスでオーバーライドされていた、みたいな不可解さを持つことがあります。 iwashi: APoSDでは、複雑性の要因は APoSD で次の2つがあがっていましたね。 Dependency (依存性) Obscurity (不明瞭性) まさに、この2つにもつながるわけですね。不明瞭性でいえば、コードの距離が遠くなりますからね。 twada: そういうわけです。というわけで、最近出てきたプログラミング言語では継承という概念がないことも増えてきて、継承ベースのテクニックが推奨されなくなってきたんですね。 おわりに 本記事では、前半でAPoSDの概要を紹介しました。後半では、小クラス主義・大クラス主義、インターフェース設計、リファクタリングによるドメイン抽出といった内容の対話を紹介してきました。 実は、後半の対話内容は全体の内容のうち、半分程度しか記事に起こせていません(半分だけでも非常に多いかも?)。本記事が好評でしたら、後半パートや、続編を検討していきますので、 Twitter などでフィードバックいただけますとありがたいです! 「fukabori.fm の文字起こしじゃないか」と思った方。だいたいあってます。 ↩
はじめに こんにちは、イノベーションセンターの鍔木(GitHub: takuma0121 )です。 今回は OT(Operational Technology)ネットワークのセキュリティリスク可視化サービスである OsecT(オーセクト)をリリースしたので、これまでの取り組みとサービスの特徴についてご紹介します。 OsecTとは OsecT は、OT ネットワークを流れるパケットを収集して、ネットワークの可視化及び脅威・脆弱性を検知するセキュリティリスク可視化サービスです。 主なターゲットは大規模なセキュリティ投資が難しい中堅企業・中小企業で、特徴の1つとして低価格であることが挙げられます。 OT や OsecT の機能については、「制御システムのセキュリティと対策技術 OsecT のご紹介( 前編 ・ 後編 )」で詳しく説明しています。 ご興味がある方はぜひご覧ください。 サービス名は「OT の中心に一本セキュリティ(SECurity)を軸に据える」の意味を込めて命名しました。 ロゴはセキュリティリスクの可視化・検知・対処を3つの箱でイメージして、周りを取り囲むようにセキュリティを守ることを連想させるものにしました。 また、セキュリティの世界ではブルーチーム = 防御側を表現するため、青色をベースにしました。 実証実験の実施 OsecT は「制御システムに影響を与えることなくセキュリティを可視化する技術」として、2021年7月〜2022年2月までの間、 実証実験を実施 していました。 お問い合わせを多数いただき、その中から3社に実験参加していただきました(1社あたり3ヶ月間)。 実証実験の進め方 本章ではこれまでの取り組みとして、実証実験についてご紹介します。 実証実験では、OsecT の機能だけでなく、サービスの導入・利用・廃止までのライフサイクルやユーザーマニュアルなどのお客さまが参照するドキュメントなどあらゆる観点を検証しました。 OsecT はパケットを取得・ログ化するセンサーと、可視化・検知機能を有するクラウドに分かれています。 OT システムの運用担当者である実証実験参加ユーザー(以下、ユーザー)には、センサーとセンサー設置手順書、及び ユーザーマニュアルを送付しました。 センサー到着後、ユーザー自身でセンサーを設置、ユーザーマニュアルを参照して OsecT を利用してもらう、という実サービスで想定している手順通りに実施しました。 実験期間中に最大限のフィードバックを得られるようにするため、利用方法や不具合などの各種問い合わせは Slack を利用して、お問い合わせに迅速に回答する環境を用意しました。 また、実験期間中に計3回のユーザーインタビューを実施しました。 インタビューでは、サービスの導入のしやすさ・利用頻度・ユースケース・Web UI の使いやすさ・価格などをヒアリングしました。 ヒアリングでは、ユーザーインタビューに関する知見がある KOEL に協力してもらい、インタビューから得られるフィードバックを最大化するようにしました。 実証実験を通じて得られたこと 各社3ヶ月間の実験期間の間に、日々のお問い合わせやユーザーインタビューを通じて、想定通りの部分や改善が必要な部分を洗い出すことができました。 導入部分 実証実験では、ユーザーにセンサーとセンサー設置手順書、及びユーザーマニュアルの送付のみで導入から利用開始まで問題が発生しませんでした。 これにより、低価格サービスに向けた導入部分のコスト削減の目処が立ちました。 また、ユーザーインタビューでは想定価格5~10万円であれば OsecT を使いたいというニーズも把握できました。 これにより、サービスリリース後に、価格が導入のネックにならないことを確認できました。 一方で、日々のお問い合わせの中で、可視化機能はユーザーマニュアルや Web UI を見るだけで使いこなせることが確認できましたが、「学習・検知機能はそもそもどのように使えばいいか全くわからない」と言った声をいただきました。 そのため、ユーザーマニュアルの改良に加えて、学習・検知機能を使うためのチュートリアルを提供予定です。 Web UI OsecT は機能が充実している一方で、画面にすべての機能が表示されると何を使っていいのかわからなくなる ≒ ユーザーフレンドリーから遠いデザインであることも明らかになりました。 そのため、KOEL の協力のもとデザインを1から見直しました。 新デザインのポイントは、ユーザーのデイリーユースをインタビューに基づいて想定し、繰り返しアクセスする箇所の使いやすさを重視した点です。 下図はデザイン見直し前後のトップページです。 ダッシュボードの導入・画面左メニューの機能を集約すること・英語表記を日本語表記に変えるなどして、ユーザーが使いやすいデザインとしています。 機能や性能 機能面は必要な機能が揃っているという評価をいただけました。 特に、端末一覧を可視化する機能は手作業で管理している資産台帳との突合による確認や、台帳そのものの更新が滞るという課題解決につながる可能性があると好評でした。 一方で、機能追加や性能改善の要望もありました。 例えば、脅威・脆弱性を検知したタイミングでアラート通知が必要であること。 さらに、学習状況によっては大量のアラート発出が想定されるため、一定間隔でアラートを集約して集約結果をメールでアラート通知する機能を実装しました。 その他に、Web UI 全体を通じてデータ量によっては応答速度が遅くなることも課題になりました。 内部アルゴリズムの見直しなどで、ストレスがないレベルまで応答速度を改善しました。 その他 その他に、実データを踏まえたクラウドやセンサースペック、モバイルデータ量などインフラコストを最適化することで、低価格サービスの実現につなげることができました。 4月25日に OsecT 正式リリース! 実証実験で得られたことを活かして OsecT を改良し、2022年4月25日に サービスリリースしました 。 本サービスの特徴 実証実験を踏まえて改善した OsecT の特徴をご紹介します。 低価格(初期費用 25万円+月額 6万円)で、大規模なセキュリティ投資が難しい中堅企業・中小企業でも導入がしやすくなっています。 複雑な構築は不要でスイッチのミラーポートに接続するだけで、OT システムに影響を与えることなく導入可能です。可用性が求められる OT システム向けには不可欠な特徴です。 リモート環境から Web UI が利用可能で、セキュリティ監視のための出勤や VPN 接続は不要です。 ユーザーインタビューを通じて明らかになった、OT 環境に求められる OT ネットワーク可視化と脅威・脆弱性検知の機能を具備しています。 おわりに 今回は実証実験の取り組みや得られた知見、及びリリースしたサービスの特徴をご紹介しました。 実証実験を通して、想定通りの部分や改善を要する部分が明らかになり、過不足のない開発を進めることができました。 実証実験にご参加いただいた企業さまには、この場をお借りして御礼申し上げます。 また、低価格で OT ネットワークの可視化や脅威検知ができるので、セキュリティ対策に手が出ていない・不十分と感じている企業さまにはぜひご利用いただきたいです。 ご興味がある企業さまは、 こちら からご契約またはお問い合わせをしていただければ幸いです。
はじめに こんにちは、田啓文と申します。NTTコミュニケーションズで開催された2週間のインターンシップに参加させていただきました。 普段はSDNアーキテクチャにおけるDDoS検知法について研究しています。 今回のインターンシップでは「 次世代のサービスを生み出す検証網Testbedの設計構築業務 」をテーマとして、Segment Routingという技術を中心とした様々な検証をしました。 私は学部時代にInter-AS MPLS VPNに関して研究していたため、今回は経験を活かしてL3VPN over SRv6 を検証しました。この記事では、その体験談を記載します。 インターンシップに参加するまでの経緯 日本へ留学する前のことですが、2018年に中国でNTTの求人情報(NTT communications China)を拝見しました。 その時はまだ日本語が流暢に話せず、日本に関する知識もあまりありませんでしたが、そのNTTの求人情報をきっかけに日本の通信企業への関心が高まり、日本への留学を決めました。 そのため、今回インターンシップに参加する際、迷わずNTTコミュニケーションズのインターンに申し込みました。 インターンシップで取り組んだこと 今回のインターンシップでは、Segment Routing(SR)技術とIPv6を融合するSRv6という新しいルーティング技術をCisco機器にて検証しました。 まず前半ではSegment Routingの概要と動作原理を紹介します。 Segment Routing Segment Routingでは、ネットワークドメインにあるそれぞれのノード、パスなどの転送対象をセグメントという単位で扱い、各ノードはSegment ID(SID)で指示された処理を行いながらパケットを転送します。 パケットに経路制御用のヘッダーを追加して、ノードはそのヘッダーの中にあるラベルを使用した上でフォワーディングを行います。 従来のMPLSには、中間ノードのステートが増加するという課題があります。 そこでSegment Routingを利用することで、ネットワークを簡素化して中間ノードの持つステートを削減し、ネットワークの管理と運用を容易にできます。 更に既存のコントロールプレーンとデータプレーンとの親和性があるために、最近では各領域に幅広く使われ始めています。 以下の図がSRで転送する一例となっています。 引用: https://www.juniper.net/us/en/research-topics/what-is-segment-routing.html SRv6 SRv6は、専用のIPv6ヘッダーを利用し、Segment Routingを実現する技術です。 ヘッダーに含まれる各SIDには、パケットの宛先を示すLocator Field、パケットに適用する機能を示すFunction Fieldが用意されています。 Function Fieldに各種の指示を埋め込むことで、SIDを参照した際に各ノードがどのような処理を行うかを指定できます。 Decap後の処理により、End.DT4、End.DT6、End.DX6、End.DX2などのFunctionが定義されています。 SRv6の利点としてはIPv6ネットワークと共存しつつ、128bitのSIDを用いてFunctionを自由に定義できることによる高い拡張性が挙げられます。 実装内容 Ciscoの環境においてSRv6プロトコルを検証するために、まずはシンプルなネットワークトポロジーを構築します。 SRv6に関わるサービスの中で、今回の検証にはIPv4 L3VPN over SRv6 を選びます。以下の図に作成した検証環境を示します。 最初に軽く検証機器について説明します。HostにはUbuntu20.04.3を2台を使います。Provider Edge(PE)としてCisco IOS-XRv 9000 Router を2台使います。 ルーターにはIOS XR Release 7.4.1を使用するため、コマンドは下記のリンクを参照しました。 Cisco IOS XR 7.4.xConfiguration Guides 検証環境を構築するために、以下の手順に従ってセットアップします。 事前準備として各機器のIP addressをセットアップする 2つのPEルーターにベーシックなIntermediate System to Intermediate System(IS-IS)をセットアップする SRv6用にIS-ISプロトコルへLocatorを発行する追加コマンドを設定する L3VPN用のBorder Gateway Protocol(BGP)環境をセットアップする Host間はIPv4 addressを使用し通信します。簡略化のために、Customer Edge(CE)を省略して、SRv6 domainにPEとするルーター2台のみを用意しました。 各PEにIS-ISプロトコルをInterior Gateway Protocol(IGP)としてセットアップし、VPNのためにBGPもセットアップします。 SRv6が正しく動かせるかどうかを検証するために、Host1からHost2へパケットが正常に発送できることを確認します。 また、SRv6 domainにLocatorとSIDが正しく発行、共有されるかどうかも確認します。 結果 インターンシップ最終日までの取り組みと結果を説明します。 まずはIS-ISを確認します。 以下がPEにおけるLocatorの学習状況です。本ルーターのLocatorの状態が up となって、さらに反対側のLocatorも学習できています。 この状態が確認できるとIS-ISのセットアップが成功となります。 次にPE側でSIDの発行状況を表示させた結果です。 End.DT4 のSIDが存在することを確認できると、BGPのコンフィグに成功したことがわかります。 次のステップはPE2でDecapする End.DT4 SIDがPE1に正しく交換されたことを確認します。 以下の図の通り正しく交換されたことが確認できます。ここで、 Behavior:19 はEnd.DT4に対応する番号です。 最後はVPN 経路構築について確認します。 上記のCisco Configuration Guidanceを参照すると、SID 情報が載るはずですが、下記の図では載っていません。 最終日でその原因を考えた結果、恐らくIPv4 packetをSRv6 domainを経由し相手へフォワーディングする際に、SRv6のヘッダーが正しくEncapされないのではないかと推測しました。 このEncapが実施できた場合、当初の期待通りIPv4 L3VPN over SRv6 が実現できると推測しています。 インターンシップの感想 インターンシップ前に何回も日本の職場の雰囲気をイメージしましたが、実際に体験するまでは色々と心配していました。 しかし、先輩社員と一緒に過ごした2週間の体験によって、日本に就職したいという気持ちがさらに湧いてきました。 本当にありがとうございます。 今回テーマとして選択したSRv6は最先端の技術です。 そのため、正しく検証するためにはまずSRv6の動作原理を明確に把握しなければ、問題等がありうまく動かない時に、実際にどの部分が足りていないかがわかりません。 一方、SRv6の情報はベンダーのテキスト以外参照できる文献も少ないという現状もあります。エラーが発生した際には、従来の経験が問題点の把握や対応にあたって大変役に立ちました。 今回のインターンを通じて、仕事上では様々な業務やコミュニケーション、経験、また思考力などが非常に大事なことを再発見いたしました。 特にコミュニケーションについては、外国人なので、専門用語や省略語などが難しく、意識して学習する必要があると感じました。 また、今回のインターンをきっかけとして、本物の職場の雰囲気も体験できました。 メンターの竹中さんには、いつも理解しやすい言葉で複雑な専門用語を説明して頂き本当に感謝いたします。 トレーナーとしてではなく、休憩時間の雑談でも親身にアドバイスやお話をして頂きました。 人見知りの私は飲み会の前にずっと心配していましたが、皆様が本当に親切に接してくれましたので、本番の時にチームの方々と盛り上がってたくさんお話ができました。 最後になりますが、二週間のインターンを通じて貴重な経験をさせていただき、改めて竹中さん、三島さん、木村さんに感謝いたします。 メンターからのコメント メンターを担当したイノベーションセンターの竹中です。2週間のインターンシップお疲れ様でした。 今回のインターンシップにおいて、田さんには我々のチームでの取り組みを理解していただいた後に、興味を持ってもらった部分である Cisco 機器での L3VPN over SRv6 の動作検証に取り組んでいただきました。 インターンシップ初日には SRv6 に関する知識はあまりないとお聞きしていたのですが、持ち前のネットワークに関する知識と探求心、情報を整理し説明/質問する力を存分に活用し、 SRv6 の動作について正確に理解しつつ機器への設定やトラブルシューティングを行い、成果として検証内容を資料にまとめていただきました。 馴染みのない技術に触れながらも、楽しんで次々と技術理解や検証に取り組んでいただけたようでメンターとしても嬉しく思います。 作成いただいた資料は config 例や verificaiton を参考として、今後私たちが行う SRv6 の実用網へのデプロイにおいて活用させていただきます。 このインターンシップを通して得られた様々な経験が今後の田さんの取り組みや意思決定のお役に立てると幸いです。 改めて、インターンシップへのご参加とご活躍、ありがとうございました!
みなさんこんにちは、社内のエンジニアが働きやすくすることを目標にする Engineer Empowerment プロジェクトの @Mahito です。 先日 NTT グループのエンジニア有志が開催する NTT Tech Conference 2022 を開催しました。 私は運営スタッフとして立ち上げからこれまでの運営に関わってきましたが、本記事では運営スタッフの立場でイベントの概要と運営の裏話についていくつか紹介いたします。 NTT Tech Conference とは NTT Tech Conference は、NTT グループのエンジニア有志が開催する技術系カンファレンスです。 NTT グループには各種 OSS のコミッタ、メンテナ、コントリビュータをはじめとしたエンジニアや、各社の様々な案件でシステムやサービスを開発をするエンジニアがいます。 しかしながら、こうしたエンジニアたちの取り組みを会社として紹介する機会は限られています。そこで、NTT グループのエンジニア有志たちが自分たちのエンジニアとしての取り組みを紹介できる場として用意したのが NTT Tech Conference です。 このイベントは、NTT グループに様々なエンジニアがいることや、扱っている技術について多くの人に知っていただくことを目的に行っています。 また、NTT グループ各社が開催するイベントとは異なり、NTT グループのエンジニアたちがやりたいこと・話したいことを通じて、参加したエンジニア同士が技術交流することも目的にしています。 イベント当日の様子 NTT Tech Conference 2022 は、午前の部と午後の部に分けて行われ、午前午後で合計 506 名の参加登録がありました。 午前の部では参加者が手を動かし学ぶ 2つの Hands-on と、参加者でグループを作り体験をしながら学ぶ Workshop が行われました。 午後からは発表が行われ、2 Track で LT も含めて 15 の発表がありました。 どのような発表があったのかご興味のある方は、以下のイベントページからご確認ください。 NTT Tech Conference 2022 イベントページ 午後の発表については、発表者の許諾を得られたものは公開しています。もし興味のあるセッションがあればぜひご覧ください。 また、発表資料が公開されたものについては上記イベントページの発表詳細に資料へのリンクがあります。 午後の部 - Track1 www.youtube.com 午後の部 - Track2 www.youtube.com イベントの裏話(運営の話) さて、前置きが長くなりましたがここからが本題です。 何度も言うように、NTT Tech Conference は NTT グループのエンジニア有志によって開催されているという少し変わったイベントです。 今回は以下の 4 つについて、エンジニアたち自身が運営上行った工夫を紹介します。 発表提案募集 招待講演 イベントページ miro を使った交流 1. 発表提案募集 イベントでの Hands-on、Workshop、発表については Call for Proposals (CFP) という形で、 NTT グループ内のエンジニアたちに手伝ってもらいながらグループ内に発表提案の募集案内を流しています。 発表を希望する側のエンジニアは、自分たちが面白いと思っている技術や紹介したい技術についての発表を提案します。 運営側は、集まった CFP の内容を確認の上、採否を通知します。 ちなみに、現在までにイベントは 6 回の開催をしましたが、毎回ほぼすべての発表提案を通しております。 (私の怪しい記憶では、過去に1度だけ同じ人からの発表提案が同時に2つあったのを1つにしてもらっただけで、ほぼ出せば通るという状態です。) これには理由があり、なるべく多くのエンジニアに発表する機会を提供したいとの思いから、可能な限り全員が発表できるように調整しています。 そのため毎回タイムテーブルの決定にはかなり苦労します。 一方で、CFP は NTT グループ内から幅広く受け付けているものの、グループ各社からまんべんなく出てくるというよりは、かなり偏りがあります。 今回は NTT Com からの発表がかなり多かったのですが、各社の発表数のバランスを考えるというような配慮は全く行っていません。 上記の通り、CFP を出してくれた人に発表をしていただいた結果、「そういう回もある」ぐらいで考えているので今後も偏りは出続けると思います。 しかし、願わくばいろいろな NTT グループの会社から発表提案をいただければと常々思っています。 2. 招待講演 今回は NTT Tech Conference #2 (2017) を最後に行っていなかった招待講演を復活させ、NTT研究所の方にお願いをして下記のタイトルで発表をしていただきました。 「スポーツ脳科学プロジェクトの女子ソフトボール日本代表との取り組み "秘密兵器" の誕生秘話」 招待講演は NTT Tech Conference #1 (2017) 、#2 で行っていました。 しかし、 #1 では講演をお願いをした方がここでは書けないようなお話をされ、会場は大いに盛り上がったものの、後ろでスタッフが慌てるという事態になりました(その後、講演してくれた方は怒られたそうですが)。 そして #2 では事故(?)を起こさないよう話をしてくれる方にお願いをしたのですが、参加者と発表内容が少々ミスマッチだったと、イベント終了後のアンケートでわかりました。そこで、イベントにマッチしない招待講演を避けるため、以降は招待講演を行ってきませんでした。 ただ、今回のイベントを行うにあたりスタッフミーティングの中で、いつもとはちょっと違うなにか面白い発表が欲しいという話になりました。 その時たまたま私が以前ニュースで見た NTT 研究所の取り組みを思い出し、エンジニアコミュニティのツテを使って関係者にコンタクトをとり、今回の発表につなげることが出来ました。 発表では、ソフトボール日本代表選手が無意識のうちに相手投手のフォームから球種を予測していることを突き止め、ピッチャーのフォームと球筋を再現するしくみを作り、それを選手たちの練習に使ってもらうまでのお話をしていただきました。 残念ながら発表者の意向で当日の発表動画の公開は行っていませんが、取り組みを紹介する記事はありますので、興味がある方はぜひご覧いただければと思います。 女子ソフトボール × スポーツ脳科学 | NTT 技術ジャーナル 3. イベントページ 先にも紹介した NTT Tech Conference 2022 イベントページ ですが、 こちらはスタッフがデザインから実装までを行い、GitHub Pages を使って公開をしています。 イベントページを変更する際は以下のような流れで GitHub Actions による CI/CD を組み合わせながら作業しました。 変更のブランチを GitHub に Push Pull Request を作成 GitHub Actions が起動 textlint による文章のチェック Firebase にレビュー用のページをデプロイ デプロイしたレビュー用ページの URL を Pull Request に投稿 2 人以上の Approve でマージ可能に main にマージ後、GitHub Pages に変更を反映 第 1 回目からイベントページの作成は自動化も含め上記のような流れで行ってきており、 変更自体は比較的簡単に行えるのですが、レビューについてはスタッフの空き時間確保が必要なため、どうしても時間がかかります。 そこで、急ぎの場合は NTT グループのエンジニアコミュニティの面々にもレビューを手伝ってもらいました。これにより速やかに変更を反映できたので、非常に助かりました。 4. miro を使った交流 NTT Tech Conference ではイベントの目的にエンジニアの交流を挙げていて、オフラインで開催していた頃は廊下での雑談や、展示会場、懇親会などを用意していました。 しかしながら、前回からオンラインでの開催となり、こうしたちょっとした場での交流というのが難しくなったと感じています。 前回は Ask the Speaker という発表者に直接質問や話をできる場を用意したのですが、まったく質問に来る人がいませんでした。 この状況に対し、「オンラインのカンファレンスで誰がいるかわからない場に飛び込んで質問をするのは敷居が高いのではないか?」という話がスタッフからでたため、今回はゆるく技術交流のきっかけになるような方法としてオンラインホワイトボードサービスの miro を採用しました。 miro には予めセッションごとの枠を作り、そこに参加者の方は付箋に書いたメッセージを通じて質問やコメントなどを残し、発表者の方には質問への回答をお願いするという非同期コミュニケーションの形をとりました。 上の画像は当日いただいた質問やコメントですが、それなりの数のコメントや質問をいただき、発表者からの回答もいただけているのでひとまず良かったかなと思っています。それでも、次回のイベントではもっと参加されるエンジニアの方々と交流できるしくみを今から考えていくつもりです。 まとめ 今回は NTT Tech Conference 2022 のご紹介と、その裏側について少しだけご紹介しました。 イベント本編だけでは伝わらないスタッフの工夫や苦労を少し知っていただき、次回以降のイベントをちょっと違った視点で楽しんでいただけたらと思います。 また、このようなイベントを開催したいなと考えている方に少しでも参考になれば幸いです。 次回の NTT Tech Conference は来年に開催する予定ですので、ご都合が合えばぜひご参加ください!
こんにちは。マネージド&セキュリティサービス部セキュリティサービス部門の閏間です。総合リスクマネジメントサービス「 WideAngle 」の新サービスの企画を担当しています。 本記事では、私がセキュリティの知識・技術向上のために業務外で取り組んでいるバグバウンティプログラムについて、3回にわたって紹介します。 本記事により、バグバウンティプログラムの有効性と、脆弱性探しのおもしろさの両方を伝えられれば幸いです。 (前編)バグバウンティプログラムの有効性について (中編)脆弱性探しの魅力と調査方法について (後編)実際に発見した脆弱性の詳細について【本記事】 なお、バグバウンティに関する記事としては、 NTT Com社内バグバウンティのご紹介 もありますので、ぜひそちらもご覧ください。 脆弱性の実例:3つの問題が重なってXSS(Cross Site Scripting)が発生 本記事では、私が過去に発見した脆弱性を1つ、技術的な詳細も含めてご紹介します(公開についてはアプリケーションの開発元に承諾を得ています)。 あるWebアプリケーションに、ユーザーのプロフィール画像を設定するという機能がありました。ここに以下の3つの問題があり、これらの問題が重なってXSSの脆弱性が発生していました。 プロフィール画像のURLに直接アクセスすると、画像ファイルがダウンロードされるのではなく、直接Webブラウザー上で表示される。 プロフィール画像を設定する際に指定した Content-Type の値が、プロフィール画像にアクセスした際のレスポンスにそのまま設定される。 プロフィール画像を設定する際、画像以外のデータを設定でき、さらに、XSSフィルタ機能はあるものの、回避する手段がある。 XSSとは、Webページにスクリプトなどを埋め込むことができてしまう脆弱性です。悪用されるとWebページを閲覧しているユーザーの情報漏洩などにつながることもある、危険な脆弱性です。 1について Webサイトにアクセスしたとき、Webサーバーから送り返されたコンテンツがWebブラウザー上で表示されるか、それともダウンロードされてファイルとして保存されるかは、Webサーバーからのレスポンスに含まれる Content-Disposition ヘッダで決まります。以下のように attachment という値が付いていれば、Webブラウザーはコンテンツをファイルとして保存しようとしますが、付いていなければWebブラウザー上に表示します。 Content-Disposition: attachment 当該アプリケーションでは、プロフィール画像のURLに直接アクセスすると、レスポンスの Content-Disposition ヘッダに attachment は付いておらず、Webブラウザー上に画像が表示されました。ここで私は、「もしプロフィール画像として< script >タグを含むHTMLを保存でき、かつ、そのコンテンツがHTMLであるとWebブラウザーに認識させられれば、XSSを起こせるな」と考えました。 2について 画像ファイルにアクセスした場合、そのレスポンスの Content-Type ヘッダには、画像コンテンツのファイル形式を示す値が設定される挙動が正しい動作です。例えば、jpeg画像の場合は以下のようになることが期待されます。 Content-Type: image/jpeg しかし当該アプリケーションでは、プロフィール画像を設定する際に自動的に設定される Content-Type の値をローカルプロキシーツールで書き換えると、その後その画像ファイルにアクセスした際のレスポンスに書き換え後の値がそのまま使用されるという動きをしていました。プロフィール画像として設定するデータの中身が実際には何であっても、です。例えば、プロフィール画像の設定時に Content-Type の値を text/html に書き換えると、画像ファイルのURLにアクセスした際、 Content-Type の値が text/html であるレスポンスが返ってきていました。 これで、「1について」に書いた「そのコンテンツがHTMLであるとWebブラウザーに認識させられれば」という条件を満たせることになります。残る壁は、いかにして「< script >タグを含むHTMLを保存するか」です。 3について プロフィール画像を設定する際、データとして単純に <script>alert("XSS!!")</script> のような文字列を渡しただけでは、エラーとなってしまって< script >タグを埋め込むことはできませんでした。どうやら、 < や > があったらエラーにするというXSSフィルタ機能がWebサーバー側に備わっていたようです。 そこでこのXSSフィルタを突破すべく、以下の文字列をWebサーバーに渡しました。 +/v8-+ADw-script+AD4-alert("XSS!!")+ADw-/script+AD4- これはエラーにならずに、プロフィール画像のデータとして保存されました。 そしてプロフィール画像のURLにアクセスすると、< script >タグが動作し、「XSS!!」と書かれたダイアログが表示されました。これでXSSの成立です。 ここで、なぜ< script >タグが動作したが疑問に思われた方もおられると思います。その原理は以下の通りです。 このXSSはInternet Explorer 11でのみ発動するのですが、以下のような流れでXSSが発動していました。 +/v8 は、UTF-7のBOM(Byte Order Mark; 符号化の種類の判別に使用する数バイトのデータ)である。 Internet Explorer 11は、UTF-7のBOMがコンテンツの先頭にあると、エンコーディングがUTF-7であるとみなしてコンテンツを表示する。 エンコーディングがUTF-7の場合、 +ADw- 、 +AD4- はそれぞれ < 、 > を表すので、 +/v8-+ADw-script+AD4-alert("XSS!!")+ADw-/script+AD4- は <script>alert("XSS!!")</script> と解釈され、スクリプトが実行される。 ※XSSフィルタの突破方法については、 Browser's XSS Filter Bypass Cheat Sheet を参考にさせていただきました。 本事例で紹介した脆弱性の要因はそれほど一般的なものではないため、作りこみ防止や検出方法はパターン化されていません。そのため、セキュア開発ライフサイクル活動や脆弱性診断をどんなに丁寧に行っても、このような脆弱性が運用フェーズに流出するリスクは残ると思います。そのようなリスクを少しでも0に近づけるために、バグバウンティプログラムは有効であると考えています。もちろん、脆弱性が運用フェーズに流出してしまったあとの検出手段としても、バグバウンティプログラムは有効です。 まとめ バグバウンティプログラムの有効性と脆弱性探しの魅力について、3回にわたってご紹介しました。 ソフトウェアやシステムの開発・運用に携わる人の中には、セキュリティを専門に扱っていなくとも、脆弱性を発見できるだけの知識やスキルを持った方は大勢いると思います。そのような人たちも巻き込んでバグバウンティプログラムが発展を続け、世の中のシステム全体がよりセキュアな方向に向かえば素晴らしいことだと思います。 その一方で、ソフトウェアの脆弱性はセキュリティインシデントの原因の一部に過ぎないので、たとえ脆弱性がなくなっても、それだけでセキュリティに関するリスクが完全になくなるということはありません。ソフトウェアの脆弱性とは関係のない、人間の不注意を突くような攻撃や、組織内部の人物が行う不正行為によっても被害は発生し得るからです。 そのような事態に備え、防御、検知、対応、復旧といった活動をスムーズに行える体制づくりとその運用は、ソフトウェアの安全性がどんなに高まったとしても重要であり続けると考えています。
こんにちは。マネージド&セキュリティサービス部セキュリティサービス部門の閏間です。総合リスクマネジメントサービス「 WideAngle 」の新サービスの企画を担当しています。 本記事では、私がセキュリティの知識・技術向上のために業務外で取り組んでいるバグバウンティプログラムについて、3回にわたって紹介します。 本記事により、バグバウンティプログラムの有効性と、脆弱性探しのおもしろさの両方を伝えられれば幸いです。 (前編)バグバウンティプログラムの有効性について (中編)脆弱性探しの魅力と調査方法について【本記事】 (後編)実際に発見した脆弱性の詳細について なお、バグバウンティに関する記事としては、 NTT Com社内バグバウンティのご紹介 もありますので、ぜひそちらもご覧ください。 脆弱性探しはおもしろい 本記事では、バグハンターとしての経験を振り返りつつ、脆弱性探しの魅力をお伝えしたいと思います。 脆弱性探しに取り組み始めたきっかけは、NTT Comの前に勤務していた会社で、セキュリティ機能の開発担当になったことです。知識向上だけでなく、実践を通した技術力向上も目指したいと考えていたところに、サイボウズの脆弱性報奨金制度のことを知り、取り組み始めました。3ヶ月くらい取り組み続けていたところで初めて脆弱性が認定され、達成感が感じられたので、以来ずっと続けています。 ちなみに私は脆弱性診断を業務として実施したことはなく、脆弱性探しはあくまで趣味として取り組んでいます。ただ趣味といっても、楽しいだけの趣味とは異なり、しんどい思いをする場面も多いです。とくに、長期間脆弱性が見つからないときは気持ちが沈みがちです。しかしうまくいけば後述するような達成感や充実感が得られるので今も続けられています。これまでに見つけた脆弱性は8年間で100件ほどです。トップクラスのバグハンターになるとひと月に20件くらいのペースで脆弱性を発見し認定されているので、私はそれほどハイペースではありません。細く長く続けているといった感じです。 脆弱性探しによりさまざまな達成感が得られる 私の場合、以下のような点で達成感や充実感が得られるのがうれしいので、脆弱性探しに取り組んでいます。 力試し 知的好奇心を満たす 本来できないはずのことができてしまうのが楽しい 品質向上への貢献 報奨金を得る バグバウンティプラットフォームが公開するレポートで、バグハンターがバグバウンティプログラムに参加する目的についてのアンケート結果が載っていることがありますが、私の目的もおおむねそれらと同じです。もちろんNTT Com社内バグバウンティプログラムにも参加しました。こちらは入社してまだ間もなかったこともあり、話題作りを狙った面もありました。 脆弱性探しに対する心構えとして、私は脆弱性が見つかればいいなという希望的観測ではなく、脆弱性は必ずあるという確信にもとづいて脆弱性探しに臨んでいます。脆弱性を探しても見つからないときは、脆弱性が存在しないのではなく存在してるけれど自分が見つけられていないだけだ、と考えます。言い換えれば最後は執着心や執念で脆弱性を見つけているのですが、その気持ちを維持できるのは、過去に他社でソフトウェア開発をしていた経験によるものです。開発当初に作りこんでしまった潜在バグが数年後に見つかる、という経験を何度も繰り返した結果、ある程度の規模のソフトウェアのバグを0にするのは極めて困難である、と考えるに至りました。このような考えを持っているため、あきらめずに脆弱性を探し続けることができます。 カジュアルな手法でも脆弱性は見つけられる 脆弱性を探す方法としては、例えばWebアプリケーションを構成するJavaScriptやOSS(Open Source Software)ライブラリのソースコードを読んだり、見つけたあやしい箇所に対して攻撃するためのツールを作って実行したりする方法があります。しかし私はそのような高い技術力を必要とする方法ではなく、WebブラウザーでいろいろなURLにアクセスしてみたり、ローカルプロキシーツールを使ってHTTPリクエストのパラメータを書き換えてみたりするといった、比較的カジュアルな方法で脆弱性探しをすることが多いです。意外に思われるかもしれませんが、そのようなカジュアルな方法でも、結構脆弱性は見つかります。 大まかには、以下のような手順で脆弱性を探しています。 何が資産かを考える。 ユーザーが保存するデータ、設定値、ユーザーアカウント情報、ログデータ、機能を利用できる権利、etc... 資産に対し、情報セキュリティの3要素(機密性、完全性、可用性)のいずれかの点で問題を起こすことを考える。 一般的に、機密性、完全性、可用性のいずれとも関係ない事象は、脆弱性であると認定されないため。 アプリケーションへの入力を、通常とは異なる形に変化させ、処理された結果問題が起きることを目指す。 コンピューターの動作の仕組みは、ものすごく単純化すると、入力→処理→出力、となるが、これをふまえると、おかしな出力を得るにはおかしな入力を与えればよいため。 ひと口にアプリケーションといっても、Webアプリケーションやネイティブアプリケーションなどいろいろなタイプのものがあります。例えばサイボウズOfficeはWebアプリケーションの一例ですし、ネイティブアプリケーションの例としてはGoogle Chromeなどが挙げられます。WebアプリケーションはWebサーバーとの通信がHTTPとして規格化されており、後述するローカルプロキシーツールで簡単に通信に介入できるといった特徴があるため、入力をいじりやすいです。その意味で、Webアプリケーションは脆弱性を探索しやすいタイプのアプリケーションともいえます。 もちろん、アプリケーションがとり得る入力をすべて網羅的に調査するのはあまりに非効率です。そのため典型的な脆弱性をまとめたドキュメントを読んで、脆弱性のパターンを理解したうえで調査するようにしています。例えば以下のドキュメントです。 安全なウェブサイトの作り方 (IPAが制作、公開) Web Hacking 101 (Peter Yaworski著) 「Web Hacking 101」はHackerOneにユーザー登録すると無償でPDF版をもらえます(少なくとも私が登録した時はもらえました)。「Web Hacking 101」には実際に発見された脆弱性の実例について詳細が書かれており、SSRF(Server Side Request Forgery)やXXE(XML External Entity)といった、比較的新しい種類の脆弱性についての記載もあります。脆弱性の実例に関する情報は Hacktivity (HackerOneのバグハンターの活動記録)などでも得ることができますが、ここで挙げたようなドキュメントで全体像を把握することもよいことだと思います。 典型的な脆弱性を探す調査のほか、セキュリティ機能の基本であるAAAも定番の調査対象です。AAAとはAuthentication(認証)、Authorization(認可)、Accounting(アカウンティング)の3つの機能のことを指します。もしこれらのセキュリティ機能が正しく動作していなければ、直接的な機密性/完全性/可用性の問題がなくとも、それだけで脆弱性といえる場合が多いからです。例えば、パスワード変更してもログインセッションが有効な状態で残っていたり、本来アクセス権のないデータを参照できたり、監査ログの改ざんができたりするといった類のものです。 そのほかには機能が複雑になればその分脆弱性が入りこむ可能性が高まると考え、機能的に複雑な部分を狙って調査することもあります。また、長くバグバウンティプログラムが実施されているソフトウェアは新たな脆弱性が見つかりにくくなる傾向があります(これを「枯れた」状態と呼びます)。そのようなケースでは、アップデート時に追加された機能に絞って調査する、という方法も有効です。 ローカルプロキシーツールを使用することは超基本 調査にあたっては、ローカルプロキシーツールと呼ばれる、WebブラウザーとWebサーバーの間のデータのやりとりを仲介するツールをよく使います。このツールを使うと、WebブラウザーとWebサーバーの間の通信の中身を見たり、Webサーバーに送信するデータを書き換えたりすることが可能です(実際にはほかにももっと豊富な機能が備わっています)。また、よく使われているローカルプロキシーツールの多くはHTTPS通信にも対応しています。「Webブラウザー - ローカルプロキシーツール」、「ローカルプロキシーツール - Webサーバー」という2つのセッションに分離し、仲介するローカルプロキシーツール上で一度HTTPSを復号することで、データの参照・書き換えを実現しています。 代表的なローカルプロキシーツールとしては以下のものがあります。 Fiddler OWASP ZAP Burp Suite Community Edition Fiddlerは、起動しただけでWindowsのプロキシー設定をFiddlerの待ち受けポートに自動的に変更しますが、この点が便利なのでお気に入りでした。しかし後継のFiddler Everywhereは旧版(Fiddler Classic)の全機能を引き継いでおらず、脆弱性探しには欠かせない「HTTPリクエスト送出をインターセプトしてパラメータを書き換える機能」すら備えていないので、最近は別のツールに浮気中です。もともとFiddlerは脆弱性調査を目的としたツールではなく、Webアプリケーションのデバッグが目的なので仕方ないのかもしれません。 また、最近調査したWebアプリケーションではWebSocketを使用していたのですが、FiddlerだとWebSocketのメッセージ書き換えができないことも、Fiddlerから気持ちが離れ気味なもう1つの理由です。OWASP ZAPであればWebSocketのメッセージ書き換えに対応しているため、最近はもっぱらOWASP ZAPを使うことが多いです。私は試したことはないのですが、Burp SuiteでもWebSocketのメッセージ書き換えができるようです。 脆弱性探しに興味を持たれた方は、まずはOWASP ZAPかBurp Suiteをインストールして、WebブラウザーとWebサーバーの間でどのような通信が行われているかを眺めるところから始めてみるとよいと思います。 なお、「ローカルプロキシー」という呼称ですが、Webブラウザーと同じPCで動作させるという意味で「ローカル」という言葉が使われているのだと思われます。しかしリモート(Webブラウザーとは別のPC)でローカルプロキシーツールを動作させることももちろん可能です。例えばこのことを利用して、スマホアプリの調査をするときは、「スマホ上のWebブラウザー - PC上のローカルプロキシーツール - Webサーバー」という構成で調査できます。 まとめ 本記事では、脆弱性探しの魅力と調査方法についてご紹介しました。 本記事をきっかけに、脆弱性探しに興味を持たれる方がいればうれしいです。 次回 は、私が過去に発見した脆弱性について、技術的な詳細も含めてご紹介します。
こんにちは。マネージド&セキュリティサービス部セキュリティサービス部門の閏間です。総合リスクマネジメントサービス「 WideAngle 」の新サービスの企画を担当しています。 本記事では、私がセキュリティの知識・技術向上のために業務外で取り組んでいるバグバウンティプログラムについて、3回にわたって紹介します。 本記事により、バグバウンティプログラムの有効性と、脆弱性探しのおもしろさの両方を伝えられれば幸いです。 (前編)バグバウンティプログラムの有効性について【本記事】 (中編)脆弱性探しの魅力と調査方法について (後編)実際に発見した脆弱性の詳細について なお、バグバウンティに関する記事としては、 NTT Com社内バグバウンティのご紹介 もありますので、ぜひそちらもご覧ください。 外部人材を巧みに活用することでシステムの安全性を高められる バグバウンティプログラムとは日本語で脆弱性報奨金制度のことで、企業が自社のソフトウェアやシステムの脆弱性を見つけてくれた人に報奨金を支払う制度のことです。脆弱性探しをする人はバグハンターと呼ばれます。文脈によってはリサーチャー、エシカルハッカー、ホワイトハットハッカーなどと呼ばれることもあります。 バグバウンティプログラム実施の流れとしては、 企業が調査対象のWebアプリケーションなどについて、調査対象URLや禁止事項、報奨金額の目安、詳細な参加規約などを公開する。 規約に同意したバグハンターが調査を行い、脆弱性を見つけたら企業に報告する。 企業が報告内容を評価し、脆弱性であると認定したらバグハンターに報奨金を支払う。 というものが一般的です。 このような流れにより、世界中の多くのバグハンターにより脆弱性を見つけてもらえるチャンスが生まれるわけです。バグハンターは報奨金を得るために脆弱性を探し、企業は見つかった脆弱性に報奨金を支払うとともに脆弱性を修正する、というサイクルを通し、ソフトウェアやシステムの安全性が高まっていくことになります。 もちろん、バグバウンティプログラムという制度とは別に、世の中のセキュリティ向上などの目的のために無報酬で脆弱性探しをされている方もいます。しかし調査規模や発見される脆弱性の件数を大きくするには、報奨金というインセンティブを制度に組み込んで、大規模にバグハンターを集めるという仕掛けが効果的であろうと思います。 ちなみに、「バグバウンティ」といってもどのようなバグに対しても報奨金が支払われるわけではなく、基本的には脆弱性、つまりセキュリティに関する欠陥のみが支払いの対象となります。 バグバウンティプログラムには、従来のテスト手法にない長所がある これまでもソフトウェアやシステムのセキュリティ強化のために、セキュア開発ライフサイクルの活動により脆弱性の作りこみを防ぎ、脆弱性診断やペネトレーションテストを通して運用フェーズへの脆弱性の流出を防ぐ、といった取り組みが行われてきました。 この上さらにバグバウンティプログラムを実施することにどのような意味があるのでしょうか? その答えは、バグバウンティプログラムの特長にあります。バグバウンティプログラムの特長としては以下のようなものがあるといわれています。 多様なバックグラウンドを持つバグハンターによる調査により、ほかのテスト手法で発見できなかった脆弱性が見つかる可能性が高まる。 期間を区切らずに実施しやすいため、世の中のセキュリティ動向に応じたタイムリーな調査につながる可能性が高まる。 (期間を区切らずに実施しやすいことの背景には、報奨金支払いが発生するのは基本的には脆弱性が見つかったときのみで実施期間の長さには依存しないため、企業側から見た経済性の面で有利であるということが挙げられます) 上記のような特長により未知の脆弱性を発見できる可能性が高まるため、バグバウンティプログラムは実施する意義があります。ただし、バグバウンティプログラムはこれまでのテスト手法にとってかわるものではありません。あくまで、これまでのテスト手法を補完する位置づけのものです。 バグバウンティプラットフォームを活用することで、バグバウンティプログラム運営の負荷低減が可能 このようにセキュリティテストとして魅力のあるバグバウンティプログラムですが、企業がすべてを自社でまかなうのは大変です。制度の維持には、以下のように多くの業務や仕組みが必要だからです。 調査ルールや参加規約の策定 バグバウンティプログラムの告知 報告の受け付け窓口の設置 報告内容の検証 報奨金の支払い事務 そこで、企業とバグハンターを結びつけ、制度運営の大部分を代行する、バグバウンティプラットフォームと呼ばれるサービスも誕生しています。以下が主なものです。 HackerOne Bugcrowd Intigriti YES WE HACK BugBounty.jp (私が調べた範囲では、ほかに10数件のバグバウンティプラットフォームがあります) これらのバグバウンティプラットフォームの中には、バグバウンティプログラム運営を支援するのみならず、脆弱性開示ポリシー策定支援、ペネトレーションテストの提供、バグハンター向けトレーニングコンテンツ提供、セキュリティカンファレンス開催、といったさまざまなサービス提供を行うプラットフォームもあります。各バグバウンティプラットフォームは、バグバウンティプログラム数や登録バグハンター数を競うだけでなく、提供するサービスの違いによっても特色を出そうとしているように見受けられます。 現在はバグバウンティプログラムを自社運営するのではなく、上記のようなバグバウンティプラットフォームを利用することが一般的になっています。バグバウンティプラットフォームを活用してバグバウンティプログラムを実施することは、企業が不特定多数のバグハンターに脆弱性探しを依頼するクラウドソーシングである、という見方をすることもできます。 バグハンターから見ても、バグバウンティプログラムを個別に探す必要がないことや、脆弱性報告を統一されたインターフェースで行うことができるといったメリットがあるので、バグバウンティプラットフォームを利用する価値は高いです。CTF(Capture the Flag)を解いて一定のポイントをためたバグハンターをプライベート形式のプログラムに招待したり、バグハンターの脆弱性発見のランキングを公開したりするなど、バグバウンティプラットフォームはバグハンターのモチベーション維持にも色々と工夫しています。 バグバウンティプログラムは、拡大を続けている 本記事のタイトルでは「新たなセキュリティテスト手法」と書きましたが、実際には歴史はそれなりにあります。現在のようなバグバウンティプログラムが初めて行われたのは、1995年のネットスケープコミュニケーションズによるものです( Wikipedia より)。本記事では、日本ではバグバウンティプログラムはまだあまり広まっていないという現状を鑑みて、「新たな」という表現を使いました。 現在バグバウンティプログラムの実施は、バグバウンティプラットフォームを利用するのが主流ですが、その中でも最大の規模を誇るバグバウンティプラットフォームはHackerOneです。HackerOneは2012年に立ち上がったバグバウンティプラットフォームで、2021年の報奨金総額は3692万5156ドルにものぼります( Hacker-Powered Security Report: Industry Insights '21 より)。バグバウンティプログラム数は、パブリック形式(公開されているもの)だけで約380件あります。2022年1月に4900万ドル(シリーズEラウンド)の資金調達に成功しており、会社規模としても拡大を続けているようですが、それだけバグバウンティ事業が活況ということなのでしょう。 日本では、バグバウンティプログラムを自社運営する企業としては、 サイボウズ (2014年~)や Sky (2022年~)があります。HackerOne上でバグバウンティプログラムを実施している日本企業も5社ほどあります。また、日本のバグバウンティプラットフォームであるBugBounty.jpでバグバウンティプログラムを実施している企業も数社あります。さらに2021年9月には、スリーシェイクが Bugty というバグバウンティ運用代行サービスをリリースしています(バグバウンティプラットフォームとしてIntigritiを利用)。 このように、日本ではバグバウンティプログラムを実施する企業の数はまだあまり多くないですが、近年少しずつ増えてきている印象を受けます。今後も増加傾向は続くのではないかと予想しています。 社内限定のバグバウンティプログラムも有効 バグバウンティプログラムの実施を検討しようとしたとき、いきなり社外のバグハンターに調査してもらうことに不安を感じる方もいらっしゃると思います。そういったケースへの1つの解として、プライベート形式でのバグバウンティプログラムというものもあります。不特定多数のバグハンターにバグバウンティプログラムを公開するのではなく、バウバウンティプラットフォーム上で一部のバグハンターのみにバグバウンティプログラムを公開し、調査してもらうという形式です。 別の方法として、社内限定でバグバウンティプログラムを実施するという方法もあります。自社の社員のみにバグバウンティプログラムを公開し、調査してもらうという方式です。この場合は、バグハンターは社員ということで、身元が完全に把握できますので安心感はより高まります。その一方で、多様なバックグラウンドを持つバグハンターに調査してもらえるというメリットは限定的になりますし、運営をすべて自社で行わなければならない(バグバウンティプラットフォームは利用できない)というデメリットもあります。バグバウンティプログラムを実施したい企業の事情に合った方式を選択するとよいのではないかと思います。 冒頭でも紹介した通り、NTT Comは社内バグバウンティプログラムを実施しています。他社で社内バグバウンティを実施している例はあまり聞いたことはなく(大学で千葉大学、電気通信大学があるくらい)、バグハンターの一人として先進的で素晴らしい取り組みだと感じています。今後も制度を継続・拡大させて、お客さまにより安全なシステムをお届けできる体制がより強固になるとよいなと思います。 まとめ 本記事では、バグバウンティプログラムの有効性についてご紹介しました。 システムやソフトウェアを提供する立場の方にとって、本記事がバグバウンティプログラムを検討する1つのきっかけになれば幸いです。 次回 は、私の脆弱性探しの経験をもとに、脆弱性探しの魅力と調査方法についてご紹介します。
はじめに こんにちは、2月14日から2月25日までNTTコミュニケーションズの職場体験型インターンシップに参加させていただきました関根です。インターンシップにはテレプレゼンスエンジニアとして参加し、VR酔いを軽減するテーマに取り組みました。この記事では、私が体験したインターンシップの内容について紹介できればと思います。 テレプレゼンスロボットとは テレプレゼンスロボットとは、遠隔操作技術や映像転送技術などを組み合わせることにより、遠隔地であっても、まるでその場にいるように活動できるロボットです。 参考: テレプレゼンスのイメージがわかる動画 今回扱ったテレプレゼンスロボットは、HMD(ヘッドマウントディスプレイ)を使用してカメラの向きの操作と、HMDへのリアルタイム映像表示ができるロボットです。3つのサーボモータによりカメラの姿勢をHMDの姿勢と同期させることができ、2つのカメラの映像をネットワーク経由でHMDの右目と左目それぞれの画面に表示することで、ロボット視点の映像を立体的に表示できます。この技術を使うことで、遠く離れた場所で点検作業やケーブルの差し替えのような簡単な作業をしたいときに、現地に行かなくても自分でロボットを操縦して対応できるようになります。 www.youtube.com 課題 一般的なVRゲームでは、HMDのジャイロセンサ等をもとに自己位置推定を行い、仮想空間上のカメラの位置・姿勢を更新しレンダリングすることで、ユーザーの動きに応じた映像変化を実現しています。 対してテレプレゼンスロボットでは、遠隔地のロボットから映像を得る必要があります。 まず、HMDで得られたユーザーの動きを、ネットワーク経由でロボットの姿勢データとして送信します。 次に、この姿勢データに合わせ、カメラの位置と姿勢を内蔵のサーボモータで変更します。 その後、得られたカメラ映像をエンコードし、ネットワーク経由でHMDに送信することでようやくHMD内に映像が表示されます。 このため、テレプレゼンスロボットでは、VRゲームと比べて大きな遅延が発生します。 通信遅延については経路や通信環境に依存しますが、例えば日本にいるオペレータがアメリカにあるロボットを操縦する場合、遅延時間は往復で200msほどになります。エンコードについては使用するハードウェアにもよりますが30〜70msほどになります。また、通信やエンコードの遅延時間だけでなく、ロボットがHMDと同じ姿勢になるまでのモータの駆動時間も遅延に含まれます。なお、今回用いたハードウェア構成では、国内ということもあり、合計すると約120msほどの遅延となりました。 カメラの映像は、HMDの左右の液晶にそのまま表示しています。そのため、HMDを被って左右に頭を動かすと、遅延時間の分だけ向いている方向と映像がズレることになります。この遅延は、HMDの動きに対する映像の遅延であるため、実際にHMDを被らないと認識が難しいです。そこで、分かりやすく可視化するために、シミュレータを作成し動画を作成しました。 www.youtube.com この動画では、遅延がある場合と遅延がない場合を比較しています。 HMDを瞬間的に動かすと、200msの間は画面が固定されたままになります。 逆に200ms後には、何もしていないのに勝手に映像が動くことになります。 これにより、VR酔いと呼ばれる乗り物酔いに近い現象が発生してしまいます。 一度VR酔いが発生すると、テレプレゼンスロボットの使用継続が困難となるため、この課題を解決することは重要です。 応答性の高い高価なモータを用いて抑えることも考えられますが、 今後テレプレゼンスロボットを多用途に普及させる上では、なるべく安価なハードウェアで長距離でも使えるように実装したいところです。 一般的に200msを超えるとVR酔いに耐えられなくなるため、日米間の通信遅延は200msであることを考えると、 遅延時間を減らすアプローチには限界があります。 そこで本インターンシップでは、遅延を許容しつつ映像のズレを軽減できるアプローチを模索しました。 検討手法 テレプレゼンスの場合でも、一般的なVRゲームと同様に、操縦者側のPC内だけで映像の描画ができれば酔いが軽減できると考えられます。 VR酔いの原因は操縦者の姿勢と描画される映像の位置のズレに酔いの原因があるため、 操縦者側のPC内に存在する情報を加工し、この差分を埋めることにしました。 今回は、既に受信済みの映像をリアルタイムに補正することで、遅延がない場合の映像に近づけることにしました。 ロボットからの映像が届いていない状態でも、操縦者の姿勢情報に合わせて映像の描画位置を補正してあげることで、頭部方向と映像のズレを緩和できます。 ただし、カメラの位置のズレに映像の補正で対応するためには、物体との距離が必要となります(カメラを一定量動かした場合、物体が近いほど大きく動きます)。 対して、姿勢のズレに映像の補正で対応する場合、物体との距離は関係ないので、カメラ映像を射影変換するだけで対応可能です。 そこで、今回のインターンシップでは、第一段階として姿勢のズレを低減する方法を提案して設計・実装しました。この射影変換を使った姿勢のズレ補正を可視化した動画が次の通りになります。 www.youtube.com この動画では、遅延している映像を射影変換によって、遅延がない映像に変換できることを示しています。カメラの視野角を超える範囲は表示できませんが、見えている範囲に関しては遅延がない映像と全く同じになります。この射影変換の処理はクライアントPC(HMDのつながっているPC)内で完結するので、通信遅延やエンコードの遅延の影響を受けることはありません。よって、仮に通信遅延が200msあったとしても、姿勢に関して遅延のない映像にできます。 1.カメラの姿勢の取得 この手法で姿勢のズレを無くすためには、HMDから得られる目標の姿勢と、映像の各フレームで撮影されたタイムスタンプにおけるカメラの姿勢を取得し、姿勢のズレだけ映像を回転する必要があります。単純化すると、HMDの角度がαでカメラの角度がβの場合は、α-βだけ画像を回転すれば良い、ということになります。 カメラの角度を取得するためには、ジャイロセンサ等を用いて姿勢推定を行う必要があります。また、得られた姿勢データは、映像の各フレームでのタイムスタンプと同期して、ロボットからクライアントPCに送信する必要があります。最後に、クライアントPCではHMDの姿勢とカメラの姿勢の差分をもとに映像を射影変換することになります。そのため、今回は新しくハードウェア・ソフトウェアの設計・実装をネットワークも含めて行うことになりました。 2.ロボットの姿勢の取得と修正 この手法を実装するにあたり新しく出てきた課題として、ロボットに加わる外力への対応があります。従来の実装では、外力によってロボット本体が回転した場合、HMDが止まっていても映像が勝手に動いていました。しかし、今回の手法では映像が動かない代わりに、映像の表示領域が動くようになります。この現象をシミュレーションした動画が次の通りです。 www.youtube.com 動画では、HMDを動かさずにロボットを外力によって回転させています。従来の補正がない場合では勝手に映像が動きますが、今回の補正では映像自体が動かない代わりに映像の表示枠が動きます。これにより、ロボットを外力で回転したままにすると、射影変換によって動かされた映像の枠が中心からズレて戻らなくなります。テレプレゼンスロボットは、車輪などでロボットごと移動するので、こういった外力への対処は避けて通れません。そこで、カメラの姿勢を一定に保つ姿勢制御(スタビライザのようなもの)を実装することにしました。 www.youtube.com この姿勢制御では、カメラとは独立して、ロボット台座の姿勢推定を行い、ロボットが外力で動かされた分だけ映像を回転させています。 これにより、外力がある状況でも、HMDの姿勢とロボットの姿勢を同期させられます。 (従来は、ロボット台座に対するカメラ姿勢とHMD姿勢が同期していたが、今回は地面に対するカメラ姿勢とHMD姿勢が同期している)。 テレプレゼンスロボットでは、基本的には室内での利用を前提としていることが多いですが、室内でもコンセントボックスやケーブルをロボットが踏むことで揺れてしまうケースは多いです。 特に、ロール軸(視線の方向)で回転させられた場合、地面が傾くような感覚となり、平衡感覚を失ってVR酔いしやすくなります。こういったVR酔いについては、姿勢制御で根本的に改善できます。次の動画は、頻繁に揺らされる場合のシミュレーションですが、姿勢制御があると映像は全く動くことはなく非常に安定しています。 www.youtube.com 実装 今回のインターンシップでは、2週間で部品の選定からコーディングまで実装する必要があったため、なるべく短期間で高い性能を出せる実装方法を模索しました。ハードウェアなので調達に時間がかかり、破損のリスクも高いため、保険となる代替プランも立てつつ、なるべく予定通りに進められるよう心がけました。 1.姿勢推定 姿勢推定は様々なアプローチがあり、突き詰めれば良い性能が期待できますが、短期間のインターンシップのため、姿勢データが直接得られるジャイロセンサBWT901CLを選定しました。BWT901CLは、カルマンフィルタが実装されている9軸センサであり、クォータニオンの姿勢データを200Hzで取得できます。また、バッテリーが内蔵されている他、BluetoothのSPP(Serial Port Profile)によりPCからキャリブレーションや取得データの設定が可能です。 ジャイロセンサの取り付けについては、ロボットにある程度しっかりと固定するため、新しくロボットのカメラとジャイロセンサを取り付けるための治具を設計して3Dプリントしました。テレプレゼンスPJのオフィスに3Dプリンタがあったため、設計データを自宅から送り、社員の方に印刷していただいて、うまく取り付けられるかチェックしてを繰り返しての実装となりました。 2.ネットワーク通信 最終的に SkyWay WebRTC Gateway を用いてインターネット上で転送することを考え、 得られた姿勢データは、UDP通信によりロボットからクライアントPCへ送信できるようにしました。 本来であればこの姿勢データを映像と完全に同期させるため、タイムスタンプを用いた実装にする必要がありますが、 今回は試験的な実装ということで、遅延時間(≒エンコード時間)をキャリブレーションして手動入力することにしました。 3.射影変換 もともとHMDへの表示にはUnityが使われているので、単にUnity上でカメラに対して映像が投影された平面を回転させるだけで実装できます。 ただし、厳密に視野角やUnity上の映像の大きさを現実に合わせる必要があったりと細かい調整が求められました。 また、ジャイロセンサも含めて基本的にロボット制御は右手系ですが、Unityは左手系なので、ロボットとクライアントPC間の通信において相互に座標変換の必要がありました。 4.ロボット制御 当初は、3軸の各サーボモータの角度を取得し、カメラに取り付けたジャイロセンサの姿勢と組み合わせることで、ロボットの台座の姿勢を計算しようとしていました。 しかし実装してみると、サーボモータのAPI取得に27ms必要だと分かったため、60FPS出せない事が分かりました。 そこで急遽予定を変更し、ロボットの台座にカメラ側とは別でジャイロセンサを取り付けました。 そして、ジャイロセンサから得られたロボットの台座の姿勢と、ネットワーク経由で得られたHMDの目標姿勢から、サーボモータの目標となる角度計算の処理を実装しました。 最終的にセンサを取り付けて組み上がった、ロボットの写真は次の通りになります。 成果物 実装後に、HMD画面も含めて実機の映像を撮影し動画にしたものが次の通りになります。 www.youtube.com 動画の前半では、本体を揺らさずにHMDを様々な角度に動かし、射影変換による補正が効いていることを確認しています。 動画の後半では、社員の方にロボット本体を様々な形で揺らしてもらい、姿勢制御が機能していること、姿勢制御下でもHMDの姿勢変更を反映できることを確認しています。 ロボット本体を揺らされた場合にHMDの画面はほぼ動かなかったため、オペレータ視点だとどのように揺らされているか分からない程にしっかりと補正できていたことは非常に良かったと感じました。 また射影変換による補正については、今回は通信遅延が120msとそこまで大きくなく、さらにチューニングが不完全なため、効果が実感しにくかったです。 但し補正自体は想定通り正しく機能していました。チューニングをしっかりし、遅延によるズレの影響を小さくすれば、VR酔いの解決に大きく近づけられると感じられました。 www.youtube.com インターンシップを終えた感想 本当にあっという間で非常に密度の高い2週間でした。 今回の実装により、提案した方法を実際に形にして、映像補正によるVR酔い低減の実装例を示すことができたと感じています。 しかしやり残したことも多く、例えば位置に対する補正であったり、映像と姿勢の同期であったり、まだまだやらないといけない事が沢山あるなと思いました。 通信周りは初めての事が多かったですが、テレプレゼンス以外にも幅広く使えそうな知識が身についたので、色々使っていきたいと思います。 また今回はリモートでのロボット開発となったため、ハードウェア周りで難しい局面も多くありましたが、 社員の方がDiscord経由で常にサポートしていただける体制となっていたため、安心して取り組むことができました。 今回のインターンシップで得られた経験を今後の活動に様々な形で活かしていくことができればと思います。 NTTコミュニケーションズのテレプレゼンスPJの皆様、ヒューマンリソース部の皆様、本当にありがとうございました!  メンターからのコメント メンターを担当したイノベーションセンターの中蔵です。2週間のインターンシップお疲れ様でした。 今回取り組んでもらったVR酔いの緩和は、単にテレプレゼンスロボットを動かす際に直面する課題というだけではなく、 多くの領域の研究者が長年取り組んでいる大きなテーマです。 短いインターン期間でできることにも限りがあるため、 当初はもっと簡単なゴールラインを設定していました。 しかし、折角やるなら本質的な解決に取り組みたいということで、自ら取組内容を企画・提案してくれました。 初めて触るセンサの挙動を把握した上で、ロボットの適切な位置に取り付けて姿勢情報を取得し、 Unity内の空間座標と照らし合わせながら描画位置を変更するという盛り沢山な内容です。 これを期間内に完成させるだけでなく、デモムービーの撮影まで完了させたのは流石の一言です。 この内容を完成させられたのは、高い技術力を発揮しただけではなく、チーム内の先輩社員とうまく連携できたのも大きいと思います。 要件の聞き取りや既成のロボットの構造把握のために受動的なコミュニケーションをとるだけではなく、 必要に応じて先輩社員に作業を依頼するなど、積極的に周囲を巻き込むことができていました。 今後何をする場合でも活かせる能力なので、ぜひこのまま伸ばしていってほしいと思います。 今回のインターンシップで得た経験が、今後の関根さんの研究や取り組みに役立てば幸いです。 改めて、インターンシップへの参加とご活躍ありがとうございました!
サマリ この記事は3社協同プロジェクトの紹介記事であり、ブログリレーの中編です configを頼りにL1トポロジをNetBox上で再現し、Batfishで解析できるようにしました 障害耐性を測るためにリンク障害を模擬したL1トポロジを自動生成しました Batfishの解析結果からL1/L2/L3の情報をモデルとして抽出し再利用可能にしました はじめに イノベーションセンターの田島です。主にサービスプロバイダ網の技術検証から検証用ASの設計・構築・運用まで担当しています。 この記事は沖縄オープンラボラトリ 1 で実施している、TIS株式会社/ビッグローブ株式会社/NTTコミュニケーションズ株式会社の3社協同プロジェクト「Model Driven Network DevOps PJ」 2 の内容を紹介します。詳細は後述しますが、このPJではトポロジをはじめとするNW構成情報を中心にした設計・構築・運用プロセスの検討とPoCの実施・評価を実施しています。 この記事では下記の図の強調部分を紹介します。前後の部分を分割し全体を3パートに分けて各社持ち回りで紹介するブログリレー形式をとっています。これに加えて前編後編もあわせてご覧いただくと全貌がより一層理解しやすくなります。 ブログリレーの前編:モデルベースなネットワーク自動化への挑戦/ビッグローブ株式会社 ブログリレーの中編:実ネットワークをモデルとして抽象化しオペレーションを高度化するチャレンジ(この記事) ブログリレーの後編:ネットワークのモデルベース検査と障害シミュレーションによる運用高度化への挑戦/TIS株式会社 プロジェクトの概要 我々のプロジェクトでは、ネットワークの複雑化に起因して発生する、人間では事前にネットワークの状態を把握しきれない問題に対処しようとしています。ネットワークの状態を把握できなくなると次のような影響が出ます。 ネットワークの運用、特にオンプレでは構築・変更に対する事前検証およびレビューが重要視される しかし規模の問題などで検証環境では不十分に模擬できない等の状況が発生し、人間の思考力に依存する箇所が発生する その結果レビューの品質が安定しないばかりか、実際にやってみて問題に直面するといった状況が発生しかねない これに対処するため、ネットワークを含めたシステム全体のあるべき姿としてモデルを定義し、モデルデータで各種の検証やテストを行えるソフトウェア群の実装を目指しています。計算機上にモデルを構築し実機との間で同期を取ることで様々なオペレーションの模擬が人間より高い精度で、広い網羅性で、短時間でできるようになると考えています。また、このモデルデータを使うことで1つの巨大なシステムでは無く各コンポーネントの責務が明確になった、対象の環境に合わせた拡張がしやすいソフトウェアを作れると考えています。図のような本番環境とモデルを相互に同期する仕組みを作り、下記のような機能の実現を目指しています。 設計ポリシのルールに各モデルデータが準拠しているのかのチェック シミュレーションによる設定変更やテスト実行 時系列のモデルデータの差分による構成や状態変化の可視化 モデルデータから検証環境の自動構築や機能検証実施 検証済みモデルデータの自動デプロイ 現段階ではネットワーク機器のconfigからモデルデータを構築し、障害試験を含む各種の疎通試験ができています。図中だとターゲットとなる本番環境からモデルを構築し、自動テストを行う部分のイメージです。モデル上だと全パターンの疎通試験を行うことができるので、単一障害点の存在を確実に検出できます。 モデルデータを用いた疎通試験に必要なコンポーネント ネットワーク機器から情報を取得し、モデルを作成し、そのモデルを用いて各種試験を模擬するためには大きく分けて下記の3ステップが必要です。 NW装置のコンフィグを自動定期取得するステップ configからL1/L2/L3に分けてモデルデータを作成するステップ ※この記事の範囲 モデルデータを評価するステップ この記事では2の部分を紹介します。1と3についてはそれぞれビッグローブ株式会社、TIS株式会社の記事をご確認ください。 モデルデータの作成 パート1であるビッグローブ株式会社の記事 によってconfigやstatusを取得できました。それらのconfigからL1/L2/L3の情報を復元していきます。方針としてはBatfish 3 でconfigからL2/L3の再構築ができるため、それ以外の部分を独自実装します。Batfish単体ではカバーできないのは次のような部分です。 2022/03現在、BatfishはIPv6に未対応 → IPv6関連を別途パースできるように開発中 (この記事では省略) L1トポロジは復元されない configから復元する リンク障害模擬・ノード障害模擬などの実験データの機械的な作成 L1トポロジをベースに別途生成する 再利用可能な形式での各レイヤーデータの保存 L1トポロジの復元 configを基にL1の接続状態を調べ、トポロジデータとして利用可能にしました。入力はconfigのテキスト群、出力はBatfish読み込み可能なlayer1_topology.json 4 です。中間生成物として、L1トポロジをデバイスとケーブルで表現したNetBoxインスタンスと、inet-henge 5 で表示可能なトポロジデータが作成されます。トポロジはグラフDBに入れて表現することもできますが、モデルデータの1つとして後々の変更チェックなどで活用できるように、専用UIが付いているNetBoxを使用しました。 レポジトリはこちらです。 github.com 下記のようなconfigを手がかりにします。 pushed_configs$ grep -B 2 description mddo_network/configs/RegionA-CE01_config.txt ! interface Ethernet1 description to_RegionA-PE01_ge-0/0/2 -- ! interface Ethernet3 description to_RegionA-CE02_Ethernet3 -- ! interface Ethernet4 description to_RegionA-CE02_Ethernet4 -- ! interface Ethernet5 description to_RegionA-Acc01_Ethernet1 -- ! interface Ethernet6 description to_RegionA-Acc01_Ethernet2 -- ! interface Ethernet7 description to_RegionA-Acc01_Ethernet5 -- ! interface Ethernet8 description to_RegionA-Acc01_Ethernet6 上記のdescriptionを基にしてNetBoxへDevicesやInterfacesを登録します。 NetBoxのデータからしたL1トポロジの例です。 pushed_configs$ cat mddo_network/layer1_topology.json | jq . | head { "edges": [ { "node1": { "hostname": "RegionA-Acc01", "interfaceName": "Ethernet1" }, "node2": { "hostname": "RegionA-CE01", "interfaceName": "Ethernet5" 障害模擬データの作成 全パターンの単一障害を模擬するために、一部が欠損したBatfishへの入力データを作成します。与えられたL1トポロジからリンクを除きsnapshotとして登録します。除くリンクの記述は runtime_data.json 6 で行いました。また、後から参照できるようにどのリンクを除いたかを含めたsnapshotのメタデータをsnapshot_info.jsonとして保存します。 レポジトリはこちらです。 github.com 障害模擬として除くリンクは下記のように表現されます。 playground$ cat ./configs/pushed_configs_linkdown/mddo_network_01/batfish/runtime_data.json | jq . { "runtimeData": { "RegionA-Acc01": { "interfaces": { "Ethernet1": { "lineUp": false } } }, "RegionA-CE01": { "interfaces": { "Ethernet5": { "lineUp": false } } } } } モデルデータの生成 ここまででL1トポロジデータとBatfishによるL2/L3のデータが準備できました。これらをまとめてモデルデータとして生成します。データの表現方法は再利用性を考え RFC8345 7 を参考にした記述としました。RFC8345形式のデータはデータ構造が決まっていることで他のソフトウェアからもアクセスしやすいと考えられます。実際にブラウザ上で表示できるnetovizがあります。 データの生成は、L1トポロジはlayer1_topology.jsonのまま、Batfishのデータは一度csvに出力し、それらを読み込み再構築します。モデルデータの再構築をする中でインターフェースの齟齬やネットワークプレフィックスの差異など、正常に再構築できないデータ、つまり設定の誤りを検出できます。 レポジトリはこちらです。 モデルデータ生成 github.com RFC8345データの可視化 github.com Batfishのデータをcsvで出力すると、例えばIPアドレスとインターフェースの対応付けであれば下記の結果が得られます。 playground$ cat models/pushed_configs/mddo_network/ip_owners.csv | head ,Node,VRF,Interface,IP,Mask,Active 0,regionb-svr01,default,enp1s5,172.31.110.100,24,True 1,regionb-pe02,default,ge-0/0/4.0,10.1.2.2,30,True 2,regionb-svr02,default,enp1s4,172.31.20.100,24,True 各データを再構築したモデルデータは下記のような構造になります。 playground$ cat netoviz_model/pushed_configs_mddo_network.json | jq . | head -n 20 { "ietf-network:networks": { "network": [ { "network-id": "layer3exp", "network-types": { "mddo-topology:l3-network": {} }, "node": [ { "node-id": "regiona-ce01", "ietf-network-topology:termination-point": [ { "tp-id": "Vlan10#1", "supporting-termination-point": [ { "network-ref": "layer2", "node-ref": "regiona-ce01_Vlan10", "tp-ref": "Port-Channel2" } このデータはnetovizで各レイヤーごとに可視化できます。 データ操作のためのWeb UIサンプル データをもとにシミュレート操作を行うためにはJupyter Notebook等を利用できますが、決まった作業であればWeb API等を介してWeb UIを用意すると操作が簡便です。しかし、BatfishではPython実装のクライアントライブラリであるpybatfishのみの提供で、そのままではフロントエンド系のフレームワークとは相性が良くないです。そこでBatfishにWeb APIのラッパをかぶせることで、Batfish内部のコマンドを隠蔽し、可視化部分を開発しやすくしました。 ここではtracerouteを実行するWeb UIの実装例を紹介します。 batfish-wrapper BatfishにWeb API経由でアクセスできるラッパです。pybatfishをflask内で使用しています。Batfishの概念であるnetwork/snapshotに応じてURLを構成できるようにしています。 レポジトリはこちらです。 github.com Batfishに現在登録されているnetworkやsnapshotやinterfaceの一覧を取る例は次のような一連のAPIです。 $ curl -s http://192.168.23.31:15000/api/networks | jq . [ "batfish-test-topology", "pushed_configs", "pushed_configs_drawoff", "pushed_configs_linkdown" ] $ curl -s http://192.168.23.31:15000/api/networks/pushed_configs/snapshots | jq . [ "mddo_network" ] $ curl -s http://192.168.23.31:15000/api/networks/pushed_configs/snapshots/mddo_network/interfaces | jq . | head -n 20 [ { "addresses": [], "interface": "ae1", "node": "regionb-ce01" }, { "addresses": [], "interface": "Ethernet10", "node": "regiona-acc01" }, { "addresses": [ "172.16.5.1" ], "interface": "Port-Channel1", "node": "regiona-ce01" }, { "addresses": [], fish-tracer インターフェースを指定して、単数もしくは複数のsnapshotに対してtracerouteのシミュレート結果を表示できるWeb UIです。モデルデータをそのまま見て解析するのはやはり見づらく、開発時にも簡単に試せるこのUIがあって便利でした。NuxtJSで上記batfish-wrapperを呼んでいます。 レポジトリはこちらです。 github.com 下記のように複数snapshotに対してのtracerouteをグラフィカルに実行できます。 まとめ この記事では「Model Driven Network DevOps PJ」の概要紹介、configからモデルデータを作成するステップを解説しました。configからL1を復元し、障害模擬トポロジを機械生成し、BatfishでL2/L3情報も復元し、モデルデータとして出力しました。 本文中にもありますが、現在のシステムではまだまだモデルの利点を活かしきれる実装ではないため、引き続きモデルを活用できる実装を増やしています。取り組みにご興味ある方は 沖縄オープンラボラトリ までご連絡ください。 ブログリレーの次パート(TIS株式会社) に続きます。 次世代ICT基盤技術の実用化と普及を目指す研究機関。 https://www.okinawaopenlabs.org/ ↩ プロジェクトの紹介および2021年度の活動紹介資料のページ。 https://www.okinawaopenlabs.org/mdnd ↩ オープンソースのconfig解析ソフト。 https://www.batfish.org/ ↩ L1トポロジを表現する、Batfishの追加入力データの1つ。 https://batfish.readthedocs.io/en/latest/formats.html#layer-1-topology ↩ ブラウザによるネットワーク図生成フレームワーク。 https://github.com/codeout/inet-henge ↩ インターフェースの状態を示すBatfishの追加入力データの1つ。なお2022/03時点ではサンプルのデータ構造と実際に正しく読み込まれるデータ構造が違うので注意が必要。 https://batfish.readthedocs.io/en/latest/formats.html#runtime-interface-information ↩ ネットワークトポロジを表現するためのYANGデータモデルの定義。 https://datatracker.ietf.org/doc/html/rfc8345 ↩
はじめに こんにちは。プラットフォームサービス本部 データプラットフォームサービス部でSmart Data Platform(SDPF)のサービス企画を行っている安井・小野です。 閲覧頂きありがとうございます。我々は当社が提供しているSDPFサービスを組合わせた具体的な事例紹介をしています。 前回の 第2回 では、1点目としてオンライン化が広がる中でのバックアップの重要性(Micosoft365の安全なバックアップ)について、 例えばSDPFサービスを使った場合にはどのように実現できるのかについてご紹介しました。 2点目として現在社会問題になっているランサムウェア対策については、データ保存の観点で改ざん防止に対応したWasabiのオブジェクトロック機能を使用することで、 万一不幸にしてランサムウェア被害にあった際にも、安全なデータからシステムやシステムデータを復旧する方法について説明しました。 このように誰もが気軽にオンライン化されたサービスを容易に利用できる反面、外部から時間を問わずいつでもあらゆる側面からアクセスされるかもしれないという状態にもあり、 「自分達の資産は自分達で守る」取組みが一層求められています。 自社の情報資産を明確化し、適切な権限設定がされており、許可されたものだけが使用可能な状態を保つ。 それらを使用した形跡を記録化し、定期的に確認する。かつ各種のアクセス情報はログ情報として一定期間、保存する。 万一の問題発生時には、ログ情報を元に分析し、原因の追究、対策を実施する。 背景 オンライン化/リモート化に伴い、オフィス外からのリモートアクセスの利用が進む 時間と場所に制約を受けない働き方が進んできており、そのニーズの高まりから、SDPFネットワークサービスメニューの1つであるFlexible Remote Accessの利用が進んでいます。 Flexible Remote Access (FRA) とは外出先や自宅等あらゆる場所からセキュアにアクセスできるリモートアクセス&セキュリティ基盤です。 ログを取っていなかったら何も分からない!? 「適切な権限設定」に基づき「許可されたユーザー」がアクセスし「どのようなアクセスを行ったのか」、 「不正な通信をした形跡はないのか、または恐れがないのか」等、総合的なセキュリティ対策を実施して運用する必要があります。 その中でも「通信アクセスに関するログ情報」は重要なデータであり、リアルタイム分析をして対応する形態や、 万一の問題発生時に過去のログを参照して分析したり、活動証跡といった監査目的のためにログを保存しておく形態等があります。 SDPFサービスを利用してどのように実現したか 以下NTT Comのサービスを組み合わせて、FRAを使用して発生するログ情報を、クラウド上に長期間保存できる方法を実現しました。 閉域網から各種クラウドサービスに接続するインターコネクトサービス「 Flexible InterConnect (FIC) 」を使ったセキュアな通信経路の確立。 安価なストレージサービス「 Wasabiオブジェクトストレージ(Wasabi) 」の利用。 SDPFクラウド/サーバーのサーバーインスタンスを利用。 検証を通じた確認 実際に検証環境を作ってみた 今回は図のような検証環境を構築しました。特徴は以下となります。 FICを活用することにより、 FRAはもちろんのこと、WasabiやSDPFクラウド/サーバー等様々なサービスと閉域で接続可能な状態にしました。 ログ受信サーバーはSDPFクラウド/サーバーのサーバーインスタンス上に導入しました。 実際に検証を実施してみた FRAから発生するトラフィックログや脅威ログ、URLフィルタリングログといった各種ログをFIC経由でSDPFクラウド/サーバー上のログ受信サーバーに転送し、 別途作成したシェルスクリプトを用いることにより、日ごとにまとめたzipファイルを同サーバーのローカルディスクやWasabiに長期保存する検証をしました。 確認後の具体的な気付きポイント FRAには0系と1系の環境があり、それぞれのポータル上にて手動で設定する必要がありました。 ログ受信サーバーにはOSSであるFluentdを用いました。 Fluentdでは、Fluentd内の"タグ"に設定したキーワードを元にログを処理することが出来ます。 また、FRAでは送信するログのファシリティをそれぞれのsyslogサーバープロファイルごとに設定出来ます。 これらの機能を組み合わせることにより、例えばトラフィックログであれば「LOG_LOCAL0」を、脅威ログであれば「LOG_LOCAL1」をFRA側で設定します。 そして、Fluentd側では、0系および1系のホスト情報をタグに設定することにより、"タグ.ファシリティ"別にログの保存先ファイルを分けることが出来ました。 下記はFluentdの設定ファイルです。トラフィックログと脅威ログに関する設定のみ抜粋していますが、他のログも同様となります。 /etc/td-agent/td-agent.conf ###################################### # FRAからのsyslog受信 # ###################################### <source> @type syslog port 514 bind 0.0.0.0 tag FRA <transport tcp> </transport> <parse> message_format auto with_priority true </parse> </source> ###################################### # 受信ログをサーバー別にタグで分類 # ###################################### <match FRA.**> @type rewrite_tag_filter # 0系用タグ <rule> key host pattern 0系のホスト名 tag "0系のホスト名.${tag}" </rule> # 1系用タグ <rule> key host pattern 1系のホスト名 tag "1系のホスト名.${tag}" </rule> </match> ######################################################## # サーバー+ファシリティ別にログファイルへログを書き込み # ######################################################## # 0系 # トラフィックログ <match 0系のホスト名.FRA.local0.**> @type file path /var/log/FRA/FRA.0系のホスト名.traffic.*.log append true <buffer> timekey 1d # 1day timekey_wait 10m # 10mins deley for flush timekey_use_utc false # local </buffer> </match> # 脅威ログ <match 0系のホスト名.FRA.local1.**> @type file path /var/log/FRA/FRA.0系のホスト名.threat.*.log append true <buffer> timekey 1d # 1day timekey_wait 10m # 10mins deley for flush timekey_use_utc false # local </buffer> </match> ~ 途中割愛 ~ # 1系 # トラフィックログ <match 1系のホスト名.FRA.local0.**> @type file path /var/log/FRA/FRA.1系のホスト名.traffic.*.log append true <buffer> timekey 1d # 1day timekey_wait 10m # 10mins deley for flush timekey_use_utc false # local </buffer> </match> # 脅威ログ <match 1系のホスト名.FRA.local1.**> @type file path /var/log/FRA/FRA.1系のホスト名.threat.*.log append true <buffer> timekey 1d # 1day timekey_wait 10m # 10mins deley for flush timekey_use_utc false # local </buffer> </match> ~ 以降割愛 ~ FRAから送られてくるすべての種類のログを1ファイルにすることも可能ですが、ログの量が多くなり、視認性が下がってしまいます。 そのため、監査目的の観点からもこの機能の組み合わせは有効性が高いと感じました。 今回の検証では通常のWasabiバケットを利用しました。今後Wasabiではライフサイクルポリシー機能が追加されるという情報を得ているため、 機能実装後はランサムウェア対策としての観点からも有効なバケットに対して転送する確認をしていきます。 また、今後はSTEP2として、総合リスクマネジメントサービスの WideAngle との連携を検討していく予定です。 具体的な構成例や設定例の見える化 具体的な構成例や設定例、注意点等の知見についてはKnowledge Centerにて記載しておりますのでご確認ください。記載している情報は2022年3月時点の情報となります。 Knowledge Center 複合ソリューション構成ガイド FRAログ保存ソリューション 最後に 今後もクラウドへのデータ保存やデータ利活用を気軽に活用できるきっかけを活動を通じて発信していきます。 「ここの記載がわかりにくい」等のご意見や、「SDPFでこんなことができないか」等の疑問がありましたら、以下メールアドレスに対してお気軽にコメント等ご連絡ください。 sdpf-testbed-02@ntt.comにメールを送る ※お手数ですが@を全角文字から半角文字に置き換えてください。 ありがとうございました。
目次 はじめに インターンシップ参加にあたって 体験内容 IoT Connect GatewayとStorage転送機能についての理解 新サービスの提案 開発環境 カメラの開発 IoT Connect Gatewayの設定 Amazon Web Services上での画像認識 画像認識結果の可視化 インターンシップを終えた感想 トレーナーからのコメント はじめに こんにちは。この度、NTTコミュニケーションズの職業体験型インターンシップのエンジニアコースに参加させていただきました小林と申します。大学では情報科学を専攻しています。 趣味は折り紙・ゲーム・アニメなどで、特に折り紙は小さい頃から親しんできました。サークル活動としてはロボコンサークルに所属し、NHK学生ロボコンへの出場を目指してロボット製作をしています。 今回はNTTコミュニケーションズのインターンシップで、IoT Connect Gatewayというサービスを開発しているチームに参加いたしました。 インターンシップではIoT Connect Gatewayの検討/開発中の新規機能を使い、サービスの提案・開発をする機会をいただいたので、その体験談をまとめます。よろしくお願いいたします。 この記事を作成するにあたって、夏のインターン生の方の記事を参考にさせていただきました。 IoT Connect Gatewayを使ってみた 番外編 ~インターンシップでリリース前の機能を使って開発してみた~ インターンシップ参加にあたって 私は大学の授業で情報科学を学んでいるのですが、まだ2年ということもあって、学んでいることがどのように仕事の役に立つのかというイメージがもてませんでした。 そこで現場で本当に必要とされている能力は何かを実感できれば、大学での学びをより有意義なものにできるのではと考え、インターンシップに応募しました。 またサークルでのロボット製作活動を通して、得意分野や性格がそれぞれ異なる集団のなかでいかに自身の能力を発揮するか、円滑なコミュニケーションができる環境をどのようにつくるかについて考える機会があったため、チームでの開発にも興味がありました。 体験内容 2週間で体験したインターンシップの内容をまとめます。 1. IoT Connect GatewayとStorage転送機能の理解 1.1 IoT Connect Gatewayの概要 まず、本チームの開発サービスであるIoT Connect Gateway (ICGW)について教わりました。 図1 IoT Connect Gatewayの概要図 ICGWは以下の2つの機能を主軸としています。 プロトコル変換機能 IoTデバイスが送信するデータを暗号化 クラウドアダプタ機能 接続情報や鍵の交換を代理で実行 これらの機能により、デバイス側の負荷や暗号化設定の労力を増やすことなくセキュアな通信ができるうえ、クラウド側のインターフェースの仕様変更があった場合にも、ユーザによるデバイス側の操作が不要になるということを教わりました。 1.2 Storage転送機能の概要 次に、ICGWの検討/開発中の新規機能であるStorage転送機能についても教えていただきました。 図2 Storage転送機能の概要図 Storage転送機能はIoTデバイスからクラウドストレージへのデータを転送する機能として開発され、以下の4つの特徴を備えています。 セキュアプロトコル変換 HTTPメソッドでの基本的なオペレーション URLのパスを利用した柔軟なオブジェクト操作 カスタムメタデータの付与 これらの特徴により、IoTデバイスからのセキュアなファイルのアップロードが容易に実現できます。 なかでも、私が注目したのは カスタムメタデータの付与 ができることです。 カスタムメタデータとは、ICGWが保持しているIMSIやIMEI、デバイス名、日時といった情報のことを指します。例えば、この機能を用いてアップロードするファイル名に日時を含めることで、予期せぬ上書きを防ぐとともに送信時間を把握しやすくなります。 実際に私が提案したサービスにもこの機能を使用しました。 詳しくは 5. IoT Connect Gatewayの設定 と 6. Amazon Web Services上での画像認識 をご覧ください。 2. 新サービスの提案 インターン中では、"ICGWの「Storage転送機能」を利用した新サービスを考える"という課題に挑戦させていただくことになりました。 以下が、私の提案したサービス概要となります。 図3 提案したサービスの概要 今回考案した遠隔監視システムは大きく3つの特徴があります。 1つ目は、需要の高いシステムであるということです。昨今のCovid-19の流行や労働者不足により、少人数で広範囲をリアルタイムで監視できるシステムはあらゆる分野で求められていると考えられます。 2つ目は、通信量を少なく抑えられるような設計になっていることです。動体を検出したときの画像のみを送信する仕組みとなっているため、そのまま動画を送るより圧倒的に少ない通信量で運用できます。 3つ目は、他の用途に応用しやすいことです。動体検出を利用しているため監視だけでなく、交通量・歩行量調査や生態系の調査といった研究の分野での使用も想定できます。 3. 開発環境 システムの開発環境は以下の通りです。 機材、端末 MacBookPro(13-inch, M1, RAM 16GB) RaspberryPi Zero W V1.1 RaspberryPi Camera Rev 1.3 無線LANルータ(NEC Aterm MP02LN CW) 利用サービス IoT Connect Gateway Dashboard Amazon Simple Storage Service (S3) Amazon Web Services Lambda Amazon Rekognition Amazon CloudWatch 4. カメラの開発 RaspberryPiとRaspberryPi Cameraを用いて、動体検知のためのカメラを開発しました。 図4 実際に使用したRaspberryPiとRaspberryPi Camera 4.1 動体検出および画像抽出 RaspberryPiにインストールしたMotionというソフトウェアを使って、監視対象をモニタリングし、動体検出と画像の抽出をしました。 参考: https://www.handsonplus.com/electronic-works/how-to-use-motion-with-raspi/ 今回は被写体として私の弟に協力してもらいました。 図5 モニタリング中の映像と動体検出した画像 4.2 画像データの送信 続いて、Pythonを使って各画像ファイルのデータを送信します。 参考: https://www.handsonplus.com/electronic-works/how-to-use-motion-with-raspi/ 実際に使用したPythonのコードは以下の通りです。 import os import time from glob import glob # 画像送信 def send_img (attachments): # ファイルを送信 for attachment in attachments: os.system( "curl -v -X PUT -H 'Content-Type: image/jpeg' --data-binary @" \ + attachment \ + " http://***:8081/s3" ) # メイン関数 def main (): while ( True ): # 画像ファイルを探す attachments = sorted (glob( './*.jpg' )) # 添付ファイル確認 if len (attachments) >= 1 : # 画像送信 send_img(attachments) time.sleep( 10 ) # 送信後ファイル削除 os.system( 'sudo rm *.jpg' ) time.sleep( 10 ) main() jpg形式の各画像ファイルを読み込み、それぞれに対してcurlコマンドを実行することで送信の処理を行います。 IoTデバイスのストレージを圧迫しないように、送信し終わったファイルは削除します。 5. IoT Connect Gatewayの設定 次に、AWS S3にデータを送信するためのStorage転送機能の設定をICGWのコンソール画面から行いました。 以下が実際に投入した設定になります。 図6 Storage転送機能の設定 今回はFilepathに"$YYYY"や"$kk"というプレースホルダーを設定しています。これによりデータが送信された際に、ICGWの機能によってその時の日時データを埋め込めるようになっています。 この機能を活用することで、動体を検出した時間が明記されるだけでなく、ICGW側で一元的に日時データを管理ができます。 6. Amazon Web Services上での画像認識 次に、AWS上で画像認識処理をできるように設定しました。 今回はS3・Lambda・Rekognition・CloudWatchの4つの機能を連携させました。 6.1 S3上のファイル S3とは、AWSのクラウドストレージサービスです。 図7 S3上のファイル まずIoTデバイスからICGW経由でS3に画像ファイルが送られてきます。送信されたファイル名は、 5. IoT Connect Gatewayの設定 で設定したように送信日時となっていることが確認できます。 6.2 LambdaとRekognitionによる画像認識処理 S3に送られた画像は、LambdaとRekognitionによって画像認識処理されます。 Lambdaとは、サーバーレスでプログラムを実行できるサービスです。 図8 Lambdaでの処理 今回使用したLambdaは、トリガーをS3の送信先のディレクトリに設定しているので、画像が送られてくるとLambda上の関数が自動で実行されるようになっています。 関数の処理の流れは上記のフローチャートの通りです。 Lambdaでは送られてきた画像に対して、Rekognitionを用いて画像を解析した後に結果を出力します。 参考: https://acro-engineer.hatenablog.com/entry/2018/12/12/120000 以下が、実際に使用したLambda上の関数のソースコードです。 import os import boto3 s3 = boto3.resource( 's3' ) def lambda_handler (event, content): # S3にアップされた画像の情報を取得する。 bucket_name = event[ 'Records' ][ 0 ][ 's3' ][ 'bucket' ][ 'name' ] object_key = event[ 'Records' ][ 0 ][ 's3' ][ 'object' ][ 'key' ] file_name = os.path.basename(object_key) print ( "bucket_name=" + bucket_name) print ( "object_key=" + object_key) print ( "file_name=" + file_name) # 画像をRekognitionで解析する。 rekognition = boto3.client( 'rekognition' ) response = rekognition.detect_labels(Image={ 'S3Object' : { 'Bucket' : bucket_name, 'Name' : object_key } }) print (response) 6.3 CloudWatchでの画像認識結果の確認 画像認識の結果はCloudWatchで確認できます。 図9 CloudWatch上の画像認識結果 結果はログとして表示されるため少しわかりにくいですが、ラベル・確信度・対象物の頂点座標などが出力されます。 上記のログは一つの画像に対する画像認識結果です。 赤枠で囲まれた所には、"ラベルがPersonでその確信度が約97.4%"であるオブジェクトを認識できた旨が書かれています。 つまり、動体の正体として人間の存在を認識したことを意味します。 7. 画像認識結果の可視化 画像認識結果がログのままではわかりにくかったので、可視化してみました。 図10 画像認識結果の可視化 本来はLambda上で可視化処理まで行おうと考えていましたが、時間が足りなかったため、手元の検証端末にインストールしたOpenCVを用いました。 参考: https://acro-engineer.hatenablog.com/entry/2018/12/12/120000 以下に、可視化処理で使用したプログラムのソースコードを示します。 from urllib import response import cv2 # 加工するファイルの名前を代入 filename = '2022-23-123902.jpg' # CloudWatchでの画像認識結果を代入 response = { 'Labels' : [{ 'Name' : 'Clothing' , 'Confidence' : 99.97942352294922 , 'Instances' : [], 'Parents' : []}, { 'Name' : 'Apparel' , 'Confidence' : 99.97942352294922 , 'Instances' : [], 'Parents' : []}, { 'Name' : 'Suit' , 'Confidence' : 99.97110748291016 , 'Instances' : [], 'Parents' : [{ 'Name' : 'Overcoat' }, { 'Name' : 'Coat' }, { 'Name' : 'Clothing' }]}, { 'Name' : 'Overcoat' , 'Confidence' : 99.97110748291016 , 'Instances' : [{ 'BoundingBox' : { 'Width' : 0.47841206192970276 , 'Height' : 0.8942648768424988 , 'Left' : 0.18658344447612762 , 'Top' : 0.05319703370332718 }, 'Confidence' : 81.37818908691406 }], 'Parents' : [{ 'Name' : 'Coat' }, { 'Name' : 'Clothing' }]}, { 'Name' : 'Coat' , 'Confidence' : 99.97110748291016 , 'Instances' : [], 'Parents' : [{ 'Name' : 'Clothing' }]}, { 'Name' : 'Person' , 'Confidence' : 98.30304718017578 , 'Instances' : [{ 'BoundingBox' : { 'Width' : 0.42601126432418823 , 'Height' : 0.9695423245429993 , 'Left' : 0.21858000755310059 , 'Top' : 0.01187931653112173 }, 'Confidence' : 79.02320861816406 }], 'Parents' : []}, { 'Name' : 'Human' , 'Confidence' : 98.30304718017578 , 'Instances' : [], 'Parents' : []}, { 'Name' : 'Man' , 'Confidence' : 96.02997589111328 , 'Instances' : [], 'Parents' : [{ 'Name' : 'Person' }]}, { 'Name' : 'Standing' , 'Confidence' : 79.08413696289062 , 'Instances' : [], 'Parents' : [{ 'Name' : 'Person' }]}, { 'Name' : 'Face' , 'Confidence' : 64.02348327636719 , 'Instances' : [], 'Parents' : [{ 'Name' : 'Person' }]}, { 'Name' : 'Tuxedo' , 'Confidence' : 56.30610656738281 , 'Instances' : [], 'Parents' : [{ 'Name' : 'Suit' }, { 'Name' : 'Overcoat' }, { 'Name' : 'Coat' }, { 'Name' : 'Clothing' }]}], 'LabelModelVersion' : '2.0' , 'ResponseMetadata' : { 'RequestId' : '***' , 'HTTPStatusCode' : 200 , 'HTTPHeaders' : { 'x-amzn-requestid' : '***' , 'content-type' : 'application/x-amz-json-1.1' , 'content-length' : '1427' , 'date' : 'Wed, 23 Feb 2022 12:39:04 GMT' }, 'RetryAttempts' : 0 }} # CV2で画像ファイルを読み込む。 np_image = cv2.imread(filename) height, width = np_image.shape[: 2 ] # ラベルの中から人と思われるものを探して四角で囲う。 for label in response[ 'Labels' ]: if label[ 'Name' ] not in [ 'People' , 'Person' , 'Human' ]: continue for person in label[ 'Instances' ]: box = person[ 'BoundingBox' ] x = round (width * box[ 'Left' ]) y = round (height * box[ 'Top' ]) w = round (width * box[ 'Width' ]) h = round (height * box[ 'Height' ]) cv2.rectangle(np_image, (x, y), (x + w, y + h), ( 0 , 0 , 255 ), 3 ) cv2.putText(np_image, label[ 'Name' ], (x, y - 9 ), cv2.FONT_HERSHEY_SIMPLEX, 1.5 , ( 255 , 255 , 255 ), 3 ) cv2.imwrite(filename + '_result.jpeg' , np_image) CloudWatchの出力ログから人間を表すラベルの頂点座標を抜き出し、その情報を基に作成したバウンディングボックスを元画像に貼りつけるという流れの処理を行いました。 インターンシップを終えた感想 最後に、本インターンシップを通して感じたことをまとめます。 1. 自力で調べる力の重要性を再確認できた RaspberryPiやICGW、AWS、Macは私にとって触れたことすらないものであり、2週間という短い期間でそれらすべてを扱うためには、自分で自発的に調べることがとても重要だと改めて実感しました。 また、特にIT業界は短いスパンで新しい技術が登場する変化の目まぐるしい業界であることからも、自力で調べる力は常に意識して伸ばしていきたいと思いました。 2. 大学での学びに対するモチベーションが得られた インターンシップに参加するまでは、大学で学んだことがどのように仕事に活かされるのかイメージできませんでした。 特にサークルでのロボット製作は、"あくまでサークル活動でお遊びである"という意識があり、自分がしてきた活動に自信を持てないことがありました。 しかし、実際に業務内容を経験させていただいたことで、大学での自分の活動が確かに仕事に結びつくと実感でき自信につながったとともに、これからの大学での学びに対する絶大なモチベーションを獲得できたと感じています。 3. チーム開発における"良い雰囲気"を実感できた インターンシップに参加することが決まったとき、技術面・経験面でも未熟な大学2年の自分が、社員の方々にご迷惑をかけずにしっかり業務をこなせるのか非常に心配でした。しかし、チームメンバーの方々は緊張して硬くなった私をアットホームな雰囲気で暖かく迎えてくださったおかげで、楽しく仕事ができました。 特に、私が自力ではどうしても解決できない問題に直面した際、気兼ねなく質問できるようにしてくださいました。 ICGW開発チームの方々からは、技術だけでなく、チーム開発をするうえで大切なチーム内の雰囲気がどのようなものであるかを身をもって学ばせていただきました。 2週間という短い間でしたが大変お世話になりました。ありがとうございました! トレーナーからのコメント 小林さんのトレーナーを務めたICGWチームの岩田です。 今回のインターンシップでは、ICGWのStorage転送機能を使って新規サービス提案に取り組んでいただきました。RaspberryPiやICGW、AWSといった小林さんにとってあまり馴染みのない技術領域が多かったかもしれないですが、その中でも自分なりのウィルを持って取り組む姿勢は印象的でした。また、大学2年生ながらもすでに大学での勉学の意義や将来を見据えており、素晴らしい姿勢だと感心しました。今後もその姿勢を大切に、学業やロボコンに打ち込んでいただけたらと思います。このインターンで得られた経験を今後の小林さんの大学生活に役立ててもらえると我々も嬉しいです! 2週間お疲れ様でした!
はじめに はじめまして。イノベーションセンター所属の @sublimer です。 2021年4月に新卒として入社し、現在はWebRTCプラットフォーム 「SkyWay」 の開発・運用の業務に取り組んでいます。 また、個人開発としてWebアプリケーションを作ったり、TURNサーバー(RFC5766)をC#で実装したりしています。 他にも、自宅サーバを運用したり、ネットワーク機器を触ってみたりもしています。 ちなみに推しRFCは RFC1149 です。 今回は、現在 SkyWay Beta として提供中の新しいSkyWayのサービス監視機能を構築した話を紹介します。 SkyWayのサービス監視における課題 まずはじめに、現行のSkyWayが抱えていたサービス監視の課題について説明します。 現在のサービス監視には、以下の3つの課題がありました。 VMを管理するコスト 障害発生時の架電 ログ調査の手間 1. VMを管理するコスト 現行のSkyWayでは、管理用のVM上で Zabbix を動かしてサービス監視を行っています。 Zabbixは非常に優れた監視用ソフトウェアですが、VMを管理する手間がかかるほか、監視サーバーのメンテナンスをしている間はサービスの監視ができないという課題がありました。 2. 障害発生時の架電 現行のSkyWayでは、社内の監視チームが障害時の状況把握や関係者への連絡といった一次対応などを行っていました。 新しいSkyWayでは、人に頼らず電話をかける仕組みを導入し、障害発生時の対応を自動化・省力化することとなりました。 Zabbixには、障害発生時に電話をかけるような機能は無いため、何らかのSaaSと連携させる必要がありました。 3. ログ調査の手間 SkyWayのアクセスログ・アプリケーションログは、 Fluentd を用いて Google Cloud Storage(GCS) にアップロードされる仕組みとなっています。 ログ調査を行う際は、GCSから該当のログをダウンロードし、手元のマシンでgrepして調査しています。 調査の際は複数のVMのログを横断的に調査したい場合もあり、その都度ログをダウンロードして調査するのはかなり手間がかかっていました。 以上の課題を踏まえ、いくつかのSaaSやOSSを比較検討した結果、 Datadog 、 PagerDuty 、 Google Cloud Logging を用いて、新しいSkyWayのサービス監視を行うことにしました。 使用した各SaaSの役割 今回使用した各SaaSについて、その役割を簡単に説明します。 Datadog VMのCPU使用率やメモリ使用量といった時系列の数値データ、「メトリクス」を収集します。 また、事前に設定した条件を満たした場合はアラートとして通知します。 メトリクスは、VMにインストールしたDatadog Agentから定期的に送信されるほか、Web APIを呼び出すことで、任意のデータを記録することもできます。 PagerDuty DatadogやGoogle Cloud Loggingと連携させ、アラートの内容をモバイルアプリや電話で通知します。 予めオンコールシフトやエスカレーションポリシーを設定することで、柔軟な通知が実現できます。 また、誰が障害対応したのか、どういった対応をしたのかといった情報を記録でき、障害対応の振り返りに役立てることもできます。 Google Cloud Logging ログを集約・転送するソフトウェアであるFluentdから送られたアプリケーションログやアクセスログを記録します。 ログを構造化しておくことで、Google Cloud Loggingで柔軟にフィルタリング・検索ができます。 また、複数のVMのログを一箇所に集約できるため、横断的にログを検索できます。 構築したサービス監視の全体像 アラート通知のフローは、以下の図のように設定しています。 もし、メトリクスやログの内容に異常があれば、PagerDutyにアラートの情報が送られ、モバイルアプリや電話経由で通知が行われます。 基本的には、まずアプリ経由で通知し、一定時間アクションがない場合に電話をかけるようにしています。 一方で、サービスの運用に致命的な影響を与えるアラートに関しては、即座に電話をかけるように設定しています。 これにより、DevOpsチームが即座に障害の対応ができるようになりました。 また、メトリクスのグラフを共有するために、DatadogからSlackへの通知も設定しています。 以前はZabbixのスクリーンショットを手動で撮影してSlackに共有していましたが、DatadogとSlackの連携設定により、 スラッシュコマンド などで手軽にグラフ画像の共有ができるようになりました。 さらに、メトリクスとアラートの情報を紐付けて表示するダッシュボードを作ることで、障害時の各種メトリクスの状態を確認しやすくなりました。 DatadogのカスタムAgentチェックを効率的に開発する 今回Datadogを導入した際に得られたtipsを共有します。 Datadogでは、デフォルトでCPU使用率やメモリ使用量などのメトリクスを送信できますが、実際のサービス監視を行う際は、これらのメトリクスだけではカバーしきれない場合があります。 その場合は、カスタムAgentチェックのスクリプトを作成することで、任意の値をメトリクスとして送信できます。 カスタムAgentチェック用のスクリプトの動作確認にはDatadog Agentをインストールする必要があるのですが、このインストールの手間を減らすため、 Docker Composeを用いて動作確認ができる仕組みを作りました。 以下のコマンドでPythonの仮想環境を作成し、カスタムAgentチェックのスクリプトを ./src/custom_example.py に、設定ファイルを ./conf.d/custom_example.yml 記述します。 $ python -m venv venv $ . ./venv/bin/activate $ pip install datadog-checks-base ./src/custom_example.py import random from datadog_checks.base import AgentCheck __version__ = "1.0.0" class CustomExample (AgentCheck): def check (self, instance): self.gauge( "custom_example.value" , random.randint( 0 , 10 ), tags=[ "env:dev" ], ) ./conf.d/custom_example.yml instances : [{}] その後、以下の docker-compose.yml を用いてDockerコンテナを起動します。 version : '3' services : agent : image : datadog/agent volumes : - ./src:/etc/datadog-agent/checks.d - ./conf.d:/etc/datadog-agent/conf.d environment : - DD_API_KEY=dummy-api-key DD_API_KEY の環境変数を設定しないとDatadog Agentが起動しないため、ダミーの文字列を入れています。 Dockerコンテナが起動した後、ホスト側で以下のコマンドを実行することで、容易にスクリプトの動作確認ができます。 $ docker-compose exec agent agent check custom_example --table === Series === METRIC TYPE TIMESTAMP VALUE TAGS custom_example.value gauge 1646983404 5 env:dev ========= Collector ========= Running Checks ============== custom_example (1.0.0) ---------------------- Instance ID: custom_example:d884b5186b651429 [OK] Configuration Source: file:/etc/datadog-agent/conf.d/custom_example.yml Total Runs: 1 Metric Samples: Last Run: 1, Total: 1 Events: Last Run: 0, Total: 0 Service Checks: Last Run: 0, Total: 0 Average Execution Time : 0s Last Execution Date : 2022-03-11 07:23:24 UTC (1646983404000) Last Successful Execution Date : 2022-03-11 07:23:24 UTC (1646983404000) おわりに 新しいSkyWayでのサービス監視の概要とtipsを紹介しました。 元々学生時代からインフラの監視技術に興味があり、自宅サーバーなどでZabbixや Prometheus を使っていましたが、商用サービスの監視に関わる機会は無く、入社前から商用サービスの監視に挑戦してみたいと思っていました。 そのことを上長との1on1で話したところ、SkyWayのサービス監視について検証や構築を任せていただけました。新入社員であってもいろいろなことに挑戦できるSkyWayのチームは、とても居心地が良いなと思っています。 今回、一からサービス監視の仕組みを作りましたが、新しいSkyWayのサービス監視は始まったばかりです。 最強で最高のサービス監視体制を目指して、引き続き改善に取り組んでいきたいと思います。 最後に少しだけ宣伝です。 現在提供中の SkyWay Beta は、アカウント登録をするだけで、個人・法人問わずどなたでも使うことができます。 これまでのSkyWayには無かった機能も今後実装される予定ですので、ぜひ一足早く体験してみてください! フィードバックなどもお待ちしています!! 参考サイト カスタム Agent チェック Datadog Docs
はじめに こんにちは、インターンシップ生の 金谷 です。 2022年2月に2週間ほどNTTコミュニケーションズのインターンシップに参加させていただきました。 普段は大学院やWIDEプロジェクト、アルバイトなどで SRv6 BGP-EPEなどオーバレイネットワーク技術の研究開発をしています。 インターンシップには「 次世代のサービスを生み出す検証網 Testbedの設計構築業務 」というテーマで、2022年2月14日から25日までの2週間参加しました。 具体的にはFRRouting(FRR)というOSSのソフトウェアルータにBGP-LSという機能を実装するという内容に取り組みました。 この記事では、その体験談を記載します。 インターンシップに参加するまで NTT Comという会社に非常に興味を持っていたため、会社の雰囲気を知りながら面白い開発が出来そうだと思い、参加を決めました。 また、インターンシップを通じて入社後どのプロジェクトに所属するかの参考にしたいと思ったのも参加を決めた理由の1つです。 インターンシップで取り組んだこと インターンシップでは、主にFRRというOSSのソフトウェアルータにBGP-LSの機能を追加し、複数のメーカーの機器(Cisco、Juniper)と相互接続できるよう実装に取り組みました。 現在NTT Comでは大規模なMulti-AS Segment Routing(SR)網の構築、運用を行なっており、その網上で効率的な経路制御を実現するためPath Computation Element(PCE)によるコントローラ制御を目指しています。 Multi-AS SR網はマルチベンダに構成されており、SRv6環境の一部では拡張性を考慮しOSSのFRRを採用しています。 このFRRをコントローラ制御の対象に含めるため、FRRにBGP-LSの機能を実装し、トポロジ情報をコントローラに送信することが求められています。 また、Multi-ASの環境ではASの接続部分でBGP-EPEを行いたいという要求もあり、BGP-EPEの情報をBGP-LSで広告できること( RFC9086 , draft-ietf-idr-bgpls-srv6-ext-09 )なども鑑みて、BGP-LSは今後必要な機能であると考えられています。 私はBGP-LSというプロトコルの名前は知っていましたが、細かい内容については知らなかったため、始めはプロトコルの理解を目的として RFC7752 , 9085 などを読み込んでいきました。 次にFRRの開発も少しかじったことはありましたが、BGPについての開発は初めてでしたので、どのファイルを読めば良いのかが分からず、printデバッグをしながら手探りでFRRの構成を読み解いていきました。 最終的にはFRRにBGP-LSのOpenメッセージを実装し、IOS-XRやJunosとの間でBGPのステートをEstablishedさせることができました。 以下では、BGP-LSの機能や必要性、インターンシップでの開発内容を具体的にご紹介します。 BGP-LSとは BGP-LSとはBGP Link Stateの略称で、IGPのリンクステート情報や、トラフィックエンジニアリング(TE)情報を伝達するためのプロトコルです。 BGP-LSを利用すれば、複数のIGPドメインのトポロジ情報をPCE等のコントローラに収集することが容易となるため、NTT Comが現在検証を進めているSegment Routingを用いたMulti-AS TEに活用できると考えられます。 上図にPCEのユースケースを示します。PCEはPEからBGP-LSによってトポロジ情報を吸い上げて経路を計算し、PCEPでPEに経路を配布します。 これにより、発行済みの経路を一元管理できるようになると共に、帯域や信頼性など経路の状態を考慮した複雑なTEが行えるようになり、効率的な運用を実現できます。 FRRの構造 FRRouting とは、LinuxやBSDなどの上で動くOSSのソフトウェアルータです。 以下の図がFRRの基本構造となっています。 FRRはLinux Server上で動くアプリケーションとなっており、vtyshでユーザの操作をCLIで取得し、その情報をisisdなどのプロトコルデーモンに送信します。 isisdにはLink State Database(LSDB)が存在し、IS-ISというIGPプロトコルによって交換された各Link State情報が格納されています。 このLSDBの情報をpathd内に存在するTraffic Engineering Database(TED)にZAPIを利用してZebra経由でエクスポートし、TEDの情報を元にbgpdがBGP Databaseを変更します。 そして、bgpdがBGP-LSにより他のルータとの間でBGP Databaseとの情報を共有します。 ネットワークスタックの利用にあたっては、ZebraがNetlinkを利用してユーザ空間の情報をカーネル空間に伝達することで行われます。 実装内容 BGP-LSによってLink State情報を複数ルータ間で交換するためには、前提としてルータ間でBGP-LSの接続を確立していなければなりません。BGP-LSの接続を確立するためには、以下の手順が必要となります。 対象となるBGPスピーカ同士でTCPセッションを確立する BGP-LSを示すCapabilityの含まれたBGP Openメッセージを送信/受信する BGP Keep Aliveメッセージを受信する これらを行うことにより、BGPが確立されたステートとなり、BGP-LSのUpdateメッセージにより実際にBGP-LSのLink State情報を相互に交換し合うことができるようになります。 上記の手順をもう少し厳密に記述すると以下の図のようになります。 今回私はインターンシップ期間でBGP-LSが既に実装されているIOS-XRやJunosと、新たに実装を追加したFRRとの間でBGP-LSのEstablishedを実現しました。 初めに、FRRにはBGP-LSのAFIやSAFIが定義されていなかったため、RFCに準拠した定義を実施しました。この際、Zebraの認識するAFI/SAFIとIANAによって定義されているAFI/SAFIの値が異なるため、変換設定も必要となります。 続いて、TCP connectionがOpenになると、BGPのステートはConnectに遷移し bgp_reads_on() と bgp_open_send() という2つの関数が呼ばれます。 bgp_reads_on() では、BGP Openメッセージを受信すると適切にパースし、ピアから受信したOpenメッセージの情報を構造体に格納します。 bgp_open_send() では、Openメッセージをピアに送信します。 これら2つの関数は非同期に実行されます。 bgp_open_send() 関数が呼ばれた際はOpen Sentステートに遷移し、 bgp_open_send() と bgp_reads_on() の双方が行われた際にはOpen Confirmステートに遷移します。 あとは既存のBGP Keep Aliveメッセージによって自動でEstablishedステートへと遷移することとなります。 結果 以下がFRR側でBGP-LSのステートを表示させた結果です。 Stateが (Policy) となっています。 これはBGPステートがEstablishedかつ、Export Filterがないという意味であるため、想定通りBGP-LSがEstabsliehdになっていることが確認できました。 また、以下がIOS-XRやJunos側でBGP-LSのステートを表示させた結果です。 こちらも双方でBGPステートが Established となることが確認できました。 本インターンでは期間の関係でLSDBのExport機能や関連するshowコマンドの実装まではできませんでしたが、FRRとのコントローラ連携の実現を目指し、今後も開発を進めて行きます。 インターンシップの感想 2週間で名前程度しか知らないプロトコルの調査から始め、現在のBGP-LSの実装状況の調査やインターンシップのゴールをどこに置くかを考え、 ゴールを実現するために1000行以上のソースコードを読み必要な箇所に機能を追加していくという経験は、環境構築や多数のファイルを行き来するコード理解など大変な部分もありましたが最終的には非常に楽しかったです。 メンターの三島さんには頻繁に相談に乗っていただき、そこでプロトコルやFRRの概念の理解の補助をしていただいたり、 ソースコードを読んでいて詰まった時のネクストアクションを提示してくださったりなど、僕がインターンシップを完走出来るよう最大限フォローしていただいたことを大変感謝しています。 フルオンラインのインターンでしたが、業務以外でインターンシップの配属チームの社員さんとオンラインでお酒を飲みながらお話をして、チームの雰囲気を知れる場を用意してくださったり、 自分が興味のある別チームの方とミーティングをセッティングして業務内容を聞く会を開催してくださったりと、 インターンシップを通じて入社後のイメージを掴むための場を最大限提供してくださったことを大変感謝しています。 最後になりますが、本インターンシップでたくさんの貴重な経験をさせていただき本当にありがとうございました。 メンターからのコメント メンターを担当したイノベーションセンターの三島です。2週間のインターンシップお疲れ様でした。 初日の説明日・最終日の成果報告日や祝日を除くと1週間に満たない作業日でしたが、 NTT Comの背景理解とテーマ設定から始まり、プロトコルのRFC調査を通じた理解やFRRのコードリーティングと実装、検証環境構築や相互接続試験まで検証業務を一連で進めていただきました。 応用的なルーティングプロトコルの機能開発という難易度の高いテーマであったにも関わらず、調査から検証までを自ら進めて成果を出していただけたこと、とても嬉しく思っています。 現在NTT Com イノベーションセンターではMulti-AS SRという独自の大規模SR網を運用しており、BGP-LSはコントローラ連携による効率的な運用のための重要な技術です。 今後も開発を続け、是非FRRへのコントリビューションなど、更なる成果をあげられればと考えています。 より面白いネットワークを実現し価値を提供できるよう、我々も頑張ります! 今回のインターンシップで得た経験が、今後の金谷さんの研究や取り組みに役立てば幸いです。 改めて、インターンシップへの参加とご活躍ありがとうございました!
はじめに こんにちは、イノベーションセンターの鈴ヶ嶺です。 engineers.ntt.com engineers.ntt.com 第1回、第2回に引き続きAWS Outposts ラックについて紹介していきます。 本記事では、 Terraform  を用いてOutposts上でオンプレ環境からのみ管理・アクセス可能なPrivate Elastic Kubernetes Service(EKS) を構築する方法を紹介します。 Terraform Terraformとは、 HashCorp が提供するインフラをコード化して自動構築を可能とするInfrastructure as Code(IaC)を実現するためのツールです。OutpostsをTerraformでIaC化するためには、対応した各リソースに outpost_arn を渡すことでOutposts上のリソースが作成されます。 以下はsubnetの一例です。 1 resource "aws_subnet" "main" { vpc_id = aws_vpc.main.id cidr_block = "10.0.3.0/24" outpost_arn = "arn:aws:outposts:ap-northeast-1:1234567890:outpost/op-1234567890" } outpost_arn - (Optional) The Amazon Resource Name (ARN) of the Outpost. CDKと比較してTerraformはAWS APIを利用しているのでOutpostsのリソースは比較的対応されていると思われます。今後新しいリソースがOutpostsで利用可能になった場合も対応スピードは早いと予想されます。ただ一方で、CDKのような高レベルなリソース作成をすることが難しいのでリソースやAPIの仕様を把握する必要があります。またリソースの認証情報・状態管理なども考える必要があります。 オンプレ環境上でマネージドk8sサービスを構築するアーキテクチャ 上記のようなオンプレ環境においてもマネージドk8sサービスを利用するアーキテクチャを構築します。Outposts上にAuto Scale Groupを設置してセルフマネージド型ノードとしてそれぞれEC2をEKS Clusterに登録させます。Outpostsとオンプレ環境との通信は全てLocal Gateway経由で接続されます。 この構成の特徴としてEKS ClusterのAPIサーバをPrivateアクセスのみ許可することで、Bastion(踏み台)経由でのみクラスタの操作を可能とするセキュアな点が挙げられます。さらに、アプリケーションもオンプレ環境のみからアクセス可能なため社内にのみ公開する秘匿性の高いシステムを構築できます。 実装 以下のような構成で、それぞれのtfファイルにリソースを定義します。 main.tf Providerや各変数などの全体を記載 network.tf ネットワークに関するものを記載 eks_cluster.tf EKS Clusterに関するものを記載 eks_node.tf EKS Nodeに関するものを記載 security_group.tf Security Groupによる通信の許可に関するものを記載 bastion.tf 踏み台サーバに関するものを記載 output.tf k8sアプリケーションのデプロイ時に使用するものを記載 main.tf terraform { required_providers { aws = { source = "hashicorp/aws" version = "~> 3.0" } } } provider "aws" { region = var.region } # Name tag に一部使用する値 variable "name" { default = "outposts-eks-sample" } # EKS Node のインスタンス variable "instance_type" { default = "m5.large" } # EKS Node のdisk(GB) variable "disk_size" { default = 100 } variable "region" { default = "ap-northeast-1" } # EKS Node の望ましいノード数 variable "desired_node" { default = 3 } # EKS Node の最低ノード数 variable "min_node" { default = 3 } # EKS Node の最高ノード数 variable "max_node" { default = 3 } # Local Gateway経由からEKSにアクセス可能なオンプレ環境のCIDR variable "local_cidr_blocks" { default = [ "192.168.0.0/16" ] } locals { # outpostのARN outpost_arn = "arn:aws:outposts:ap-northeast-1:1234567890:outpost/op-1234567890" # Local GatewayのID lgw_id = "lgw-1234567890" # Local GatewayのルートテーブルのID lgw_rtb_id = "lgw-rtb-1234567890" # 顧客所有のIPアドレス customer_owned_ipv4_pool = "ipv4pool-coip-1234567890" # GPUを使用しているかどうか gpu = replace (var.instance_type, "g4dn" , "!" ) != var.instance_type } # Bastion, EKS Nodeにアクセス可能な鍵 resource "aws_key_pair" "key" { key_name = "$ { var.name } -key" public_key = file ( "~/.ssh/id_rsa.pub" ) } main.tfには AWS Provider と各変数、Bastionの鍵を設定します。 OutpostsのARN outpost_arn や Local GatewayのID lgw_id などはここに設定しておきます。 Outposts上でもAuto Scaling groupは仕様可能です。その設定に用いてる max_node , min_node , desired_node の関係は次の画像のようになっております。 2 今回は3ノードの構成で作成します。 network.tf resource "aws_vpc" "main" { cidr_block = "10.0.0.0/16" # VPCエンドポイントを有効にするためにdns hostnames, supportを設定 enable_dns_hostnames = true enable_dns_support = true tags = { Name = "vpc-$ { var.name } " } } # EKS Clusterのsubnet resource "aws_subnet" "cluster01" { vpc_id = aws_vpc.main.id cidr_block = "10.0.1.0/24" tags = { Name = "subnet-cluster01-$ { var.name } " } } # EKS Clusterのsubnet resource "aws_subnet" "cluster02" { vpc_id = aws_vpc.main.id cidr_block = "10.0.2.0/24" tags = { Name = "subnet-cluster02-$ { var.name } " } } # Outpostsのsubnet resource "aws_subnet" "outposts_subnet01" { vpc_id = aws_vpc.main.id cidr_block = "10.0.3.0/24" outpost_arn = local.outpost_arn availability_zone = "ap-northeast-1a" tags = { Name = "subnet-outposts-subnet01-$ { var.name } " "kubernetes.io/cluster/$ { aws_eks_cluster.main.name } " = "owned" "kubernetes.io/role/elb" = "1" } } # デフォルトルートをLocal Gatewayに設定 resource "aws_route_table" "local_gateway" { vpc_id = aws_vpc.main.id route { cidr_block = "0.0.0.0/0" local_gateway_id = local.lgw_id } tags = { Name = "local-gateway-route-table-$ { var.name } " } } resource "aws_route_table_association" "outposts_subnet01" { subnet_id = aws_subnet.outposts_subnet01.id route_table_id = aws_route_table.local_gateway.id } data "aws_ec2_local_gateway_route_table" "main" { outpost_arn = local.outpost_arn } resource "aws_ec2_local_gateway_route_table_vpc_association" "main" { local_gateway_route_table_id = data.aws_ec2_local_gateway_route_table.main.id vpc_id = aws_vpc.main.id } # EKS Nodeに設定するEIPのPool resource "aws_eip" "main" { count = var.max_node customer_owned_ipv4_pool = local.customer_owned_ipv4_pool tags = { Name = "eip-node-$ { count.index } " } } # Bastion(踏み台)のEIP resource "aws_eip" "bastion" { customer_owned_ipv4_pool = local.customer_owned_ipv4_pool tags = { Name = "eip-bastion-$ { var.name } " } } # 初期起動時にEKS Node自身がEIPを自ら設定するためのEC2エンドポイントを設定 resource "aws_vpc_endpoint" "ec2" { vpc_id = aws_vpc.main.id service_name = "com.amazonaws.$ { var.region } .ec2" vpc_endpoint_type = "Interface" subnet_ids = [ aws_subnet.outposts_subnet01.id ] security_group_ids = [ aws_security_group.vpc_endpoint_ec2.id, ] private_dns_enabled = true } network.tfにはネットワークに関するリソースを定義します。 ネットワーク構成として、Public AWS上にsubnetを2つ、Outposts上にsubnetを1つ作成します。OutpostsのsubnetにEKSのWorker Nodeが設置されます。基本的にOutpostsのsubnetはデフォルトルートをLocal Gatewayに設定することでインターネットなどのアクセスはオンプレ環境経由で接続されるようにします。これにより社内のネットワークポリシーの適用や監視などを可能とします。また、Local Gatewayに接続するためには顧客所有のIPアドレスcustomer owned IP address(CoIP)をアタッチする必要があるためEKS Node、Bastion用のCoIPのElastic IP アドレス(EIP)を作成しておきます。 eks_cluster.tf # EKS Cluster resource "aws_eks_cluster" "main" { name = var.name role_arn = aws_iam_role.cluster_role.arn vpc_config { security_group_ids = [ aws_security_group.cluster.id ] subnet_ids = [ aws_subnet.cluster01.id, aws_subnet.cluster02.id ] # Private Accessのみに設定してBastion経由でのみ操作する endpoint_public_access = false endpoint_private_access = true } depends_on = [ aws_iam_role_policy_attachment.eks_cluster_policy, aws_iam_role_policy_attachment.eks_service_policy, aws_iam_role_policy_attachment.eks_vpc_resource-controller, ] } resource "aws_iam_role" "cluster_role" { name = "eks-cluster-role-$ { var.name } " assume_role_policy = <<EOF { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Service": "eks.amazonaws.com" }, "Action": "sts:AssumeRole" } ] } EOF } resource "aws_iam_role_policy_attachment" "eks_cluster_policy" { policy_arn = "arn:aws:iam::aws:policy/AmazonEKSClusterPolicy" role = aws_iam_role.cluster_role.name } resource "aws_iam_role_policy_attachment" "eks_service_policy" { policy_arn = "arn:aws:iam::aws:policy/AmazonEKSServicePolicy" role = aws_iam_role.cluster_role.name } resource "aws_iam_role_policy_attachment" "eks_vpc_resource-controller" { policy_arn = "arn:aws:iam::aws:policy/AmazonEKSVPCResourceController" role = aws_iam_role.cluster_role.name } # ALBを使用するためのIAM Policy resource "aws_iam_policy" "aws_load_balancer_controller" { name = "alb-policy-$ { var.name } " # from https://raw.githubusercontent.com/kubernetes-sigs/aws-load-balancer-controller/v2.2.1/docs/install/iam_policy.json policy = <<EOF { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "iam:CreateServiceLinkedRole", "ec2:DescribeAccountAttributes", "ec2:DescribeAddresses", "ec2:DescribeAvailabilityZones", "ec2:DescribeInternetGateways", "ec2:DescribeVpcs", "ec2:DescribeSubnets", "ec2:DescribeSecurityGroups", "ec2:DescribeInstances", "ec2:DescribeNetworkInterfaces", "ec2:DescribeTags", "ec2:GetCoipPoolUsage", "ec2:DescribeCoipPools", "elasticloadbalancing:DescribeLoadBalancers", "elasticloadbalancing:DescribeLoadBalancerAttributes", "elasticloadbalancing:DescribeListeners", "elasticloadbalancing:DescribeListenerCertificates", "elasticloadbalancing:DescribeSSLPolicies", "elasticloadbalancing:DescribeRules", "elasticloadbalancing:DescribeTargetGroups", "elasticloadbalancing:DescribeTargetGroupAttributes", "elasticloadbalancing:DescribeTargetHealth", "elasticloadbalancing:DescribeTags" ], "Resource": "*" }, { "Effect": "Allow", "Action": [ "cognito-idp:DescribeUserPoolClient", "acm:ListCertificates", "acm:DescribeCertificate", "iam:ListServerCertificates", "iam:GetServerCertificate", "waf-regional:GetWebACL", "waf-regional:GetWebACLForResource", "waf-regional:AssociateWebACL", "waf-regional:DisassociateWebACL", "wafv2:GetWebACL", "wafv2:GetWebACLForResource", "wafv2:AssociateWebACL", "wafv2:DisassociateWebACL", "shield:GetSubscriptionState", "shield:DescribeProtection", "shield:CreateProtection", "shield:DeleteProtection" ], "Resource": "*" }, { "Effect": "Allow", "Action": [ "ec2:AuthorizeSecurityGroupIngress", "ec2:RevokeSecurityGroupIngress" ], "Resource": "*" }, { "Effect": "Allow", "Action": [ "ec2:CreateSecurityGroup" ], "Resource": "*" }, { "Effect": "Allow", "Action": [ "ec2:CreateTags" ], "Resource": "arn:aws:ec2:*:*:security-group/*", "Condition": { "StringEquals": { "ec2:CreateAction": "CreateSecurityGroup" }, "Null": { "aws:RequestTag/elbv2.k8s.aws/cluster": "false" } } }, { "Effect": "Allow", "Action": [ "ec2:CreateTags", "ec2:DeleteTags" ], "Resource": "arn:aws:ec2:*:*:security-group/*", "Condition": { "Null": { "aws:RequestTag/elbv2.k8s.aws/cluster": "true", "aws:ResourceTag/elbv2.k8s.aws/cluster": "false" } } }, { "Effect": "Allow", "Action": [ "ec2:AuthorizeSecurityGroupIngress", "ec2:RevokeSecurityGroupIngress", "ec2:DeleteSecurityGroup" ], "Resource": "*", "Condition": { "Null": { "aws:ResourceTag/elbv2.k8s.aws/cluster": "false" } } }, { "Effect": "Allow", "Action": [ "elasticloadbalancing:CreateLoadBalancer", "elasticloadbalancing:CreateTargetGroup" ], "Resource": "*", "Condition": { "Null": { "aws:RequestTag/elbv2.k8s.aws/cluster": "false" } } }, { "Effect": "Allow", "Action": [ "elasticloadbalancing:CreateListener", "elasticloadbalancing:DeleteListener", "elasticloadbalancing:CreateRule", "elasticloadbalancing:DeleteRule" ], "Resource": "*" }, { "Effect": "Allow", "Action": [ "elasticloadbalancing:AddTags", "elasticloadbalancing:RemoveTags" ], "Resource": [ "arn:aws:elasticloadbalancing:*:*:targetgroup/*/*", "arn:aws:elasticloadbalancing:*:*:loadbalancer/net/*/*", "arn:aws:elasticloadbalancing:*:*:loadbalancer/app/*/*" ], "Condition": { "Null": { "aws:RequestTag/elbv2.k8s.aws/cluster": "true", "aws:ResourceTag/elbv2.k8s.aws/cluster": "false" } } }, { "Effect": "Allow", "Action": [ "elasticloadbalancing:AddTags", "elasticloadbalancing:RemoveTags" ], "Resource": [ "arn:aws:elasticloadbalancing:*:*:listener/net/*/*/*", "arn:aws:elasticloadbalancing:*:*:listener/app/*/*/*", "arn:aws:elasticloadbalancing:*:*:listener-rule/net/*/*/*", "arn:aws:elasticloadbalancing:*:*:listener-rule/app/*/*/*" ] }, { "Effect": "Allow", "Action": [ "elasticloadbalancing:ModifyLoadBalancerAttributes", "elasticloadbalancing:SetIpAddressType", "elasticloadbalancing:SetSecurityGroups", "elasticloadbalancing:SetSubnets", "elasticloadbalancing:DeleteLoadBalancer", "elasticloadbalancing:ModifyTargetGroup", "elasticloadbalancing:ModifyTargetGroupAttributes", "elasticloadbalancing:DeleteTargetGroup" ], "Resource": "*", "Condition": { "Null": { "aws:ResourceTag/elbv2.k8s.aws/cluster": "false" } } }, { "Effect": "Allow", "Action": [ "elasticloadbalancing:RegisterTargets", "elasticloadbalancing:DeregisterTargets" ], "Resource": "arn:aws:elasticloadbalancing:*:*:targetgroup/*/*" }, { "Effect": "Allow", "Action": [ "elasticloadbalancing:SetWebAcl", "elasticloadbalancing:ModifyListener", "elasticloadbalancing:AddListenerCertificates", "elasticloadbalancing:RemoveListenerCertificates", "elasticloadbalancing:ModifyRule" ], "Resource": "*" } ] } EOF } eks_cluster.tfでは、EKS Clusterに関するリソースを定義しています。 ここでは、EKS ClusterのPrivate Accessを設定することで内部からのみAPIサーバにアクセス可能となります。この設定をすることでよりセキュアなk8s環境が実現できます。運用の際にはBastion経由でkubectl, helmなどの操作をします。 eks_node.tf locals { # 初期起動時にEKS Node自身がEIPを自ら設定してオンプレ環境に対して通信する node_user_data = <<EOF #!/bin/bash set -o xtrace export AWS_DEFAULT_REGION=$ { var.region } instance_id=$(curl 169 . 254 . 169 . 254 /latest/meta-data/instance-id/) # EIP Check function function check_eip () { RES=`aws ec2 describe-addresses --filters Name=allocation-id,Values=$ 1 | jq .Addresses [ 0 ] .InstanceId` if [ "$RES" = "null" ] ; then return 0 fi return 1 } eips= "$ { join ( " " , aws_eip.main.*.id) } " # EIP Attach for eip in $eips; do check_eip $eip if [ $? = 0 ] ; then aws ec2 associate-address --no-allow-reassociation --instance-id $instance_id --allocation-id $eip if [ $? = 0 ] ; then break fi fi done # EKS Node Bootstrap /etc/eks/bootstrap.sh $ { aws_eks_cluster.main.name } EOF node_role_name = "node_role_$ { var.name } " } data "aws_ssm_parameter" "eks_cpu_ami" { name = "/aws/service/eks/optimized-ami/1.21/amazon-linux-2/recommended/image_id" } data "aws_ssm_parameter" "eks_gpu_ami" { name = "/aws/service/eks/optimized-ami/1.21/amazon-linux-2-gpu/recommended/image_id" } resource "aws_launch_template" "node_template" { name_prefix = "eks_sample_node_group_template" image_id = local.gpu == true ? data.aws_ssm_parameter.eks_gpu_ami.value : data.aws_ssm_parameter.eks_cpu_ami.value instance_type = var.instance_type vpc_security_group_ids = [ aws_security_group.node.id ] key_name = aws_key_pair.key.id block_device_mappings { device_name = "/dev/xvda" ebs { volume_size = var.disk_size } } iam_instance_profile { name = aws_iam_instance_profile.instance_role.name } tag_specifications { resource_type = "instance" tags = { Name = "eks-sample-node" } } user_data = base64encode (local.node_user_data) } # EKS NodeのAutoScaleGroup resource "aws_autoscaling_group" "node_group" { vpc_zone_identifier = [ aws_subnet.outposts_subnet01.id ] desired_capacity = var.desired_node max_size = var.max_node min_size = var.min_node health_check_type = "EC2" wait_for_capacity_timeout = "120m" launch_template { id = aws_launch_template.node_template.id version = "$Latest" } tag { key = "kubernetes.io/cluster/$ { aws_eks_cluster.main.name } " value = "owned" propagate_at_launch = true } } resource "aws_iam_role" "node_group_role" { name = "node-group-role-$ { var.name } " assume_role_policy = <<EOF { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Service": "ec2.amazonaws.com" }, "Action": "sts:AssumeRole" } ] } EOF } resource "aws_iam_role_policy_attachment" "eks-worker-node-policy" { policy_arn = "arn:aws:iam::aws:policy/AmazonEKSWorkerNodePolicy" role = aws_iam_role.node_group_role.name } resource "aws_iam_role_policy_attachment" "eks-cni-policy" { policy_arn = "arn:aws:iam::aws:policy/AmazonEKS_CNI_Policy" role = aws_iam_role.node_group_role.name } resource "aws_iam_role_policy_attachment" "eks-container-registry-read-only" { policy_arn = "arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly" role = aws_iam_role.node_group_role.name } resource "aws_iam_instance_profile" "instance_role" { name = local.node_role_name role = aws_iam_role.node_group_role.name } # EKS NodeにEIPをAttachするためのIAM Policy resource "aws_iam_policy" "eip_attach_policy" { name = "eip-attach-policy-$ { var.name } " policy = <<EOF { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "ec2:DescribeAddresses", "ec2:AssociateAddress" ], "Resource": "*" } ] } EOF } resource "aws_iam_role_policy_attachment" "eip_attach_policy" { role = aws_iam_role.node_group_role.name policy_arn = aws_iam_policy.eip_attach_policy.arn } # セルフマネージド型ノードがEKSに登録されるための設定、BastionでのAPIサーバに対するkubectl操作の許可設定 resource "local_file" "aws_auth" { content = <<EOF apiVersion: v1 kind: ConfigMap metadata: name: aws-auth namespace: kube-system data: mapRoles: | - rolearn: $ { aws_iam_role.node_group_role.arn } username: system:node:{{EC2PrivateDNSName}} groups: - system:bootstrappers - system:nodes - rolearn: $ { aws_iam_role.bastion.arn } username: bastionrole groups: - system:masters EOF filename = "_aws-auth.yaml" } eks_node.tfでは、EKS Nodeに関するリソースを定義しています。 Outposts上ではAuto Scale Groupの利用が可能なためその設定を定義しています。ここでの特徴はAuto Scale Groupの各ノードに対して user_data の中の処理でCoIPを設定しているところです。Auto Scale Groupには自動でEIPを設定が現状できないため、この設定によりLocal Gateway経由でインターネットやオンプレ環境と通信することが可能となります。 また、 aws_auth ではBastionのRoleがAPIサーバにアクセスするための設定ファイルを作成しています。 security_group.tf resource "aws_security_group" "cluster" { vpc_id = aws_vpc.main.id name = "eks-cluster-security-group-$ { var.name } " tags = { Name = "eks-cluster-security-group-$ { var.name } " } ingress { from_port = 1025 to_port = 65535 protocol = "tcp" security_groups = [ aws_security_group.node.id ] } ingress { from_port = 443 to_port = 443 protocol = "tcp" security_groups = [ aws_security_group.node.id, aws_security_group.bastion.id ] } egress { from_port = 0 to_port = 0 protocol = "-1" cidr_blocks = [ "0.0.0.0/0" ] } } resource "aws_security_group" "node" { vpc_id = aws_vpc.main.id name = "eks-node-sg-$ { var.name } " tags = { Name = "eks-node-sg-$ { var.name } " } egress { from_port = 0 to_port = 0 protocol = "-1" cidr_blocks = [ "0.0.0.0/0" ] } } resource "aws_security_group_rule" "node_ingress_443" { security_group_id = aws_security_group.node.id type = "ingress" from_port = 443 to_port = 443 protocol = "tcp" source_security_group_id = aws_security_group.cluster.id } resource "aws_security_group_rule" "node_ingress_1025_65535" { security_group_id = aws_security_group.node.id type = "ingress" from_port = 1025 to_port = 65535 protocol = "tcp" source_security_group_id = aws_security_group.cluster.id } resource "aws_security_group_rule" "node_ingress_localgateway" { security_group_id = aws_security_group.node.id type = "ingress" from_port = 0 to_port = 0 protocol = "all" cidr_blocks = var.local_cidr_blocks } resource "aws_security_group_rule" "node_ingress_self" { security_group_id = aws_security_group.node.id type = "ingress" from_port = 0 to_port = 0 protocol = "all" self = true } resource "aws_security_group" "vpc_endpoint_ec2" { vpc_id = aws_vpc.main.id name = "vpc-endpoint-ec2-sg-$ { var.name } " tags = { Name = "vpc-endpoint-ec2-sg-$ { var.name } " } ingress { from_port = 0 to_port = 0 protocol = "all" security_groups = [ aws_security_group.node.id ] } egress { from_port = 0 to_port = 0 protocol = "-1" cidr_blocks = [ "0.0.0.0/0" ] } } resource "aws_security_group" "bastion" { vpc_id = aws_vpc.main.id name = "bastion-sg-$ { var.name } " tags = { Name = "bastion-sg-$ { var.name } " } # オンプレ環境からのみアクセス可能な設定にする ingress { from_port = 22 to_port = 22 protocol = "tcp" cidr_blocks = var.local_cidr_blocks } egress { from_port = 0 to_port = 0 protocol = "-1" cidr_blocks = [ "0.0.0.0/0" ] } } security_group.tfにはSecurity Groupに関するリソースを定義します。 こちらでは、EKSやオンプレ環境とのSecurity Groupについて設定されています。 local_cidr_blocks については適宜それぞれのオンプレ環境に適した設定が必要となります。EKSについての詳細は次の公式ドキュメントを参照してください。 Amazon EKS セキュリティグループの考慮事項 bastion.tf locals { # APIサーバを操作するためのkubectl, helmなどの各種ツールをインストールする bastion_user_data = <<EOF #!/bin/bash set -o xtrace # install kubectl curl --retry 180 --retry-delay 1 -LO "https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl" chmod +x ./kubectl sudo mv ./kubectl /usr/local/bin/kubectl # install helm curl -sSL https: //raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 | bash EOF } data "aws_ssm_parameter" "amzn2_ami" { name = "/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2" } data "aws_iam_policy_document" "assume_role" { statement { actions = [ "sts:AssumeRole" ] principals { type = "Service" identifiers = [ "ec2.amazonaws.com" ] } } } resource "aws_iam_role" "bastion" { name = "bastion-role-$ { var.name } " assume_role_policy = data.aws_iam_policy_document.assume_role.json } # APIサーバ認証のためのIAM Policy resource "aws_iam_policy" "bastion" { name = "bastion-policy-$ { var.name } " policy = <<EOF { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "eks:UpdateClusterConfig", "eks:DescribeUpdate", "eks:DescribeCluster" ], "Resource": "$ { aws_eks_cluster.main.arn } " }, { "Effect": "Allow", "Action": [ "eks:ListClusters" ], "Resource": "*" } ] } EOF } resource "aws_iam_role_policy_attachment" "bastion" { role = aws_iam_role.bastion.name policy_arn = aws_iam_policy.bastion.arn } resource "aws_iam_instance_profile" "bastion" { role = aws_iam_role.bastion.name } resource "aws_instance" "bastion" { ami = data.aws_ssm_parameter.amzn2_ami.value subnet_id = aws_subnet.outposts_subnet01.id instance_type = var.bastion_instance_type key_name = aws_key_pair.key.key_name vpc_security_group_ids = [ aws_security_group.bastion.id ] iam_instance_profile = aws_iam_instance_profile.bastion.name user_data = local.bastion_user_data tags = { Name = "bastion-instance-$ { var.name } " } } resource "aws_eip_association" "bastion" { instance_id = aws_instance.bastion.id allocation_id = aws_eip.bastion.id } bastion.tfにはBastion(踏み台)に関するリソースを定義します。 こちらでは、BastionにEKSのAPIサーバを操作可能とするためのIAM Roleを設定していることがわかると思います。また通信もCoIPを設定してオンプレ環境からのみ接続できるように設定します。 output.tf output "cluster" { value = aws_eks_cluster.main.name } output "alb_arn" { value = aws_iam_policy.aws_load_balancer_controller.arn } output "region" { value = var.region } output "ip_pool" { value = local.customer_owned_ipv4_pool } output "vpc_cidr" { value = aws_vpc.main.cidr_block } output "bastion_ip" { value = aws_eip.bastion.customer_owned_ip } output.tfにはk8sアプリケーションのデプロイに使用する値を定義します。 Terraform Apply 上記のtfファイルを次のようにapplyすることで一連のリソースの自動構築が可能です。 terraform init terraform plan # dry run terraform apply Setup EKS 初期には、APIサーバにはEKSクラスタ作成者のみがアクセス可能なため sshuttle というsshでBastion経由の通信を可能とするツールを用います。aws-authをapplyした後はBastion上での操作が可能となります。まず初めに helm を用いてEKS上でALB利用するためのOIDCやServiceAccountなどの設定をして、サンプルアプリケーションgame2048をデプロイしてみます。 事前に使用する sshuttle , kubectl , helm などのツールをインストールします。 動作確認環境: Apple M1 Max MacBookPro18,4 macOS 12.2.1 Darwin 21.3.0 brew install sshuttle brew install kubectl brew install helm brew tap weaveworks/tap brew install weaveworks/tap/eksctl CLUSTER="`terraform output -raw cluster`" ALB_ARN="`terraform output -raw alb_arn`" REGION="`terraform output -raw region`" IP_POOL="`terraform output -raw ip_pool`" BASTION_IP="`terraform output -raw bastion_ip`" VPC_CIDR="`terraform output -raw vpc_cidr`" sshuttle -r ec2-user@$BASTION_IP $VPC_CIDR & PID=$! aws eks --region $REGION update-kubeconfig --name $CLUSTER kubectl apply -f _aws-auth.yaml # oidc eksctl utils associate-iam-oidc-provider \ --region=ap-northeast-1 \ --cluster=$CLUSTER \ --region=$REGION \ --approve # attach policy eksctl create iamserviceaccount \ --cluster=$CLUSTER \ --namespace=kube-system \ --name=aws-load-balancer-controller \ --attach-policy-arn=$ALB_ARN \ --override-existing-serviceaccounts \ --approve # ssh bastion ssh ec2-user@$BASTION_IP # ALB helm install aws-load-balancer-controller eks/aws-load-balancer-controller \ -n kube-system \ --set clusterName=$CLUSTER \ --set serviceAccount.create=false \ --set image.repository=602401143452.dkr.ecr.ap-northeast-1.amazonaws.com/amazon/aws-load-balancer-controller \ --set serviceAccount.name=aws-load-balancer-controller # ALB Application game2048 curl -s https://raw.githubusercontent.com/kubernetes-sigs/aws-load-balancer-controller/v2.3.1/docs/examples/2048/2048_full.yaml | \ sed "51s|.*| alb.ingress.kubernetes.io/customer-owned-ipv4-pool: $IP_POOL|" > _2048_full.yaml kubectl apply -f _2048_full.yaml kill -9 $PID ingressの設定の中に alb.ingress.kubernetes.io/customer-owned-ipv4-pool 3 を設定することでオンプレからアクセス可能なALBを作成することが可能となります。 上記のようにgame-2048をdeployすることで以下のようにingressが立ち上がります。表示されたURIにオンプレ環境からアクセスすることでサンプルのアプリケーションが表示されます。 kubectl get ingress -n game-2048 NAME CLASS HOSTS ADDRESS PORTS AGE ingress-2048 <none> * k8s-game2048-ingress2-xxxxxxxxx-1234567890.ap-northeast-1.elb.amazonaws.com 80 4d10h 以上のようにサンプルアプリケーションを実行できました。 まとめ 本記事では、Terraformを用いてOutposts上でオンプレ環境からのみ管理・アクセス可能なPrivate EKSを構築する方法を紹介しました。 開発面では、オンプレ環境でもIaCを用いてマネージドk8sを利用することが大きなメリットかと思います。利用面でもセキュアな可用性の高いアプリケーションを構築することが可能となるため非常に有用であると思います。 https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/subnet ↩ https://docs.aws.amazon.com/autoscaling/ec2/userguide/what-is-amazon-ec2-auto-scaling.html ↩ https://kubernetes-sigs.github.io/aws-load-balancer-controller/v2.1/guide/ingress/annotations/#customer-owned-ipv4-pool ↩