/var/log/study

つまり雑記

golangのnet/urlでhttpのURLをパースする際の細かい話

この記事は富士通クラウドテクノロジーズアドベントカレンダー202220日目の記事です。

前日はaokuma さんの GitLab agent for Kubernetes を使った GitOps (Helm版) を試してみた でした。 CI/CDはそれ自体の難しさもありますが、ソースコードやその成果物に対してセキュアにアクセスするのが面倒な印象があります。 GitLab上でk8sにデプロイすることも一括で面倒を見てくれるとなると、より便利に安定して使える様になることを期待してしまいますね。

(余談ですが、ニフクラでは, DevOps with Gitlab と言うマネージドのGitLabサービスを提供しているので、興味があれば見て見てください。)

さて、本日は、URLとは?と言う話に触れつつ、golangのnet/urlの Parse 関数について記載しようと思います。

目次

前提

  • golang のバージョンは1.19 動作確認しています
  • URLの話をするので、MDNのURLとは? に目を通しておくと良いと思います
    • より詳しく知りたい場合は、RFC3986の和訳 を参考にすると良いでしょう
  • RFC的なURLの正しさ、の話はするつもりがないです
  • また、golangの実装がおかしい、と批判するつもりもないです。

TL;DR

golang の net/url のParse関数を利用する際は、net/url をよく読みましょう。きっと考えているよりもさまざまなことを実施してくれます。ユースケースに応じて、特に不正だと思えるURLのケースの振る舞いをよくチェックしましょう

背景

この記事を記載するに至った理由を少し記載します。

とあるシステムにhttpもしくはhttpsでアクセス可能なURLを登録したいのですが、そのシステムはURLを全体で1つの文字列として取り扱わず、要素毎に登録用のフィールドが分かれています。 そのシステムにURLを登録するツールをgolangで作りたかったので、適当な文字列を与えた時、その文字列をURLのフォーマットに従って、分解するしできる限りオリジナルな入力のまま扱う仕組みがgolangで欲しい、と言うのが今回の課題です。

ただ、これはあくまできっかけに過ぎず、色々と触った結果を踏まえて記述できればと考えています。

url.Parseは手広くパース成功と判定する

golangでURLを要素ごとに分解しようと思うと、ブログのタイトルにしているくらいなので、net/url のParseパッケージが利用できます。

package main

import "net/url"

func main() {
     parsed, err := url.Parse("http://user:pass@example.com:8080/path1/path2?key=value#fragment")
     if err != nil {
        fmt.Println(err)
     } else {
       fmt.Println("ok")
       fmt.Printf("Scheme: %s\n", parsed.Scheme)
       fmt.Printf("UserInfo: %s\n", parsed.UserInfo)
       fmt.Printf("Host: %s\n", parsed.Host)
       fmt.Printf("Path: %s\n", parsed.Path)
       fmt.Printf("RawQuery: %s\n", parsed.RawQuery)
       fmt.Printf("Fragment: %s\n", parsed.Fragment)
     }
}

いかがでしたでしょうか?

想定している課題によっては間違いではありませんが、前述した背景のようなケースで、url.Parseでパース出来ない文字列はURLではない、と考えていると問題点があります。まず、Parseのドキュメントを参照すると、以下の様に記載があります。

Parse parses a raw url into a URL structure. The url may be relative (a path, without a host) or absolute (starting with a scheme). Trying to parse a hostname and path without a scheme is invalid but may not necessarily return an error, due to parsing ambiguities.

と言うことで、

url.Parse("/hoge/fuga")

などはschemeやauthorityが無いケースは許容されることになります。

また、実際にPase関数のドキュメントの直下に、ParseRequestURIと言う関数がありますが、こちらは

ParseRequestURI parses a raw url into a URL structure. It assumes that url was received in an HTTP request, so the url is interpreted only as an absolute URI or an absolute path. The string url is assumed not to have a #fragment suffix. (Web browsers strip #fragment before sending the URL to a web server.)

なので、絶対URI絶対パスしか許容しない、と言うことになります。

if _, err := url.ParseRequestURI("/hoge/fuga"); err != nil {
   fmt.Println(err)
} else {
  fmt.Println("absolute: ok") # こちらになる
}
if _, err := url.ParseRequestURI("hoge/fuga"); err != nil {
   fmt.Println(err) # こちらになる
} else {
  fmt.Println("relative: ok")
}

どちらの関数も、特定のRFCに準拠している、と言う記載も無いので振る舞いに対しての意見は無いですが、schemeやauthorityが欠けていてもパースを成功と判定するので注意すべき点だと考えています。

urlはhttpだけではない

URLの表記はhttpやhttpsだけではありません。 例えば、chrome://settings であったり、gitlabのGlobal IDでgid://gitlab/Issue/123 のように利用されているケースもあります。 URLはその利用ケースに応じて、schemeもそうですが、URLで表現したいリソースを下支えする制約 (例えばIPv4IPv6, 将来的に出てくるそれ以外のプロトコルや、TCPUDPのポートナンバーの上限下限など) があるはずです。

と、言うところで、責務を考えれば当然ですが(そして他言語のライブラリでも同様ではあると思うのですが)、url.Parseではhttp向けではないURLも全く問題なくパースすることができますし、例えばTCPのポートナンバーの上限を超えた値をセットしたURLも問題なくパース成功とみなします。

url.Parse はパスのパーセントエンコーディングで振る舞いが変わる

少し話が変わりますが、httpもしくはhttpsでアクセス可能なURLというと、例えば、URL内に日本語がある時、その日本語はパーセントエンコーディングされるか、日本語のままか、と言うのは少し気になる点ではあります。 以下の2つのURLは実質同じURLで前者がパーセントエンコーディングなし、後者がパーセントエンコーディングありとなります。

  • http://example.com/ほげ/ふが
  • http://example.com/%E3%81%BB%E3%81%92/%E3%81%B5%E3%81%8C

上記の2URLでurl.Parseを動かすと、以下のような振る舞いになります。

if s, err := url.Parse("http://example.com/ほげ/ふが"); err != nil {
    fmt.Println(err)
} else {
    fmt.Println(s)               // http://example.com/%E3%81%BB%E3%81%92/%E3%81%B5%E3%81%8C
    fmt.Println(s.Path)          // /ほげ/ふが
    fmt.Println(s.RawPath)       // /ほげ/ふが
    fmt.Println(s.EscapedPath()) // /%E3%81%BB%E3%81%92/%E3%81%B5%E3%81%8C
}
if s, err := url.Parse("http://example.com/%E3%81%BB%E3%81%92/%E3%81%B5%E3%81%8C"); err != nil {
    fmt.Println(err)
} else {
    fmt.Println(s)               // http://example.com/%E3%81%BB%E3%81%92/%E3%81%B5%E3%81%8C
    fmt.Println(s.Path)          // /ほげ/ふが
    fmt.Println(s.RawPath)       //
    fmt.Println(s.EscapedPath()) // /%E3%81%BB%E3%81%92/%E3%81%B5%E3%81%8C
}

特に気にすべきは、パーセントエンコーディングされたURLを与えた時に、パース結果のPathと言うフィールドがデコード後の結果が入り、RawPathがなく、オリジナルなデータを確認するにはEscapedPathメソッドを呼ぶ必要がある点だと思います。些細な点ではあり、かつユースケースとして珍しいとは思いますが、url.Parseの結果だけでは元々与えられたURLのパスが、エンコーディング前の状態だったか、エンコーディング後の状態だったかを判別することができず、ParseしたURLともともとのURLの比較が必要になります。

punycode対応はしてくれない

前述した内容では、urlのパスなどにパーセントエンコーディングされた文字が入ってくるとデコードしてくれることは示しました。 URLに日本語を入れる、と言う観点で考えると、国際化ドメインがどうなるのか?というのが少し気になるのではないかと思います。

例として以下のURLはpunycodeで変換されている、されていないの違いで、実質的な意味は一緒のものだと思います。

  • http://たとえ.テスト
  • http://xn--r8j2b1a.xn--zckzah

エンコードされている文字列がデコードされる、と考えると、url.Parseを通すと、後者が前者に変換されるように思えますが、こちらは特に変換されず、また前者のURLからpunycode変換する機能はnet/urlでは提供されておらず、golang.org/x/net/idna などを利用することになります。

国際化ドメインの話は、URLの話は別だと考えると自然な結果かもしれません。

終わりに

golangnet/url のParse関数を使った際の話をつらつらと書きました。この記事を読んで、ちょっとした想定外を回避できる人が増えたらうれしいと思っています。

この記事は富士通クラウドテクノロジーズアドベントカレンダー202220日目の記事でした。 明日は、natsumo さんの、続・Scratchにデータベースが使える拡張ブロックを追加して活用してみた話です。 GUIで簡単にプログラムを組めるScratchにデータベースが使える拡張が入ると、例えば、Scratchでゲームを作りたい人にとってはできることの幅が広がってうれしいのではないか?と思うので気になりますね!

NSX Advanced Load Balancer でパケットをキャプチャする

この記事はvExperts Advent Calender 2022 の16日目の投稿です。

昨日はKoichi TakedaさんのAWS BackupでVMware Cloud on AWSをバックアップ/リストアしてみた #4 (クロスリージョンコピーを活用したDR対策 編) でした。 リージョンを跨いだバックアップの実現は事業継続の観点で重要なのではないかと思うので、興味があればぜひチェックしてみてください。

簡単な自己紹介なのですが、私は普段、仮想化基盤上のネットワークに関する設計/構築/開発/運用、時にはトラブルシューティングなどに携わるチームで仕事をしています。 ネットワークのトラブルを解決する為にできることは色々とあると思いますが、その中でも直接パケットをキャプチャし、そのパケットの内容から事態を整理してくのが、解決への近道では無いかと考えていて、 チーム内では「初手パケットキャプチャ安定」などと考えられているくらいには強力です。

VMware製品でいえば、ESXi/NSXでどのようにパケットをキャプチャすべきか?はよく語られていると思うので、 本日は、VMwareが提供してるNSX Adbanced LaodBalancer (通称 ALB もしくは VMwareが買収する以前の製品名 Avi) でパケットをキャプチャする方法などを調査、紹介、整理しようと思います。

目次

想定読者

  • NSX ALBの基本的なアーキテクチャは知っていて、UIは操作したことがある程度
    • ALBの用語の説明等は特にしない、ということです。

前提

  • 2022/12/15時点でのALBの最新版である22.1.2系で確認等をしています。
  • 今回の記事もあくまで参考記事です
    • ALBでキャプチャをする上で不明点等あればサポートに質問をしてください
    • この記事を参考にオペレーションをした結果、不具合等が起きても私は責任を取れません

NSX ALBのパケットキャプチャ概要

基本的にはドキュメント が良くまとまっているのでそこを見てください。ここでは改めて要点だけを説明します。

UIからでも、CLIからでも、APIからでもキャプチャを開始できます。キャプチャのポイントは、VirtualServiceもしくはServiceEngine を指定できますが、UIからパケットをキャプチャする際は、VirtualService単位でのキャプチャしか選ぶことができません。 またUIからは細かいオプション (わかりやすい例では、キャプチャしたファイルをローテーションする設定など) の指定が出来ないので、CLIAPIからキャプチャを開始することに慣れるのが無難だと思います。

キャプチャの際には、キャプチャするパケットを絞る為にフィルタのオプションを指定したい場合、 そのフィルタのオプション等は、APIドキュメントをみるのが良いです。 各キャプチャポイントと対応するように DebugVirtualService, DebugServiceEngine を確認してみてください。

(余談ですが、NSX ALBにもUIから利用できない機能がいくつもあります。例えば、ユーザーに対してUIからは付与できない権限が、APIからは付与できたりします。これも対応するAPIドキュメントとパラメーターを眺めるとわかります。)

キャプチャを終了すると、VirtualServiceを指定してキャプチャした場合はControllerの /tmp 配下にファイルが配置され、ServiceEngineを指定してキャプチャした際も Controllerの /var/lib/avi/se-pcap 配下にファイルが置かれます。 なので、キャプチャが終わった際は、Controllerからscpなどで作業用の端末にコピーすると良いです。(また、ALBはキャプチャした情報を何かしらの形式で内部的に持っている様で、それとは別にキャプチャの内容を利用者がアクセスしやすい場所にファイルで出力する、という作りになっているのを意識すると良いかと思います。)

以降はVirtualServiceのキャプチャに絞って話を進めます。

CLIからVirtualServiceでパケットをキャプチャする

もしALBのCLIの利用方法を知らなければ、ALBのCLIガイド があるので、先に目を通すと良いと思います。

まず、ALBのコントローラーにSSHログインして、cliを立ち上げます。(今回の私の環境ではコントローラーに172.16.0.20のIPアドレスを付与しているので、以降コマンドのサンプル内に記載のある、そのアドレスはコントローラーだと読み替えてください。)

admin@172-16-0-20:~$ shell
Login: admin
Password:

[admin:172-16-0-20]: >

次に、今回のキャプチャの対象であるVirtualServiceを確認しておきます。 今回のキャプチャのテストでは、pcap-testというVirtualServiceを利用します。 (つまり、以降のコマンドの例でpcap-test と指定していたら、キャプチャしたい環境のVirtualService名に適当に読み替えてください。)

[admin:172-16-0-20]: > show virtualservice
+-----------+--------------+-------------+----------+---------------+------------+----------------+--------+
| Name      | IP Address   | IP6 Address | Services | Cloud         | Oper State | Type           | Parent |
+-----------+--------------+-------------+----------+---------------+------------+----------------+--------+
| pcap-test | 172.16.1.100 | -           | 443      | Default-Cloud | OPER_UP    | VS_TYPE_NORMAL | -      |
+-----------+--------------+-------------+----------+---------------+------------+----------------+--------+

では、このVirtualServiceに対してキャプチャの設定を入れてみます。 ひとまず何も指定せずに100くらいのパケットを取ることにします。

(以降の手順で、 debug virtualservice と、 debugvirtualservice が出てきて、似ていますが異なるコマンドですので注意してください。)

[admin:172-16-0-20]: > debug virtualservice pcap-test
Updating an existing object. Currently, the object is:
+--------------+-----------------------------------------------------+
| Field        | Value                                               |
+--------------+-----------------------------------------------------+
| uuid         | virtualservice-7b63bd3e-00e0-485f-b414-93ce8c69d0e4 |
| name         | pcap-test                                           |
| debug_hm     | DEBUG_VS_HM_NONE                                    |
| tenant_ref   | admin                                               |
| cloud_ref    | Default-Cloud                                       |
| resync_flows | False                                               |
+--------------+-----------------------------------------------------+
[admin:172-16-0-20]: debugvirtualservice> capture
[admin:172-16-0-20]: debugvirtualservice> capture_params
[admin:172-16-0-20]: debugvirtualservice:capture_params> num_pkts 100
[admin:172-16-0-20]: debugvirtualservice:capture_params> file_count 1
[admin:172-16-0-20]: debugvirtualservice:capture_params> save
[admin:172-16-0-20]: debugvirtualservice> save
+----------------------------------+-----------------------------------------------------+
| Field                            | Value                                               |
+----------------------------------+-----------------------------------------------------+
| uuid                             | virtualservice-7b63bd3e-00e0-485f-b414-93ce8c69d0e4 |
| name                             | pcap-test                                           |
| capture                          | True                                                |
| capture_params                   |                                                     |
|   pkt_size                       | 128 bytes                                           |
|   duration                       | 0 min                                               |
|   num_pkts                       | 100                                                 |
|   enable_ssl_session_key_capture | False                                               |
|   pcap_ng                        | True                                                |
|   file_count                     | 1                                                   |
| debug_hm                         | DEBUG_VS_HM_NONE                                    |
| tenant_ref                       | admin                                               |
| cloud_ref                        | Default-Cloud                                       |
| resync_flows                     | False                                               |
+----------------------------------+-----------------------------------------------------+

VirtualServiceのVIPである172.16.1.100 に少しトラフィックを流したのち, キャプチャしたファイルを /tmp に吐かせる為に以下のコマンドを叩きます。

(もし、キャプチャの修了条件を満たす前にキャプチャを止めたければ、no capture を実行することで明示的にキャプチャを止めることができます。)

[admin:172-16-0-20]: > show debug virtualservice pcap-test service capture
Downloaded the attachment to /tmp/vs_virtualservice-7b63bd3e-00e0-485f-b414-93ce8c69d0e4.1671117277.pcapng

キャプチャできているようなので、前述の通り、Controllerの /tmpを確認してみることにします。(これは、cliを立ち上げる以前のALB コントローラーのシェルで確認します。)

admin@172-16-0-20:/tmp$ ls -lah /tmp/vs_virtualservice-7b63bd3e-00e0-485f-b414-93ce8c69d0e4.1671117277.pcapng
-rw-r----- 1 admin admin 37K Dec 15 15:14 /tmp/vs_virtualservice-7b63bd3e-00e0-485f-b414-93ce8c69d0e4.1671117277.pcapng

これを適当な端末にscpしてくると良いと思います。

ただし、この方法だと、いくつかキャプチャを取った際に、最新のキャプチャ以外が取得できないようなのですが(少なくとも現時点でcliのオプション等は見つけられませんでした)、別解として、captureのファイルをCLIから調べて、curl等でキャプチャファイルをダウンロードする方法もあります。

まず、いくつかキャプチャしたファイルの名前を控えます。

[admin:172-16-0-20]: > show debugvirtualservice pcap-test captures
+--------------------------------------------------------------------------+-------+--------------------------+
| Name                                                                     | Size  | Date Modified            |
+--------------------------------------------------------------------------+-------+--------------------------+
| vs_virtualservice-7b63bd3e-00e0-485f-b414-93ce8c69d0e4.1671117277.pcapng | 37372 | 2022-12-15T15:14:37+0000 |
| vs_virtualservice-7b63bd3e-00e0-485f-b414-93ce8c69d0e4.1671117105.pcapng | 144   | 2022-12-15T15:11:45+0000 |
| vs_virtualservice-7b63bd3e-00e0-485f-b414-93ce8c69d0e4.1671116754.pcapng | 37084 | 2022-12-15T15:05:54+0000 |
| vs_virtualservice-7b63bd3e-00e0-485f-b414-93ce8c69d0e4.1671116565.pcapng | 37140 | 2022-12-15T15:02:45+0000 |
| vs_virtualservice-7b63bd3e-00e0-485f-b414-93ce8c69d0e4.1671116066.pcapng | 37140 | 2022-12-15T14:54:26+0000 |
+--------------------------------------------------------------------------+-------+--------------------------+

(また、このキャプチャファイルの一覧を確認する別解として、 https://${CONTROLLER_IP}/api/fileservice?uri=controller://vs-pcap というエンドポイントがあるようですが、APIドキュメントからは記載が見つけられていないです。 私はALBのUIがどのような仕組みでUIにキャプチャしたファイルの一覧を出しているのかを調べて気づきました。また vs-pcapse-pcap に変えることも可能です。 CLIに show debugvirtualservice でVSでとったキャプチャの一覧をみるコマンドがあるが、show debugserviceengine 相当のコマンドは見つけられなかったので、このAPIを利用した方が便利かもしれません。)

次にAviのUIからAPIトークンを生成して、以下のコマンドを適当な端末から以下のコマンドを叩きます。 以下は,vs_virtualservice-7b63bd3e-00e0-485f-b414-93ce8c69d0e4.1671117105.pcapng を取得する例です。

$ curl -k -H "Authorization: token ${API_TOKEN}" 'https://172.16.0.20/api/fileservice?uri=controller://vs-pcap/vs_virtualservice-7b63bd3e-00e0-485f-b414-93ce8c69d0e4.1671117105.pcapng' --output vs_virtualservice-7b63bd3e-00e0-485f-b414-93ce8c69d0e4.1671117105.pcapng

さらに別解として、キャプチャの設定自体はCLIで実施し、キャプチャしたファイルのダウンロードはUIから行う方法もあります。

VirtualServiceでキャプチャする際の便利オプション

これはドキュメントにも記載がありUIからも指定ができますが、2点あります。

  1. sslkeylogfileの合わせて出力するオプション
    • HTTPSの通信を復号して確認するためのファイルです
      • 利用方法は、「Wireshark sslkeylog」などで検索すると出てくるので割愛します
    • cli からは enable_ssl_session_key_capture を有効にします
  2. VirtualServiceが取り扱うトラフィックのうち、実際の通信とヘルスチェックの通信をキャプチャするか否か選ぶオプション
    • cli からは debug_hm を DEBUG_VS_HM_NONE, DEBUG_VS_HM_ONLY, DEBUG_VS_HM_INCLUDE のうちから指定

前述の通り、細かいオプションの指定はAPIドキュメントをみるのが良いです。

APIでキャプチャを指定する

一度設定したキャプチャのパラメーターは、captureのTrue/False 以外は設定したままの状態で保持されます。ただ、いくつかのVirtualServiceでキャプチャの設定を使いまわすことを考えると、cliのコマンドの手順作っておく、では若干不便かと思います。 なのでその対策として、APIでキャプチャのパラメーターを出力してとっておき、別のVirtualService に使いまわす、などをすると良いと考えています。

以下が、一度設定したキャプチャを保存する方法 (要 jq コマンド)

# VirtualService pcap-test のUUIDを確認する
$ curl -k -s -H "Authorization: token $API_TOKEN" https://172.16.0.20/api/virtualservice?name=pcap-test' | jq '.results[0].uuid' -r
virtualservice-7b63bd3e-00e0-485f-b414-93ce8c69d0e4

# VirtualServiceのUUIDを利用して、キャプチャの設定を取得する
$ curl -k -s -H "Authorization: token ${API_TOKEN}" 'https://172.16.0.20/api/debugvirtualservice/virtualservice-7b63bd3e-00e0-485f-b414-93ce8c69d0e4' | jq . | tee example_capture.json
{
  "url": "https://172.16.0.20/api/debugvirtualservice/virtualservice-7b63bd3e-00e0-485f-b414-93ce8c69d0e4",
  "uuid": "virtualservice-7b63bd3e-00e0-485f-b414-93ce8c69d0e4",
  "name": "pcap-test",
  "tenant_ref": "https://172.16.0.20/api/tenant/admin",
  "cloud_ref": "https://172.16.0.20/api/cloud/cloud-f6c7c4ae-f7af-4090-a2c6-826b275139c7",
  "_last_modified": "1671117293782360",
  "capture": false,
  "capture_params": {
    "duration": 0,
    "enable_ssl_session_key_capture": false,
    "num_pkts": 100,
    "pcap_ng": true,
    "pkt_size": 128
  },
  "debug_hm": "DEBUG_VS_HM_NONE",
  "resync_flows": false
}

さらに、copy-pcap-test というVirtualServiceを用意したので、キャプチャの設定を一度確認してみます。

# VirtualService copy-pcap-test のUUID を確認する
$ curl -k -s -H "Authorization: token ${API_TOKEN}" 'https://172.16.0.20/api/virtualservice?name=copy-pcap-test' | jq '.results[0].uuid' -r
virtualservice-ed9f734a-5420-4d0d-83d7-d4fe27c5e008

# 同様にキャプチャの設定を確認する
$ curl -k -s -H "Authorization: token ${API_TOKEN}" 'https://172.16.0.20/api/debugvirtualservice/virtualservice-ed9f734a-5420-4d0d-83d7-d4fe27c5e008' | jq .
{
  "url": "https://172.16.0.20/api/debugvirtualservice/virtualservice-ed9f734a-5420-4d0d-83d7-d4fe27c5e008",
  "uuid": "virtualservice-ed9f734a-5420-4d0d-83d7-d4fe27c5e008",
  "name": "copy-pcap-test",
  "tenant_ref": "https://172.16.0.20/api/tenant/admin",
  "cloud_ref": "https://172.16.0.20/api/cloud/cloud-f6c7c4ae-f7af-4090-a2c6-826b275139c7",
  "_last_modified": "1671121416017733",
  "debug_hm": "DEBUG_VS_HM_NONE",
  "resync_flows": false
}

pcap-test で取得した設定 example_caputre.json をrequest.json という名前でコピーして、 jsonないのidを書き換えたものを用意し、APIリクエストをしてみます。

$ cat request.json
{
  "url": "https://172.16.0.20/api/debugvirtualservice/virtualservice-ed9f734a-5420-4d0d-83d7-d4fe27c5e008",
  "uuid": "virtualservice-ed9f734a-5420-4d0d-83d7-d4fe27c5e008",
  "name": "copy-pcap-test",
  "tenant_ref": "https://172.16.0.20/api/tenant/admin",
  "cloud_ref": "https://172.16.0.20/api/cloud/cloud-f6c7c4ae-f7af-4090-a2c6-826b275139c7",
  "capture": false,
  "capture_params": {
    "duration": 0,
    "enable_ssl_session_key_capture": false,
    "num_pkts": 100,
    "pcap_ng": true,
    "pkt_size": 128
  },
  "debug_hm": "DEBUG_VS_HM_NONE",
  "resync_flows": false
}

$ curl -k -s -H "Authorization: token ${API_TOKEN}" 'https://172.16.0.20/api/debugvirtualservice/virtualservice-ed9f734a-5420-4d0d-83d7-d4fe27c5e008' -XPUT -H "Content-Type: application/json" -d '@./request.json'| jq .
{
  "url": "https://172.16.0.20/api/debugvirtualservice/virtualservice-ed9f734a-5420-4d0d-83d7-d4fe27c5e008#copy-pcap-test",
  "uuid": "virtualservice-ed9f734a-5420-4d0d-83d7-d4fe27c5e008",
  "name": "copy-pcap-test",
  "tenant_ref": "https://172.16.0.20/api/tenant/admin#admin",
  "cloud_ref": "https://172.16.0.20/api/cloud/cloud-f6c7c4ae-f7af-4090-a2c6-826b275139c7#Default-Cloud",
  "_last_modified": "1671122012689416",
  "capture": false,
  "capture_params": {
    "duration": 0,
    "enable_ssl_session_key_capture": false,
    "num_pkts": 100,
    "pcap_ng": true,
    "pkt_size": 128
  },
  "debug_hm": "DEBUG_VS_HM_NONE",
  "resync_flows": false
}

コピーできたら、あとはAPIもしくはCLIからcaptureをtrueにするだけで、同じ設定でキャプチャすることができます。

後片付け

キャプチャした情報をずっと残しておいても仕方がないので、消すのが良いのではないかと思います。

[admin:172-16-0-20]: > show  debugvirtualservice captures
+--------------------------------------------------------------------------+-------+--------------------------+
| Name                                                                     | Size  | Date Modified            |
+--------------------------------------------------------------------------+-------+--------------------------+
| vs_virtualservice-7b63bd3e-00e0-485f-b414-93ce8c69d0e4.1671117105.pcapng | 144   | 2022-12-15T15:11:45+0000 |
| vs_virtualservice-7b63bd3e-00e0-485f-b414-93ce8c69d0e4.1671116754.pcapng | 37084 | 2022-12-15T15:05:54+0000 |
| vs_virtualservice-7b63bd3e-00e0-485f-b414-93ce8c69d0e4.1671116565.pcapng | 37140 | 2022-12-15T15:02:45+0000 |
| vs_virtualservice-7b63bd3e-00e0-485f-b414-93ce8c69d0e4.1671116066.pcapng | 37140 | 2022-12-15T14:54:26+0000 |
+--------------------------------------------------------------------------+-------+--------------------------+
[admin:172-16-0-20]: > delete debugvirtualservice captures filename vs_virtualservice-7b63bd3e-00e0-485f-b414-93ce8c69d0e4.1671117105.pcapng
Are you sure you want to delete file: controller://vs-pcap/vs_virtualservice-7b63bd3e-00e0-485f-b414-93ce8c69d0e4.1671117105.pcapng [yes/no]:yes
[admin:172-16-0-20]: > show debugvirtualservice captures
+--------------------------------------------------------------------------+-------+--------------------------+
| Name                                                                     | Size  | Date Modified            |
+--------------------------------------------------------------------------+-------+--------------------------+
| vs_virtualservice-7b63bd3e-00e0-485f-b414-93ce8c69d0e4.1671116754.pcapng | 37084 | 2022-12-15T15:05:54+0000 |
| vs_virtualservice-7b63bd3e-00e0-485f-b414-93ce8c69d0e4.1671116565.pcapng | 37140 | 2022-12-15T15:02:45+0000 |
| vs_virtualservice-7b63bd3e-00e0-485f-b414-93ce8c69d0e4.1671116066.pcapng | 37140 | 2022-12-15T14:54:26+0000 |
+--------------------------------------------------------------------------+-------+--------------------------+

(こちらも別解があり、 https://${CONTROLLER_IP}/api/fileservice?uri=controller://vs-pcap/${filename}&purge=true などのURLにDELETEメソッドを使ってアクセスすると、キャプチャした成果物を消すことができそうです。)

まとめ

  • NSX ALBでもパケットキャプチャできます
  • sslkeylogの設定や、ヘルスチェックのみ/本来のロードバランシングの通信のみをキャプチャする設定などもあります
  • UIからもキャプチャを開始することができますが、CLIAPIをうまく使ってキャプチャすると良いと思います

基本的にはドキュメントに記載のあることが多いですが、細かい点でドキュメントに記載されていない内容があったり、VirtualServiceとServiceEngineとで差異があったりするので、キャプチャを実施する前はよく調べてから、取り組むと良いと思い、 この記事がその足がかりとなれば嬉しいです。

この記事はvExperts Advent Calender 2022 の16日目の投稿でした。

明日はHtYzの投稿となります。お楽しみに!

備忘録: Ubuntu cloud-image の初期化設定のための変数@vSphere

誤認しているかもしれないが、ubuntuのcloud-image特有 (さらにvSphere固有) の話はドキュメントなさそうで、
vSphere環境のcloud-imageにデフォルトパスワードを設定する方法も以下のstack overflowを見つけなかったら、うまく設定できないでいるところだったので備忘録として残しておく。

askubuntu.com

以下はstack overflowに書いてあることを勝手に補足しつつの適当な訳

ubuntt 22.04 (もしかしたらそれ以前も含むかもしれない) では cloud-image をovaインポートした際に、isoから初期設定のための変数を読み取ることになっていて、 ovaインポートのウィザードで入れる変数を読み取ってくれない。 なので、もし、ovaインポートのウィザードで設定した項目を利用してubuntuが初期設定されて欲しければ、stack overflowの解答にあるように、VMの設定画面から構成->vAppオプション->編集->OVFの詳細->OVF環境の転送のチェックボックスでISOイメージからVMware Toolsに変更してあげる必要がある。

(この文章は元のstack overflowのライセンスに従います。CC BY-SA 4.0 です。)

nixosにddccontrolをインストールする備忘録

https://github.com/ddccontrol/ddccontrol#usage のusage を見たら良いのだが、インストールしただけではダメで、以下の3点を行う必要がありそう。

  1. kernel moduleを起動時に読み込むようにする
  2. ddccontroleのサービスを起動するようにする
  3. root以外のユーザーはi2cグループに所属するようにする

上記はいずれもnixosの流儀に沿って行う必要があり、/etc/nixos/configuration.nix 辺りに書けば良い。

以下はテキトウな設定の抜粋

{
  boot.kernelModules = [
   ...
    "i2c-dev"
  ];
  ...
  services.ddccontrol.enable=true;
  users.extraUsers.hoge = {
    extraGroups = [... "i2c"];
 ...

pipr,vcsim,govc,jqを組み合わせてクエリ構築を簡単にする

先日、piprというツールが紹介されていて、vSphere運用のワークフローを便利にする方法を思いついたので紹介

gihyo.jp

概要

vcsim + pipr + govc + jq を使うとインタラクティブにvCenter から情報を引き出す試行錯誤環境ができる。

pipr_vcsim_govc_jq

--no-isolation

piprはbubblewrapというツールを使って環境を保護するので、ファイルを書いたり消したりができないようになっているが、govcを使う際にsessionをファイルに書こうとするので、--noisolationオプションが必要。
ただ、自分が知らないだけで、何か--no-isolationオプションを使わない方法があるかもしれない。

govcに -persist-session=true Persist session to disk [GOVC_PERSIST_SESSION] があるのでそれを有効にすると pipr の --no-isolation は不要になる。上のgifもソレに合わせて作り直している

背景/改善ポイント

vSphereをそこそこの規模で運用していると、運用している全vCenter/ESXiからサッと情報を得て引き出して現状を確認したい、ということが多々ある。
大抵のケースではgovcとjqを組み合わせたら欲しい情報は引き出せて、普段は以下のようにしている。

  1. テスト用のvCから情報を引き出すgovcのコマンドを作る
  2. コマンドが作れたら、以降無駄にvCenterのAPIを叩かないように一旦jsonに書き出しておく
  3. 書き出したjsonを使って必要な情報に絞り込むjqのクエリを考える
  4. 1と3のコマンドを繋ぎ合わせてうまく動作するか確認する
  5. 組み立てたコマンドを複数vCenterに流す仕組みを作って実行する

ただ、vCenterから取得できる情報のjsonは人間が把握するには情報量が多く、jqのクエリを作り切るのにそこそこ試行錯誤が必要で、ステップ3をやっているうちにステップ1のコマンドがよくわからなくなり... みたいなことがよくあった。
そこでvcsimを用いてAPIを何度コールしても問題無い状況を作り、piprのインタラクティブな環境でjqのクエリを試行錯誤できるようにすると, 元々のステップ1,2,3,4を1つにまとめることができるのではないか?というのが今回の紹介でした。

proxy越しにpyvmomiを利用する

レアケースだとは思いますが、pyvmomiを利用する際のclientとサーバーの間にproxyがいる場合意外と接続が面倒だったので備忘録

参考は以下

github.com

要点は以下

github.com

pyvmomi-community-samples などで使われている、pyvmomiのSmartConnect や、SmartConnectNoSLL を利用せず、 SmartStubAdapterにproxyの設定を渡し, そのAdapterをVimSessionOrientedStubに渡し、VimSessionOrientedStubをServiceIntence生成に使うという方法になっている。

pyvmomiでmoidをベースにオブジェクトを参照するメモ

vSphereのAPI経由でオブジェクトの情報を参照したいとき、かつ、moidはわかっているので
改めてAPI経由で検索する必要がない時のショートカット方法のメモです。

from pyVim.connect import SmartConnect
from pyVmomi import vim

if __name__ == "__main__":
    si = SmartConnect(
        host="ip or fqdn",
        user="username",
        pwd="password",
        port="443",
        disableSslCertValidation=True
    )
    vm = vim.VirtualMachine("moid", si._stub)
    print(vm.name)