LinuxユーザーがイジるはじめてのAzure

LinuxユーザーがAzureを使いこなせるように応援するブログです

Azure Hub&Spoke構成とAppGWとAzure FirewallとX-Forwarded-Forと私

久しぶりすぎる更新です。イミフなタイトルは生暖かい目で見てくださいw

 

昨今ではAzure vNET Hub&Spoke構成がだんだん主流になってきましたね。それもそのはずCAFやWAF(Well-Archited Frameworkのほう)といったAzure上でシステムを構成する際に気を付けるべき観点や、ベストプラクティスとなる基本構成などがオフィシャルサイトでも登場し徐々に浸透してきたからでしょうか。(そうだろう、きっと、そう思うようにしようw)

 

ただ、一方でHub&Spoke構成をとった際に、今まで通りにロードバランサを入れてバランシングしたい、といったシンプルな要求に対して、Hub&Spoke構成だとSpoke-vNET側ではUDR(User Defined Route)でスタティックルーティングを入れているからAppGWをソースIPとした通信がAzure Firewallに通信が丸投げされてAzure Firewall側でパケットドロップされちまうがな、TCPが確立しない、といった非対称ルーティングの問題が起きやすくなっていることも裏ではあります。

オフィシャルサイトでは以下のような例も出てきています。

Azure Firewall と Azure Standard Load Balancer を統合する | Microsoft Docs

 

ここではStandard LBとAzure Firewallを共存させる構成で描かれていますが、ぶっちゃけこの構成が取れるならStandard LBにPublic IPは不要です。Public Standard LBではなくInternal Standard LBにし、Internal Standard LBはSpoke-vNET側に配置します。また、Azure FirewallからInternal SLBのPrivate IPアドレスへ許可ルールを指定してあげれば済む話です。

 

いや、それだとWebサーバーを担当するエンジニア(Spoke-vNET側のWebサーバーを管理する側のエンジニアという意味)からすると、Webサーバー側に吐き出されるAccess_logにX-Forwarded-Forの値を出力させて見れないじゃん、バーストアクセスが来た時に制御したくてもお手上げじゃん、という話が出てきます。Azure Firewallを管理するエンジニアとWebサーバーを管理する人が同一であれば別にAzure FirewallのアプリケーションルールやネットワークルールをLog Analyticsやblobに配置させ通信ログデータを見ればいいぢゃん、という話になりますが、大抵どこの企業さんでも統合的なインフラリソースを管理する情報システム部門とプロジェクト部門は分かれており、お互いのニーズをマッチさせることが難しいことが起きます。

 

ということで、今回はHub-vNET側にAppGW(Application Gateway WAF v2)を最上位に構え、Azure Firewallをプライベート通信で経由させSpoke-vNET側に配置されているUbuntuのWebサーバーへバランシングし、かつX-forwarded-forのクライアントIPアドレスを出力させる、という構成をとりました。

 

参考にするサイトはここです。

仮想ネットワークの Azure Firewall と Application Gateway - Azure Example Scenarios | Microsoft Docs

 

アドレス空間がバラバラ出てくるため以下にまとめておきます。

●AppGW (WAF v2)

 Public IP Address 20.78.120.100

 Private IP Address 192.168.2.4 (AppGatewaySubnetは192.168.2.0/24)

●Azure Firewall

 Public IP Address 20.78.57.122(使いません)

 Private IP Address 192.168.0.4

●Spoke-vNET側のVM

 Private IP Address 172.16.0.14 (1台ですが複数台でもOK)

 

補足:

 AppGWでWAFはONにしています。

 Rewriteルールを設定し、X-Forwarded-For:Port番号のPort番号は削除する設定を入れています。

 Azure Application Gateway で HTTP ヘッダーと URL を書き換える | Microsoft Docs

 80 port/tcpで受け付けし、バックエンドプールにも80port/tcpで流すシンプルな構成です。

 

 Azure Firewallは、もともとSpoke-vNET側の外部通信を全て集約するように設定してあります。つまりAzure Firewallが配置されているHub-vNETとPrivate IPしか持たないVMがSpoke-vNETにあり、その間をvNET Peeringで設定してあります。また、Spoke-vNET側には0.0.0.0/0をアドレスプレフィックスとする通信は全てAzure FirewallのPrivate IP Addressへ丸投げするようスタティックルーティングのUDRをつけてあります。

 

Spoke-vNETのVMUbuntuです。nginx 1.10.3を入れてあり、nginx.confの設定でlog formatにX-forwarded-forをAccess_log行の最終カラムに追加するように設定してあります。

 

では最初から見ていき、、の前に、最初に結果から見せます。

 

結果1:ちゃんとnginxのドキュメントルートである/var/www/html/index.html(初期ページをコピー)が表示されています。

 

f:id:akazure:20210601170418p:plain

 

 

結果2:アクセスログです。106.71.xxの箇所がクライアントIPになり、X-fowarded-forの設定した通りログの最終カラムに出力されています。

f:id:akazure:20210601170623p:plain

 

結果3:Azure Firewallを経由し、ネットワークルールコレクションで許可しているログが出ています。

f:id:akazure:20210601170823p:plain

 

結果は以上ですが、それぞれの設定情報を見ていきましょう。

まずはApplication Gatewayから。

 

AppGWは作成時にPublic IPを持つ構成かPrivate IPを持つ構成か、どちらもかを選択できます。ここでは最上位をAppGWとして裏の通信はPrivate通信にしたいので両方を選んでデプロイしています。

f:id:akazure:20210601175155p:plain

 

以下はバックエンドプールの設定です。1台しか登録していませんが、ロードバランサですので本来は複数台登録します。

f:id:akazure:20210601175330p:plain

 

HTTPの設定です。特に個別の設定は入れていませんが、タイムアウト20秒はちょっと短いかもしれませんね。デフォ値ですが気になる方はTCPの再送間隔も踏まえて30秒以上がよろしいかと。

f:id:akazure:20210601175437p:plain

 

リスナーの設定です。80port/tcpをPublicで受け付ける、といういたって普通の設定のままです。

f:id:akazure:20210601175626p:plain

 

ルール設定です。今回はバックエンドプールへバランシングしたいだけなのでリダイレクトではなくバックエンドプールへ促せ、と設定しています。

f:id:akazure:20210601175731p:plain

 

X-Forwarded-Forの書き換え設定です。これをしておかないと、nginx側のaccess_logでは以下のようなログが出力され、ポート番号がX-Forwarded-Forの後ろについてきます。

192.168.2.5 - - [01/Jun/2021:05:00:42 +0000] "GET / HTTP/1.1" 304 0 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gec
ko) Chrome/91.0.4472.77 Safari/537.36 Edg/91.0.864.37" "106.73.2.64:54591"

コロンでデリミタ指定できるので文字列整形すればよいんですが、アクセス解析するたびにsed/awk/cutなどで繰り抜くのも面倒なのでこの設定を入れておきました。

f:id:akazure:20210601175904p:plain

 

最後に正常性プローブの設定です。設定時にテスト通信ができますのでAzure FirewallもSpoke-vNET側のUDRも設定済の場合はテスト通信で通ります。が、まだ設定できていない場合は、ここではテスト通信せずに設定保存だけしておきます。※ちなみにこの正常性プローブの通信が異常の場合はバックエンドプールへの通信がされません。AppGWくんは対象との通信が成功している場合に、対象への通信を促そうとしますので、ここが異常だと動作してくれません。

f:id:akazure:20210601180230p:plain

 

次にAppGWを配置したappgwsubnetのUDR設定です。Spoke-vNET側のSubnet空間(172.16.0.0/24)をアドレスプレフィックスの対象とする通信は全てAzure FirewallのPrivate IP Address(192.168.0.4)へ丸投げしろ、というルートテーブル(UDR)の設定をappgwsubnet(Hub-vNETにあるAppGW専用サブネット)に関連付けさせています。

f:id:akazure:20210601180711p:plain

 

AppGW側の設定は以上です。続いて同じhub-vNETに配置されているAzure Firewall側の設定を見ていきます。

 

Azure Firewallは簡単です。ネットワークルールコレクションに以下を追加して保存するだけです。192.168.2.0/24(AppGWのサブネット空間)から、172.16.0.0/24(Spoke-vNET側のnginxがいるサブネット空間)のアドレスへの通信は許可してね、という設定だけです。

f:id:akazure:20210601181239p:plain

 

少し補足しておくと、http(80port/tcp)なのでアプリケーションルールで設定しても問題ないかと思いますが、その場合はVM側にFQDNアクセスできるWebサーバーの設定をしておく必要があります。Azure Firewallは、もともとNSGではできなかったFQDNフィルタリングのアクセス制御ができる位置づけで登場してます。また、アプリケーションルールの設定は全てSNATされることになるため、先ほど記載したnginx側のaccess_logの行頭のIP AddressはAppGWのPrivate IPではなくAzure FirewallのPrivate IPに変わります。ネットワークルールコレクションでPrivate通信の場合はSNATが無視されます。あとネットワークルールコレクションとアプリケーションルールコレクションの定義では、ネットワークルールコレクションが優先されます。

 

では最後にSpoke-vNET側を見ましょう。

Hub&Spoke構成ですので、もともと0.0.0.0/0をアドレスプレフィックスとした宛先の通信は全てAzure FirewallのPrivate IPへ行け、とスタティックルートの設定が入っています。ここでは192.168.2.0/24(appgwsubnet)をアドレスプレフィックスとした宛先の通信は全てAzure FirewallのPrivate IPへ行け、という設定を追加しています。

f:id:akazure:20210601181957p:plain

 

ここも補足しておくと、ルートテーブルは複数定義することができますが、複数定義された場合はロンゲストマッチで優先度のルーティングが判定されます。0.0.0.0/0は、ほぼほぼ優先度最低の定義になるためappgwsubnetの定義が優先判定されます。

 

//おまけ情報

Azureでのルーティング判定は以下のサイトに記載されています。

Azure 仮想ネットワーク トラフィックのルーティング | Microsoft Docs

UDR>BGP>システムルート(NSG)の順で優先度が決まるとありますが、正確には以下です。

● Azureの機能についている専用経路>UDR>BGP>システムルート の順です。

Azureの機能についている専用経路って何やねん!って話だと思いますが、例えばvNET Service Endpointなどが該当します。これがvNETに設定されているとUDRが設定されていようが関係なくvNET Service Endpointの経路で通信されます。(vNET Service Endpointはログが出ない仕様なので、制御できない&受け側でIP Addressが検知できるだけですが)

// おまけ ここまで

 

最後にnginx側の設定です。Azureの箇所ではないので参考まで。

/etc/nginx/nginx.conf のx-forwarded-forの設定箇所です。(上がhttp ディレクティブの箇所、下がserver ディレクティブの箇所)

access log定義の最終行にlog_formatで定義した main を入れ忘れないように!

f:id:akazure:20210601183756p:plain

 

f:id:akazure:20210601184048p:plain

ちなみにnginx 1.10.3を使っています。ちなみにnginxのX-Forwarded-Forの設定はngx_http_realip_moduleというモジュールを使っているので、nginx -Vのコマンドでこのモジュールがincludeされているか確認してから設定してくださいね。

 

長々といつも通り長文になりましたが、最後です。

最近Azure Firewall Premiumなるものがプレビューで出てきています。上記はAzure Firewall標準のものを試していますが、違いとして1番のメリットはIDPS(IDS/IPS機能)がついていることでしょうね。

 

ここでAzure Firewall Premiumを使い、Spoke-vNET側からの外部通信をAzure Firewall Premium経由で集中管理させているのが主流になってくると思いますが、上記のようにAppGWでWAFをONにしていると、クライアントからの通信はAppGW側でOWASPのWAFエンジンルールで判定され、丸投げされたAzure Firewall Premiumでも同じくIDPSがONのため2重の防御チェックがされレイテンシやhttpレスポンスの遅延など様々なことが想定されます。そうならないように、Azure Firewall PremiumではIDPSバイパス機能がついており、検査対象外とするセグメント情報を登録しておくことで2重のチェックをしないようにすることができます。

Azure Firewall Premium プレビューの機能 | Microsoft Docs

が!!記載されている通りTLS通信であるhttps通信の場合はこの機能はスルーされる既知の問題があるため、そこは注意しましょう。(まだ、ぷれびゅーですからねw)

 

いじょ。

ドメインの異なるアカウント同士で vNET間を結ぶ

長くエンタープライズ顧客を相手にしていると単純なLift&Shift(オンプレからの移行)もあるけど、サブスクリプション間で移行したいといった要件も出てきますね。慣れている人は「Azure ADテナントが違って」とか「サブスクリプション移行をしたくて」といった言葉が出てきただけで、変な汗が出てくること間違いないw(私だけ?汗)

 

それもそのはずで、契約形態や移行したいシステムリソースによって対応方法もいくつかあり、簡単なものから複雑なもの、リスク込みのものや金かけてでもダウンタイム最小限にしたいものまであるから、といった実情があるからだろう。

 

例えばこんなパターンが考えられる。

1)EA契約のサブスクリプションからCSP契約のサブスクリプションへ移行したい

2)EA契約同士、もしくはCSP契約同士でサブスクリプションを移行したい

3)無償利用で作ったものをEA契約もしくはCSP契約へ移行したい

4)同じ契約内で異なるサブスクリプションへ移行したい

 

1~3はそもそもAzure AD テナントが変わる前提で、4)だけ変わらない。あと2のEA契約同士というのは、本社とグループ会社で、というのが多いが、CSP間で、とかはそもそもビジネスの主管を変えるって話になるので技術の前に話し合うべきことが多いことも押さえておこう。まぁ平たく言えば、hogehoge.comで管理されている環境で作ったシステムリソースをfugafuga.comで管理されているシステムリソースへ移行したいのか、hogehoge.comで管理されている環境は変えずに別のサブスクリプションへ移行したいのかって違いがあるということ。

 

なーんだ、システムリソースはあるんだから契約情報だけ差し替えてくれればオッケーっす!マイクロソフトさんよろ!なーんて考えていると甘い。マイクロソフトはセキュリティの会社と言われているぐらいなので、締結された契約をそんな安易に企業間の都合だけで対応できるようになんて仕組みにしていない。たぶんw

 

とはいえ技術的な側面ではしっかりとお互いの認証を行い双方での疎通が取れる状態にすることはできるので、今回はその1つである「異なるAzure ADテナントの異なるサブスクリプション間のvNET同士をピアリングし、プライベート間で通信できるようにする」ということを試した結果を記事にしておく。

 

やりたいことは簡単。koiketmp1@outlook.jp(マイクロソフトアカウント)のVMからkoiketmp1@outlook.com(マイクロソフトアカウント)のVMへプライベート接続でデータを送りたい。つまりoutlook.jpとoutlook.comの仮想ネットワーク(vNET)間をプライベートピアリングして接続したい、ということ。

※社員用サブスクは制限かかっているようなのでできまへん。ちーん。

 

ちなみに以下のオフィシャルサイトを参考にしてほしい。

https://docs.microsoft.com/ja-jp/azure/virtual-network/create-peering-different-subscriptions 

なんだか小難しい手順が書かれているが、簡単に言うと以下となる。

 

1)Azure ADで「ゲストユーザー招待」を完了させておけ

2)お互いのvNETで相手側のアカウントが操作できるよう

  「ネットワーク共同作成者」をRBAC(IAM)で相互で設定しておけ

3)ピアリング設定をする際は「リソースID指定」で認証してやれ

 

ということでやってみよう。

 

まず「outlook.jp」側のリソースがこれ。

f:id:akazure:20200615071738j:plain

「cent-vnet」という名前のvNETに「cent0001」という仮想マシンが 172.16.0.4 というプライベート用のNiCが付いた状態でいる。

 

もう片方は「outlook.com」側のリソース。

f:id:akazure:20200615081704j:plain

「cent-vnet2」という名前のvNETに「cent0003」という仮想マシンが 192.168.0.4 というプライベート用のNiCが付いた状態でいる。

 

とりあえずAzure Portalの左ペインからAzure Active Directoryを選択。

f:id:akazure:20200615082022j:plain

 

「ユーザー」から「+新しいゲストユーザー」を選択し、以下メールアドレスを入力して「招待する」を押す。

 

f:id:akazure:20200615082327j:plain

 

 

しばらくすると上で入力されたメールアドレスへ以下のようなゲストユーザー招待のメールが届く。

 

f:id:akazure:20200615082708j:plain

 

メール内にある「招待の承諾」を押すことブラウザから認証が走るので、招待された側は自分(outlook.jp側)のアカウントとパスワードを入力して招待を受ける。

 

この作業はoutlook.jp側からoutlook.com側への招待も同じく実施する。つまりそれぞれ1回ずつ招待し計2回やる。

 

反映に少し時間がかかるかもしれないが、以下のような画面がAzure Active Directoryのユーザー一覧に出力されることを確認しよう。

 

f:id:akazure:20200615083124j:plain

※これはoutlook.com側のアカウントでログインした時の画面。koiketmp1@outlook.jpのアカウントがゲストユーザーとして反映されていることが確認できる。

 

これでとりあえずAzure AD側では双方のアカウントが認識された状態となる。次はそれぞれのvNET(cent-vnetとcent-vnet2)にて、RBAC(IAM)を操作し、お互い「ネットワーク共同作成者」のロールを付与する。つまりお互いがお互いのvNETに対して作成や更新ができる状態にしよう。

 

outlook.jpとoutlook.com両方のvNETに、以下のようにアクセス制御(IAM)の設定を入れて保存する。

f:id:akazure:20200615084606j:plain

保存が正常に終わると「アクセス制御(IAM)」の「ロールの割り当て」画面に、指定したアカウントとロールが一覧に表示されるので確認しておこう。ちなみにRBAC(あーるばっく)とはRole-Based Access Contolの略で、この「アクセス制御(IAM)」を指します。

 

双方(outlook.jpとoutlook.comの両方)でのRBACの設定が終われば最後はピアリング。

 

ここまでで、以下の1)と2)が終わった状態。

1)Azure ADで「ゲストユーザー招待」を完了させておけ

2)お互いのvNETで相手側のアカウントが操作できるよう

  「ネットワーク共同作成者」をRBAC(IAM)で相互で設定しておけ

3)ピアリング設定をする際は「リソースID指定」で認証してやれ

 

最後に3)を行うが、先に許可を許す側のvNETの「プロパティ」を選択し、「リソースID」をコピっておこう。

f:id:akazure:20200615084151j:plain

※2画面で別々のアカウントでログインしてやっていると、サインアウトしないといけない部分などがタイミングによってあるので、ここからは1画面でやるのがよい。

 

お互いのリソースIDをメモ帳などにコピーしたら、vNETの「ピアリング」から「追加」を選ぶ。

f:id:akazure:20200615090043j:plain

 

Peeringの設定ではピアリングの名前は適当(任意)でOK。リソースIDを知っている、というチェックボックスにチェックを入れる。

f:id:akazure:20200615090326j:plain

先ほどメモ帳でコピーしたリソースIDを、べたっと張り付けるとディレクトリがプルダウンで選択できるようになるため、該当のっディレクトリを選択して「認証」を押す。

上の画面ではoutlook.jp側のアカウントでログインしoutlook.jp側のvNET(cent-vnet)のピアリングを追加する画面。なのでoutlook.com側のリソースIDとディレクトリを指定してあげて認証する。認証が終われば1番下のOKボタンを押す。

 

以下のように設定したピアリング項目がvNETのピアリング画面に表示されればOK。

f:id:akazure:20200615090746j:plain

※ピアリング状態が「開始済み」なので、まだ片方向でしか設定が完了していないことを示す。双方向でピアリングが確立していると「接続済み」というステータスになるため、現時点ではまだvNET Peeringは終わっていない。

 

ということで今度はもう片方側でログインして同じようにピアリングの設定を行おう。

f:id:akazure:20200615091450j:plain

 

こんな感じで「接続済み」ステータスになっていれば万事OK!!!

 

ちなみにこんなエラーが出てピアリングがうまく行かない時がある。

f:id:akazure:20200615100458j:plain

 

この場合はピアリング先のアカウントがログイン状態になっている、とか、Azure AD側の反映が遅れている、などの原因が考えられるため、1日おいて片方のアカウントだけでログインしてもう一度設定することをお薦めする。

 

では最後に仮想マシン間でチェックしよう。

f:id:akazure:20200615092449j:plain

f:id:akazure:20200615092503j:plain

 

outlook.jp側もoutlook.com側も、どちらのVMからも双方向でpingチェックの通信が通ることが確認できた。ってことでファイルをコピーしてみよう。

 

172.16.0.4 のVMから192.168.0.4 のVMへ scp でファイルコピー。

f:id:akazure:20200615095856j:plain

f:id:akazure:20200615095808j:plain

 

すばらしい。

 

ってことで、異なるアカウント同士で異なるネットワークセグメント間のVM同士を仮想ネットワーク単位でピアリングすることができました。VMイメージのvhdをエクスポートしてインポートしたり、VMを新規作成してデータだけblobへ配置して吸い上げる、Azure Filesで双方でマウントする、などなどいろんな移行方法はあるけど、サーバー間を繋げることでプライベート網を使った接続が簡単にできるので、今後の移行方法の1つとして考えてもらえればと。

 

ストレージ診断ログのblobアクセスログを解析する

あー、ついに禁断の地へw

 

パブリッククラウド全般に言える共通点ですが、データが外からクラウド

入ってくる分には無料だけど、クラウド内の同一リージョンもしくは同一

データセンターから外に行く分(主にダウンロードやリスト表示等)には

課金される、という法則は一般的ですね。

※ベンダーから見ると簡単に無料でデータが他社に引っ越されてしまうのは

 いかんぜよ、という昔からの戦略というか怖い意志がありますからねw

 

もちろんAzureでも同様なのですが、よくある話としては以下のような

ケースです。EAポータル等から課金レポート(csv)をダウンロードして

見たり、Azure Cost Managementを使ってリソースタグの集計結果グラフ

などを見ていると、ある日を境に突然blobのデータ転送料がドエライこと

になっててビビる!というものです。特にこれは複数の部門やチームに

跨ってAzureをご利用頂いている時に起きますかね。

 

そしてエンタープライズオーナーやサブスクリプションオーナーが

ぎゃあぁ!なんだこれ!今月分くっそ高いぞ!てか誰だよこんな使い方

してるやつはぁ!っと目くじら立て原因の特定に入っていきます。

 

さて、ぼちぼち本題にはいっていきますが、もちろんストレージ

アカウントのストレージ診断をONにしていないと細かい原因がわかり

ません。以下Azure Portalの設定個所です。オンにしておきましょう。

 

f:id:akazure:20200430081503j:plain

 

ストレージ診断をONにしておくと、このストレージアカウント

に自動的に$logs というコンテナが作成されます。先頭が$で始

まっているので、隠しディレクトリという扱いのようです。

 

この $logs 配下の階層は、/YYYY/MM/DD/HHmm/000000.log の

ようになっていますが、アクセスがないと何も作成されません。

※mmの部分は固定で00になるので「分」までは厳密なディレ

クトリ管理はしていません。

※000000.log は時間をおいてアクセスすると000001.log のように

 インクリメントされたファイル名として複数に分けられます。

 

この000000.logファイルのフォーマットは以下に記載があります。

https://docs.microsoft.com/ja-jp/rest/api/storageservices/storage-analytics-log-format

 

つまりこの000000.logのテキストファイル(アクセスログ)を

解析すれば何か原因がつかめそうですが、いかんせん、とても、

や、、ややこしいw

 

ということでLinuxな人ならではの解析方法をいくつか紹介しておきます。

とりあえずblobfuseでblob/$logsをマウントし、コマンドが実行できる

状態にしましょう。

 

以下を参考にマウントします。(rpmでパッケージをダウンロードする

際のOSのバージョンに注意してください)

https://docs.microsoft.com/ja-jp/azure/storage/blobs/storage-how-to-mount-container-linux

 

※注意

 CentOS7でこの手順を行うと yum install -y blobfuse を実行した時に

 以下のエラーが出る場合があります。

 

 ---> Package fuse-libs.x86_64 0:2.9.2-11.el7 will be installed
--> Finished Dependency Resolution
Error: Package: blobfuse-1.0.2-1.x86_64 (packages-microsoft-com-prod)
           Requires: libgnutls.so.26(GNUTLS_1_4)(64bit)
Error: Package: blobfuse-1.0.2-1.x86_64 (packages-microsoft-com-prod)
           Requires: libgnutls.so.26(GNUTLS_2_10)(64bit)
Error: Package: blobfuse-1.0.2-1.x86_64 (packages-microsoft-com-prod)
           Requires: libgnutls.so.26()(64bit)
 You could try using --skip-broken to work around the problem
 You could try running: rpm -Va --nofiles --nodigest

 

 この場合は libgnutls.so.26 という共有オブジェクトがねぇ!って

 言われてますが、yumのキャッシュが邪魔している時があるので

 yum upgradeした後なんかは以下の手順でクリアにするとblobfuseが

 インストールできます。

 

# yum remove packages-microsoft-prod-1.0-1.el7.noarch
# yum clean all
# rm -rf /var/cache/yum
# rpm -Uvh https://packages.microsoft.com/config/rhel/7/packages-microsoft-prod.rpm
# yum install blobfuse fuse -y

 

 あとストレージアカウントの設定ファイルとして用意する

 fuse_connection.cfg のコンテナ名は、普通に $logs と入れます。

 こんな感じで。(ストレージアカウントのキーはマスクしてます)

f:id:akazure:20200430091844j:plain

 

無事 $logs をマウントできれば

以下のように操作できる状態になっていると思います。

f:id:akazure:20200430092047j:plain

 

ここまでで取り合えず解析する準備ができました。いよいよ本ちゃんです。

なにやらデリミタ(区切り文字)は「;」セミコロンになっているようです。

 

●blobへのアクセスが集中している時間帯を特定しよう

# cd /${mount_point}/${blobdir}/blob/YYYY/MM

# awk -F";" '{ print $2 }' ./*/*/*.log | cut -f1 -d":" | sort | uniq -c | sort -nr

 出力結果サンプル

  167 2020-04-29T21
     42 2020-04-29T23
     1 2020-04-29T03

 ⇒ 4/29の21時が1番アクセスが多い時間だとわかります。

   4/29の21時はUTCです。+9時間のJSTに読み替えましょう。

   あと診断ログの日付フォーマットはISO8601になってます。

   わかりやすい日付フォーマットにする場合は、python

   phpでやったほうが楽かと。

 

●4/29 21時のログから何のリクエストが多いのかを特定しよう

# cd /${mount_point}/${blobdir}/blob/YYYY/MM/DD/2100

# awk -F";" '{ print $3,$4 }' ./*.log | sort | uniq -c | sort -nr

 出力結果サンプル

     89 ListBlobs Success
     46 GetBlobProperties BlobNotFound
     14 GetBlobProperties Success
      6 BlobPreflightRequest AnonymousSuccess
      3 GetBlob Success
      2 ListBlobs SASSuccess
      2 GetBlobProperties SASSuccess
      1 ListContainers Success
      1 ListBlobs OAuthAuthorizationError
      1 GetContainerServiceMetadata Success
      1 GetContainerProperties Success
      1 GetBlobServiceProperties OAuthAuthorizationError

 ⇒ ListBlobsの成功(Success)が 89 回あったことがわかります。

 

●どのURL(blob object)へのアクセスが多いかを特定しよう

# awk -F"\"|&" '{ print $2 }' ./*.log | sort | uniq -c | sort -nr

 出力サンプル

    170 https://share001.blob.core.windows.net:443/$logs?comp=list
     13 https://share001.blob.core.windows.net:443/$logs/blob/2020/04/29/2100/000002.log
      9 https://share001.blob.core.windows.net:443/$logs/blob/2020/04/29/2100/000003.log
      9 https://share001.blob.core.windows.net:443/$logs/blob/2020/04/29/2100/000001.log
      9 https://share001.blob.core.windows.net:443/$logs/blob/2020/04/29/2100/000000.log
      8 https://share001.blob.core.windows.net:443/$logs/blob/2020/04/29/2100/.hoge.swp
      6 https://share001.blob.core.windows.net:443/url?restype=container
      6 https://share001.blob.core.windows.net:443/$logs/blob/2020/04/30
      4 https://share001.blob.core.windows.net:443/$logs/blob/2020/04/29/2300/000001.log
      4 https://share001.blob.core.windows.net:443/$logs/blob/2020/04/29/2300/000000.log

 ⇒ $logs のリスト表示が多いのがわかるが、今操作しているblobfuseの

   ログも出力されるので、grep -v "\$logs" を追加すると良いかも。

   あとURL抽出だとクエリ内で使われる「&」が大量に出るため

   awkのデリミタには「&」も追加して検索。

 

●どこのソースIPからのアクセスが多いかを特定しよう

# awk 'BEGIN {FS="\"";OFS="\""};{gsub(";", "",$2);print}' ./*.log | awk -F";" '{ print $16 }' | cut -f1 -d":" | sort | uniq -c | sort -nr

 出力サンプル

  266 172.19.0.4
    12 106.73.2.64
      3 10.81.128.71

 ⇒ 172.19.0.4 からのアクセスが266回と1番多いことがわかる。

   いきなりawk文がややこしくなったが、デリミタとしていた「;」

   がダブルクォート内のURLにも利用されるためこのようにした。

 

●特定の時間にGetBlobされたデータ容量の合計を出そう

grep "2020-04-30T00:" *.log | grep ";GetBlob;" | awk 'BEGIN {FS="\"";OFS="\""};{gsub(";", "",$2);print}' | awk -F";" '{ sum += $21 } END { print sum }'

 出力サンプル

345161

 ⇒ 2020年4月30日の09時(JST)に、約345kbyteのデータ量が

   GetBlobのリクエストで取得された総データ量。

 

●おまけ:利用しているblobのAPIバージョンを全て特定する

 # awk 'BEGIN {FS="\"";OFS="\""};{gsub(";", "",$2);print}' ./*.log | awk -F";" '{ print $17 }' | sort | uniq -c | sort -nr

  出力サンプル

  266 2017-04-17
      6 2019-10-10
      6 2015-02-21
      3 2019-07-07

  ⇒ 2017-04-17のAPIバージョンが1番利用されていることがわかる。

 

とまぁsedでやると正規表現コテコテになるのでawkでやってみたけど

awkでも結構難解になりましたね(汗

 

もちろん診断ログはLog Analyticsへ飛ばすこともできるので

お金かけてkustoで検索した方が楽だと言う方はそっちをお薦めします。

 

今回はこんなところで。

Azure DB for PostgreSQL の バックアップ・リストアを試す

タイトルにあることを考えると、大抵以下のサイトに行きつくと思う。

https://docs.microsoft.com/ja-jp/azure/postgresql/concepts-backup

 

が、なんだかよく読んでいると???になってくることもw

 

フルバックアップは毎週で差分バックアップは1日に2回行ってて、でもリストアする時は日時だけじゃなく時分秒まで設定を入れてポイントインタイムリストアできると。。はてww 復元されたデータのタイミングはどこの静止点になるんだ?と。

 

オンプレでは主にpg_dumpやpg_dumpallをcronに登録しておいて、決まった日時や曜日、1日に1回、などなどデータベースのダンプバックアップファイルを別のNASへ配置しておき、何か想定外のことが起きればNASにあるdumpファイルから空のPostgreSQLの環境へスキーマ毎復元、みたいなことをやると思います。

 

が、Azure DB for PostgreSQLでは、「決めらている静止点」での復元になるのか、「選択可能な静止点」での復元なのかが、モヤっとしますね。

 

ということで試しましょ。

 

 まずはDB for PostgreSQLをデプロイ。

f:id:akazure:20200323151202j:plain

※USリージョン使っているのに特に意味はないです。

 

つづいてバックアップ設定。

f:id:akazure:20200323151405j:plain

※デフォではバックアップの保有期間が7日になっていたので、14日に変更。

※今回は同一リージョン内での復元をするのでローカル冗長。

 

接続元は特定のVMからにしたいのでDB for PostgreSQLに付いているファイアウォール機能を使い、SSL接続は無効に。(手っ取り早く疎通取りたかっただけ)

f:id:akazure:20200323151600j:plain

 

で、とりあえずDB側の設定は終わりで、続いてクライアント側からは定期的にデータをぶち込むスクリプトを用意。

※パスワードは環境変数でPGPASSWORDに入れてある。

デバッグ用のechoは無視してOK)

 

#!/bin/sh

COUNT=1
while :
do
        DATENOW=`date`
        WORD="insert into test values (${COUNT}, '${DATENOW}');"
        psql --host=postgresqltest001.postgres.database.azure.com --port=5432 --username=azure01@postgresqltest001 --dbname=postgres -c "${WORD}"
        echo "${COUNT} , ${DATENOW}"
        echo "${WORD}"
        COUNT=`expr ${COUNT} + 1`
sleep 300
done

 

ちなみに事前にpostgreSQL 11はインストールしてあるのでpsqlのパスは通ってる前提。

yum -y install https://download.postgresql.org/pub/repos/yum/reporpms/EL-7-x86_64/pgdg-redhat-repo-latest.noarch.rpm

 

yum -y install postgresql11-server postgresql11-contrib

 

で、DB for PostgreSQLへ接続して放置してあったデータの一覧を見るとこんな感じで見える。

f:id:akazure:20200323152409j:plain

※最初の1,2,1,2,3は何度かリランした残骸なので無視してw

 

こんな感じのデータが約8日間、ずっと5分毎にinsertされまくってる状態。

ということで、2020年3月20日の15:00の時点で復元を試みてみよう。

 

リストアは簡単。postgreSQLのOverviewにある上段の「復元」タブを押して日付と復元した後の新しいpostgresサーバー名を入力するだけ。

f:id:akazure:20200323154229j:plain

 

復元が終わるとこんな感じでDB for PostgreSQLのリソース2つ目ができます。

f:id:akazure:20200323155132j:plain

 

おしおし、ってことで早速DBに登録されている最新レコードが何時何分のものなのか見たくなるのですが、復元した直後の状態はファイアウォール設定がコピーされない状態で復元されるので、再度設定してあげます。

不便じゃーって思う人も多いかもしれませんが、仮にSQLインジェクションが発見されて落ちてしまったDB for PostgreSQLを復元させると、復元と共にまた脆弱性をつつかれる、みたいなことにならないようネットワークレイヤの設定とDB設定は別にされているのかもしれませんね。

 

f:id:akazure:20200323161136j:plain

 

おおう、すばらしい。直前まで書き込んでいたデータがちゃんと反映されてますね。あ、ちなみにpostgres側のユーザーやパスワードはリセットされませんので、そのまま今までのものが引き継がれます。

 

いや、でもたまたま15:00にバックアップ取られていただけでタイミングよかったからじゃね?という小悪魔な自分が囁き始めたので、今度は3月18日の16:50での復元を試してみました。結果は以下。

f:id:akazure:20200323170735j:plain

 

すんばらしい、よくできている。

てことで、バックアップは設定した直後からバックアップはされますが、保持期間の間であれば任意な時間で復元が可能ということがわかります。 

Azure Firewall の Inbound 制御について

Azure Firewall をアウトバウンド制御するFWとして利用する記事は割と多めですが、インバウンドだって制御しまっせ、という記事を少し記載しておきますかね。

 

Azure Firewall はvNET(仮想ネットワーク)に所属させますので、デプロイするとグローバルアドレスとプライベートアドレスの代表をそれぞれ1つずつ持ちます。

 

f:id:akazure:20200323111157j:plain

 

今回の構成としてはAzure Firewallを上段に、下段にはILBとしてApplication Gateway(AppGW) を構え、AppGW の下段にVMを配置してあります。VMはnginxのページが表示されるよう80 port/tcpでlistenしてある状態です。

 

ではAzure Firewall のルールを見てみます。

f:id:akazure:20200323112310j:plain

 

ルールでは「NATルールコレクション」「ネットワークルールコレクション」「アプリケーションルールコレクション」とありますが、ここではNATルールコレクションを選択します。

 

f:id:akazure:20200323112517j:plain

このような設定をしています。

1つ目のルール名natruleSGTは、オンプレから外に出る際に利用されるグローバルアドレス(167.220.233.219)をソースIPとする80port/tcpのリクエストは、Azure Firewallのグローバルアドレス(52.156.53.53)からAppGWのプライベートアドレス(172.19.3.254)へ変換して通信を促せ、というルールです。いわゆるグローバルアドレスをプライベートアドレスと通信させたいDNATとしてよく利用される機能ですね。

 

2つ目のルール名natruleOtherは、上記以外をソースIPとする80port/tcpの通信は、Azure Firewallのグローバルアドレス(52.156.53.53)から、別vNETに配置したVMのPIP、グローバルアドレス(104.41.185.108)へ通信を促せ、というルールです。

 

f:id:akazure:20200323113253j:plain

Public to Public へNATした場合は上記のnginxのページが表示されていますね。

f:id:akazure:20200323114119j:plain

一応VM側から見たソースIPを確認するとAzure Firewallのグローバルアドレスとして持つ「52.156.53.53」から来ていると見えます。

 

一方AppGWのILBへDNATした場合は、こんな感じで見えます。

f:id:akazure:20200323115254j:plain

VM側から見るとILBとして立てたAppGWから来ていると見えます。

f:id:akazure:20200323115316j:plain

 

といった感じで、外部から来たリクエストをAzure Firewallから内部、外部へ促すことができます。(IPv4レベルですが)

 

 

また、以下のようにネットワークルールコレクションで設定すると、特定の外部ソースIPから来たリクエストは拒否する、といったこともできます。

f:id:akazure:20200323120419p:plain

 

以上、Azure Firewallでインバウンド制御する場合の内容でした。

Azure Linux VMでのsudo脆弱性対応(CVE-2019-14287)

なんだかsudoに脆弱性が発覚し騒がれつつありますね。

 

それもそのはず、CVSSのスコアが「7.0」と警告レベルを超えて

重要だぜ、というスコアになっているのと、一部分だけを読むと

rootユーザー権限が乗っ取られる、と解釈できる部分があるから

でしょうね。

参考:https://access.redhat.com/security/cve/cve-2019-14287

 

いや、そもそもCVSSってなんぞ?スコアって?という方は

以下を参考に。

https://www.ipa.go.jp/security/vuln/CVSS.html

 

まぁいわゆる脆弱性レベルって一概にいっても様々な軸と捉え方が

あるわけで、そこを包括的にオープンな情報として各機関が軸や

考え方をまとめたスコアですよ、ということ。つまりこのスコアを

本人や企業ががどう扱おうと自由なわけですが、エンジニアとしては

今やるべきなの?後回しでもいいの?スルーしていい?というモノサシ

がほしいですよね。

 

そういう意味では今回のは重要レベルのスコアなので踊らされてしまう

のも無理はないですが、ちゃんと中身を読むとsudoをuser毎に厳密に

権限管理されている場合、root権限を与えていないuserでもroot権限で

実行できてしまうセキュリティホールが見つかったよ、というもの。

 

言い方を変えるとsudo = root という扱いでしか利用されていない

Linux環境であれば何の問題もないわけです。具体的にはこんな感じ。

 

通常利用

$ sudo -u#${uid} command

 ⇒ uidを使ってcommandを実行

 

問題となった利用方法

$sudo -u#-1 command

もしくは

$sudo -u#4294967295 command

 ⇒ -1も4294967295も存在しないuidだが、バグで0と解釈されて

   しまい、かつ存在しないuidのためパスワードデータベース

   への参照もされずPAMも制御外となる、そしてuid=0はroot

   のuidのためroot実行されちゃう、というw

 

で、じゃあどうすんだよ、という話に移る。

今回問題があるsudoのバージョンは1.8.28より前のバージョンが該当

するとのこと。

参考:https://bugzilla.redhat.com/show_bug.cgi?id=CVE-2019-14287

 

で、Azure VM(やっとここでAzureな話w)のRhelCentOSyum upgrade

/update で対応できるかなー、っと思ったけどまだrepositoryに展開されて

いない模様。

 

# yum list sudo
Loaded plugins: fastestmirror, langpacks
Loading mirror speeds from cached hostfile
 * epel: ftp.riken.jp
Installed Packages
sudo.x86_64                                        1.8.23-4.el7                                         @base
#

 ※あ、epel-releaseもインストール済でつ。

 

ということでsudo本家の以下サイトから1.8.28をtar.gzでダウンロードしま

しょ。

https://www.sudo.ws/stable.html

 

ソースからの展開なのでパッケージ展開と比べてダルいですがコンパイラ

必要なのでgccあたりはあらかじめyum installしておいてください。

tar.gzを展開するとこんな感じ。

 

f:id:akazure:20191017132019j:plain

 

./configure、make、make installして反映しましょ。

※rootアカウントが利用するsudoはデフォで/bin/sudoになってます。

 このソースからmake installで展開されるのは/usr/bin/sudoのs-linkと

 /usr/local/bin/sudoの実態なので、混乱しないように。

 自分は混乱するので/bin/sudoを/usr/local/bin/sudoの実態を参照する

 ようにS-linkはっておきましたが。

 

で、例の問題となった脆弱性をつつくコマンドを叩いた結果が以下。

f:id:akazure:20191017132330j:plain

 

いいね!ちゃんとエラーとなって解釈できているようでつ。

対応方法と対応した後の確認までの記事があまり見つからなかったので

敢えてこの記事を書きましたが、もう一度言っておくとsudo = rootと

いうシンプルな扱いをされている環境では対応の至急性はないです。

repositoryに反映されたら定期的にyum upgradeかけてるし問題なす!

な話です。

 

ちなみに今回はCentOS(ディストリビューションRHELと同様)で

試しましたが、JPCERT CCが各ディストリビューション毎の記事を

まとめられていたので、ご参考まで。

 

https://www.jpcert.or.jp/newsflash/2019101601.html

 

 

いじょ

特定のログからWebシナリオテストを自動実行する

前回の記事内容の派生形となります。

 

 

実現イメージは以下ですが、前回との変更点は赤文字/赤線の部分です。

 

f:id:akazure:20190327112808j:plain

 

変更点の補足です。

④ Azure Automation Runbook Python2 から、事前に用意しておいた

  Blob ファイルを取得しています。ファイルの中身は以下です。

 

https://www.google.com/
https://www.microsoft.com/ja-jp
https://hogehogekoike.com
https://aws.amazon.com/jp/

 

 ここでは正常にHTTPの応答を返すか否かの対象となるサイトURLを

 1行ずつ記載しています。

 (3行目のhogehogekoike.comは存在しないURL)

 

⑤Blob ファイルに記載されたURLに対して1行ずつHTTPのスポンスを

 取りに行っています。

 

⑥各URLのHTTPレスポンスの結果をメールの本文に入れてメール通知します。

 

ある程度想像できた方も多いと思いますが、24/365の稼働を提供している

自社のWebサービスにおいて、裏側で構成されているWebのシステムから

致命的なログが確認されたとき、多くのエンジニアは監視オペレータから

の呼び出しや緊急連絡網からの確認依頼、障害対処依頼がきますよね。

 

担当エンジニアは原因の究明や一次対処、サービス正常性確認などを

実施されていると思いますが、これをAzure PaaSを使ってすべて自動化

してしまおう、という一例です。営業時間内のオペレーションであれば

特に問題はないのですが、夜間や休日にサービス影響がない監視連絡ほど

出たくない電話はないですからね(汗

 

では本編に入ります。

 

 

前回の記事も試されて、かつ、せっかちな方(笑)は、以下の

pythonコードをAzure Automation Automation Runbook (python2) に

べたっと張り付けましょう。

(標準ライブラリのみで動作するように記載しています)

 

https://github.com/akkoike/sample/blob/master/automationpython.py

※{}の個所のみ、ご自分の環境に合わせた文字列を入れてください。

例:

storage_resource_group = "{RESOURCE_GROUP_NAME}"

⇒ storage_resource_group = "hogehoge-rg"

 

47行目まではAuto Credentialの部分ですので変更不要です。

49行目から68行目まではblob storageの 定義とインスタンス化です。

コメントアウト部分はデバッグ用で残しておきました。

 

69行目から79行目までがURLを1行ずつ持ってきてHTTP(GET)を

発行し、その結果を変数に代入している部分です。

単純なBasic認証ならrequest.post(url,auth=('user','passwd'))と

指定すればいけそうですね。

 

80行目から最後の101行目まではSendGridへメール送信する部分

です。サブミッションポート(587)のTLS通信でメール送信しています。

 

では試してみましょう。

 

まずLog Analytics(Azure Monitor Logs)では、以下のクエリを

発動条件にしています。

 

f:id:akazure:20190327131730j:plain

Syslog
| where Computer == "centlog002"
| where SyslogMessage contains "critical error"

 

Runbookの発動をAzure MonitorのAlert ruleで指定します。

f:id:akazure:20190327131856j:plain

 

閾値は0件「より大きい」(つまり1以上)にしています。

 

では対象VMのcentlog002にて、「critical error」をrsyslogdへ飛ばしてみます。

 

f:id:akazure:20190327150038j:plain

logger -p authpriv.crit "web service critical error , please check"

 

しばらく待つとLog AnalyticsのクエリログにHITし、Azure Monitorの

Alert Ruleに引っかかり、runbook pythonが処理され、以下のような

メールが飛んできます。

f:id:akazure:20190327150305j:plain

 

各URLのレスポンス結果がメール本文に入っていますね。

もちろんすべて200 OKならメールを通知させる必要もないので

その場合はPythonの中身を自由にカスタマイズしてください。

 

最後に、補足です。

Webのシナリオテストを実行する、というだけなら他にもよさげな

ものがいくつかあると思います。以下はご参考まで。

 

1、Azure DevOpsのCI/CD機能を使う

  今回は試していませんが、gitへnull commitするトリガーで

  いくつかのサイトへのHTTPレスポンスを計測する、といったことも

  できると思います。

 

2、Logic Appsを使う

  以下のように組み立ててれば、簡易版のシナリオテストも

  実行できました。

 

f:id:akazure:20190327151014j:plain

f:id:akazure:20190327151027j:plain

 

 ※ただ、確認したいURLが数百あると、1つ1つアクションを追加する

  のも面倒ですし、blobに置いたURLリストをJSON形式に変換する必要

  がありそうだったため、今回はこちらを使いませんでした。

 

3、Azure functionsで実装する

 今回行ったAzure Automation Runbook pythonは、functionsで実装しても

 大差ありません。お好みで選んでよいと思います。

 

4、SeleniumなどのOSSをOS内で実行する

 Seleniumによる自動化のよいところは、細かいブラウザ内の制御を

 自作できる部分です。

 例えば以下のような記載になります。(簡単なサンプルです)

 

# -*- coding: utf-8 -*-
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
 
driver = webdriver.Chrome(executable_path='C:\\Users\\akkoike\\AppData\\Local\\Programs\\Python\\Python37-32\\Scripts\\chromedriver.exe')
# chrome browser
driver.get('https://www.google.co.jp/')
# input keyword to textarea
driver.find_element_by_name("q").send_keys("まいくろそふと")
# search return
driver.find_element_by_name("q").send_keys(Keys.ENTER)
# click link by classname
driver.find_element_by_class_name("LC20lb").click()
driver.back()

 

 ※事前にChromeドライバのダウンロードと配置、pipでselenium

  インストール(pythonの場合)しておく必要があります。

  ここではchromeブラウザを起動し、google検索フィールドに

  「まいくろそふと」と入力しリターン。検索ヒットした1つ目の

  リンク先をクリックし、最後に「戻る」を押す、といった簡単な

  ものです。

 

ポップアップ認証サイトや、Basic認証以外の認証を経由した

サイトへのアクセスも含めてシナリオテストへ反映したい、という

場合なんかはSeleniumでやった方が楽な部分も多いですね。

(といっても座標指定など結構、力業な部分が多いですがw)

ただ、PythonユーザーだとMicrosoft Edgeブラウザはドライバが

提供されていませんので注意が必要です。(C#Javaが提供中)

 

今回はこんなところで。