Quantcast
Channel: normalian blog

Azure Blob Storage に SFTP でアクセスする - 閉域網版

$
0
0

前回の記事で Azure Storage に対して SFTP でアクセスするところまで行いました。Azure Storage 個別にユーザを作成し、パスワード or SSH Key Pair での認証が可能、Azure Storage 個別で認可制御を行えることが分かりました。
normalian.hatenablog.com
しかし、上記の記事では石橋を叩いて壊す社畜黒帯の方々にとって大事な観点が欠けています。そう、閉域網でアクセスするネットワーク制御です。こちらに関しては社畜というだけでなく、様々なコンプライアンスに対応するためにも非常に重要な観点です。特に金融系のコンプライアンスである FICS や PCI-DSS 等に準拠をする場合、Express Route や VPNで on-premise 側とつないでの閉域網通信は必須要件と言えるでしょう。
Microsoft Azure の場合 What is a private endpoint? - Azure Private Link | Microsoft Learnと呼ばれる機能を利用し、Microsoft Azure の仮想ネットワークに対してのみエンドポイントを公開することで閉域網からアクセスすることが可能になります。

今回の構成例としては以下になります。xxxxxxxxxwestus2.blob.core.windows.net 側が公開 FQDNとなり、xxxxxxxxxwestus2.privatelink.blob.core.windows.net が Private DNSが関連付けされている VNET でのみ名前解決が可能な FQDNとなります。

Private Endpoint を設定してインターネットアクセスを禁止する

まずは Private Endpoint から設定しましょう。以下の様にストレージアカウントの Network メニューから Private Endpoint 作成のメニューを選択し、当該 VNET に対してPrivate Endpoint 作成を完了します。

Private Endpoint の作成が完了すると、以下の様に VNET 側に NICが登録されます(例でのプライベートアドレスは 192.168.0.7 です)。

次にインターネット側からのアクセスを制限します。ストレージアカウントの Private Endpoint の有効化を行うことで閉域アクセスは可能となりましたが、このままの設定では公開 FQDNからのアクセスは未だに可能となっています。こちらを禁止するためには以下の様にストレージアカウントの Network メニューの Firewalls and virtual networks のメニューから禁止できます。加えて Network Routing は Microsoft Network routing となっていることを確認ください。

インターネット経由でのアクセス

社畜御用達ツール WinSCPで前回行ったアクセスを再度実施してみましょう。以下の様なエラーが表示され、アクセスがブロックされるはずです。

Private Endpoint 経由でのアクセス

次に Private Endpoint 経由でのアクセスを行います。Host name には Private DNSで指定した FQDNでアクセス可能です。以下の様に Private DNS側に登録された FQDNを使えばアクセス可能です。


感のいい方なら「NICに登録された Private IP でもアクセスできるのでは?」と気づいたかもしれません。ご認識の通り当該 Private IP を指定することで SFTP 接続は可能でしたが、Private Endpoint に割り当てられた Private IP は以下の様に Dynamic となります。

Dynamic で割り当てられた IP の場合、何らかの要因で IP アドレスの再割り当てがされた場合に IP アドレスが変更されることを意味しています。古からの Azurer なら「原則 xxx.xxx.xxx.4 から割り当てられるからそこを駆使すればいいじゃん」と思うかもしれませんが、明にサポートされていないやり方はお勧め致しません。

実はこのブログを書いた後に Azure の更新を眺めていたら以下の情報を見つけてしまいました。どうやら Private Endpoint だろうが IP 固定ができる様です。
azure.microsoft.com

詳細は az network private-endpoint ip-config | Microsoft Learnのブログを参照頂くとして、以下のコマンドを行うことで無事に IP アドレスが固定できました。以下の Private IP アドレスは予め Private Endpoint 作成時に割り当てられた IP アドレスです。

az network private-endpoint ip-config add --endpoint-name "daisamiclientwestus2-storage" -g "daisami-client-rg" -n myipconfig --group-id blob --member-name blob --private-ip-address "192.168.0.7"

Update Management Center (Preview) を試す

$
0
0

今回は現時点(2022年11月)では Previewが取れていませんが、今後は重要度が増すであろう Update Management Center について紹介したいと思います。事前に以下のページ位は斜め読みをすると理解が早いのではと思います。
learn.microsoft.com

Update Management Center は OS のセキュリティパッチの適用をコントロールすることができるクラウドサービスですが、個人的に Update Management Center が非常にイケていると思っているところは主に二点です。

  • Windowsだけでなく Linux側も含め一括で制御可能
  • Azure Arc と連携することで Azure や on-premise のみならず AWSGCP等の Azure 外部のプラットフォームについても制御可能

上記で何が嬉しくなるかというと SIer各位の頭痛の種である Excel表でのサーバ情報のマスター管理からの解放という点が挙げられると思います。運用手順等で管理をしていたとしても人間が手作業で行う以上はミスや抜け漏れが発生するものです。Update Management Center を活用することでサービス全体の各マシンの状況が一括で確認できるようになります。

管理画面のトップページとしては以下となります。管理対象マシンが何台あるか、どのマシンにどのパッチが当たっていないか、パッチ適用の実施状況、Windows/Linuxで適用されていないパッチの一覧等が表示されます。

Azure Automation の Update Management との違いは?

Azure 歴の長い方は「Azure Automation の Update Management があるじゃないか」とお気づきの方もいらっしゃると思いますが、Azure Automation とは以下の明示的な差が存在します。

  • Azure Automation の Update Management は別途 Azure Automation をリソースとして作成する必要があるが、Update Management Center は組み込み機能
  • Supported regions for linked Log Analytics workspace | Microsoft Learn Log Analytics workspace を Linked workspace として作成する必要があり、Azure Automation リソースの作成場所に制限があった(日本なら Japan East に作って東西の VMリソースを繋げば良いという話はありますが

イメージとして「Update Management Center」は 「Azure Automation の Update Management」の後継という捉え方をすれば祖語はない認識です。今までは Azure Automation のリソース管理や Log Analytics エージェントのインストール(Azure Automation で仮想マシン等を操作するために必要)等の追加の対応が必要でしたが、Update Management Center は特別な処理をせずに利用可能となります。

Update Management Center を使う場合のデメリットは?

逆に Update Management Center を利用する場合のデメリットをあえて考えてみたいと思います。ざっと以下のサポートマリックスを確認しました。

Windows側はほぼ差がない認識ですが、例えば Cent OS, RHEL, OracleLinuxの場合に Update Management Center 側は v6 に対応していない(Azure Automation 側は対応)点、Ubuntuでは v14 に対応していない点が挙げられます。
また、現時点ではプレビューだということもありますが、現時点ではサポートされているリージョンに日本が含まれていません(もっとも近い場所は東南アジア)。

外部ベンダーが提供するサービスをサポート有&追加構築無しの利用を夢見る女子高生な方々にとって、サポート無かつ日本リージョンでスケーリングされない(日本から利用できないというわけでなく Update Management Center の内部リソースは現時点では日本に展開されないの意)現状での利用は門限破りの朝帰り位の覚悟を要するのではという感覚です。

Update Management Center 自体を有効化するには

上記の通り Azure にとっては組み込みサービスなので、利用の開始自体に特に必要な処理はありません。。。。と言いたいところですが、現時点ではプレビューなこともあり以下の記事に従ってリソースプロバイダの有効化を行ってください。
learn.microsoft.com

az login
az account set --subscription "your subscription id"
az feature register --namespace Microsoft.Compute --name InGuestAutoAssessmentVMPreview
az feature show --namespace Microsoft.Compute --name InGuestAutoAssessmentVMPreview

上記のリソースプロバイダ登録に10分程度かかると思いますが、以下の様に az feature show で Status が registered になるまで随時確認してください。

Update Management Center を実際に利用する

ップ画面から Machines のタブをクリックすると以下の様に管理対象のマシンが表示されます。こちらの一覧に追加するのに特別な手順は不要です。Azure の仮想マシンなら自動的にこの一覧に加えられ、Azure 外部のマシンは Azure Arc を有効化することで一覧に加えられます。

画面で確認できる通り、Update Status の箇所で「更新パッチを全て適用済/X個のパッチが未適用/アセスメントが未実施」の三種類となります。アセスメントが実施されていない登録直後のマシンに対しては No updates data で表示されるので、最初に実施するのはアセスメントとなります。

アセスメントは以下の様にマシンを選択して Check for updates を実施することでアセスメントを手動で実行可能です。

当然以下の様に定期的なアセスメントを実施する設定も可能です。24時間ごとにパッチ適用の状況をチェックします。
Enable periodic assessment using policy | Microsoft Learnの様に Azure Policy との併用も可能です。

アセスメント実施後は未適用のパッチ数が表示されるので、再度対象マシンを選んで One-Time update を選択しましょう。

次に適用パッチを選びます。こちらについては Windows/Linuxを一括で選択できるようになった点が Azure Automation との大きな差と言えるかもしれません。適用対象パッチを細かく選択できる点は Azure Automation 側と同様です。

最後に、以下の様に再起動の条件を設定することで更新パッチの適用が行われます。

パッチ適用を実施後、画面左の historyタブを選択することで以下の様にプロセスの状況を確認できます。

パッチ適用中の場合は InProgress となりますが、それ以外にも成功・失敗が確認できます。また、操作内容がアセスメント・パッチ適用なのか、手動操作か定期実行操作か等が確認できます。

今回は簡単な概要紹介をさせて頂きましたが、今後は Azure 外部も含めたマシンの更新管理で重宝される機能だと思われるので、是非お試しください。

Microsoft Defender for Cloud にオレオレコンプライアンスを登録する

$
0
0

このブログを読むような方々はコンプライアンスと聞くと身構えたりテンションが下がるフレンズだと思います(けものフレンズはもう5年前か…)。できれば関わりたくないですが、社畜業を営む我々にとって避けて通れないのもまた事実。可能であれば何とか楽に「コンプラ対応ばっちりっすわ~」と流したいものでしょう。Microsoftは以下の様なコンプライアンスオファリングと呼ばれるサイトがあり、膨大な情報が提供されているのでこれで大丈夫!
learn.microsoft.com
と言いたいところですが、実際のところ「そんな大量の情報読めない」「コンプライアンスのどの項目が Azure のどの機能に対応しているか分からん」というのが実態でしょう。その場合にお勧めしている機能が Microsoft Defender for Cloud の Regulatory complianceです。画面を眺めた方がわかりやすいので、まずは以下のスクリーンショットを参照下さい。

対応しているコンプライアンス一覧に関しては上記リンクを参照して頂ければと思いますが、この例では PCI-DSS での項目が表示されています。仮想マシンや仮想ネットワーク等の IaaS 機能だけでなく、Azure PaaS 機能に加えて AWSGCPにもアカウントリンクして情報の一元管理が可能です。
特に PCI-DSS については、Microsoft Defender for Cloud で分類されている項目がそのまま PCI-DSS 自体の項目に対応しているので、自身のサービスが PCI-DSS に順守する場合に何を確認したら良いか一元的に Azure 上で管理できるということがわかります。
learn.microsoft.com
learn.microsoft.com
後に詳細に解説しますが、上記二つ目のリンクで参照する通り Regulatory complianceの中身は Azure Policyであり、いくつかのコンプライアンスMicrosoft側より提供されています。今回はこちらに対してオレオレのコンプライアンスを登録する方法を紹介します。

また、念のためご注意頂きたいのが、仮に Microsoft Defender for Cloud の項目にすべて順守したからと言ってもコンプライアンス準拠は保証されない点です。これまた PCI-DSS を例に挙げると Qualified Security Assessor (QSA)と呼ばれる人間のみ審査可能であり、外部ベンダーが行うことはできません。

オレオレコンプライアンスを登録する

ガバナンスやコンプライアンスという言葉が大好きな自分で手を動かさない方々にとって、ルールを敷いたら現場で順守が当たり前、仮に何かあったら現場での実施ルールが足りなかった等の揚げ足取りの地獄がまっており、実際に作業する方々(特に現場のリーダー層等)にとっては辟易することが多いと思います。Microsoft Defender for Cloud の Regulatory complianceに対し、オレオレコンプライアンスを登録することができるとしたらどうでしょう。どのようなルールが実施されているかは一覧化され、順守状況の可視化もされて一目瞭然となり、ガバナンス運用の手間は大きく減らせることでしょう(その手のガバナンスチームとのコミュニケーションも難関という点は一旦不問でお願いいたします)。
上記で記載した通り Regulatory complianceの中身は Azure Policy であり、特に Initiative と呼ばれる個別 Policy の集合体となります。さっそく自分向けの Initiative を作って試してみましょう。まずは Azure ポータルを開き、Azure Policy の画面から以下の様に Initiative の作成を実施します。

まず最初にルール名と割り当て領域を策定しますが、それ以上に大事なのが Category に Regulatory Complianceを選択することです。これにより後で Microsoft Defender for Cloud の管理画面に登録することが可能になります。

Initiative の作成時、以下の様に Control タブから各 Policy 向けのグルーピングができます(なぜか Policy 選択後なので先にしてほしい…)。こちらで作成する Control がそのまま Regulatory compliance画面で表示されるグルーピング名になるので注意してください。

次に Policies タブで利用する Azure Policy を選択します。この画面でどの Control に属するかも選択可能です。参考のため、ここではあえてどの Control にも含めない Policy を一つ追加しました。

Initiative の作成後は Assignment を行う必要があります。今回は割愛しますが、監査対象である subscription に作成した Initiative を割り当ててください

当然これだけでは Regulatory compliance画面に表示されません。ここからは Microsoft Defender for Cloud の画面に戻り Environment setting メニューを選び、監査対象の Subscription の「…」をクリックして表示される Edit settings を選択します。

こちらで遷移した先の画面からさらに Security Policy メニューを選択することで Your custom initiatives 画面が表示されるので、ここから自身で作成した Initiative を追加します。

これでもう終わったと思ってしまうせっかちさんも多いと思いますが、Regulatory Complianceの監視周期は即時自実行ではありません。以下の記事を参照して頂ければと思いますが、Azure Policy や Initiative 自体は 15 分後には利用可能になりますが、Regulatory Complianceについては24時間ごとの実行となります。つまりその間は Microsoft Defender for Cloud の画面に反映されません。
learn.microsoft.com

それが待てないせっかちさんの貴方に朗報です。Azure cli等を利用することで即時実行が可能です。Get policy compliance data - Azure Policy | Microsoft Learnを参考に以下のコマンドを実行して即時反映が可能です(といっても私の場合、コマンド自体の実行完了には15分程度かかりました)。

az login
az account set -s "your subscription id"
az policy state trigger-scan

コマンド実行後は Microsoft Defender for Cloud の Regulatory Complianceの画面からオレオレコンプライアンスが無事に参照可能です。

上記で分かる通り、Initiative 作成時の Control 毎にグルーピングされ、特に Control を割り当てなかったものは Additional Recommendations というカテゴリになります。

以上でオレオレコンプライスを Microsoft Defender for Cloud で表示する方法を紹介しました。他のコンプライアンス含めてマルチクラウドマルチプラットフォームが一元管理可能できるので有用度は高いのではないでしょうか。

podcast 配信に誘われて英語の勉強やらアメリカ生活やらについて感想を話した件

$
0
0

Microsoft MVP時代に仲良くして頂いた竹原さんに英語についてアレコレ聞きたいと相談されたので、誘われて podcastに出てみました。アメリカでの生活とか英語でどう苦労したとか語ってるんで良かったらどうぞ。
anchor.fm
出だしから 「Foreign language side effect って何だっけ?」位の英語力ですが、何とかアメリカで生活しております。過去に英語についてポストもしたことあるので、良かったらこっちもどうぞ。
normalian.hatenablog.com

また、私が言及してる発音矯正してくれた学校はここになります。
www.thejingles-summit.co.jp

Azure Bastion の shareable links 機能を使って Azure Portal にアクセス権のない人間でも VM 上での作業を可能にする

$
0
0

本記事のタイトルで伝えたいことが完了している気がしないこともないですが、つい先日にまたしても社畜心を捉えて離さないナイス機能が発表されました。そう、Azure Bastion の shareable links 機能です。
azure.microsoft.com
そもそも Azure Bastion をご存じない方の為にざっと解説すると、Azure Bastion は自身の仮想マシンへのアクセスを閉域的に行うことができるサービスであり、エンドポイントをインターネットに公開したり、踏み台の設定等を行うことが不要となります。
これ自体は非常に有用な機能なのですが、Azure Bastion の利用を踏まえたうえでも良く頂いた質問があります。それは「特定の仮想マシンの操作以外は何もさせたくないし、できれば Azure ポータルにもアクセスさせたくないけど、どういった権限を与えたらいい?」です。
石橋を叩いて壊す社畜黒帯な方々と相対したことのある皆様なら「にゃんこ大戦争のねこラーメン道」位の勢いで首を縦に振ってくれることでしょう(にゃんこ大戦争知らない人すいません)。上記の要望を実現する場合、RBAC を活用しても Built-in Role では制限が難しいケースもあり、要望達成がやや難しいという難がありました。

ここで今回紹介する Azure Bastion の shareable links 機能を使うことさえできれば上記の要望が実現可能です。Azure Bastion が生成するリンクだけを共有し、リンクから当該 VMにアクセスが可能となります。この際、リンクを利用するユーザは仮想マシンの捜査権限どころか Azure Portalにログインする権限すら必要ありません。そんな素敵な Azure Bastion の shareable links 機能ですが、現時点での注意点としては以下だと思っています。

  • Azure Bastion の shareable links は現時点でプレビュー - 2022年11月時点
  • Standard SKU でないと利用できない
  • 同じサブスクリプション仮想マシンじゃないと利用できない
  • 同じ VNET 上の仮想マシンじゃないと利用できない(つまり VNET Peering 越しはダメ)

プレビュー機能なこと自体は元記事を見ればご理解頂けると思いますが、VNET Peering 越しがダメなのはちょっと苦しい条件です。なぜなら一般的な社畜エンプラアーキテクチャでは Hub-Spoke 構成にすることが多く、Hub VNET 側に Azure Bastion を配置し、Hub/Spoke の仮想マシンにアクセスすることが多いからです。この点については Azure Bastion の追加配置を含む検討が必要になるところでしょう。

どうやって利用するの?

これ自体は非常に簡単です。Azure Bastion を Standard SKU で作成するか、Azure Bastion 既存の Basic SKU を Standard SKU に変更して以下の様に Shareable Link メニューにチェックして設定を保存してください。設定反映には10分程度の時間がかかりますが、以下のスクリーンショットで表示されている左のメニューの Shareable Links は設定有効前は表示されませんが、設定完了後も自動では追加反映されなかったので、10分程度たったら F5 等でブラウザを更新して設定完了を確認下さい。

Shareable Links は SKU を Standard に変更しないと有効化することができません。また、一度 Standard SKU に変更した Azure Bastion は Basic SKU に戻せない点もご注意ください。

Shareable Links を有効化後、左メニューから Shareable Links を選択して以下の様にリンクを作成したい仮想マシンを選択してください。この際、最初に注記で示した通り「当該 Azure Bastion の同一サブスクリプション&同一仮想ネットワーク」の仮想マシンしか選択できない点に注意ください。

仮想マシンの選択後は以下の様にクリップボードにコピー可能な URL が生成されます。

具体例があった方がわかりやすいと思うので、私が作成した Shareable Link を記載すると https://bst-"何かのUUID".bastion.azure.com/api/shareable-url/"何かのUUID"な感じです。使いまわしが可能な点に加え、特定のユーザや権限を示唆するものが何もないという点に注意が必要です。

Windowsマシンに接続してみる

まずは Windows側の Shareable Link をブラウザに入力してみましょう。以下の様が画面がブラウザ上に表示されます。

こちらに対し、ユーザ名とパスワードを入力すれば通常通りログイン可能です。ブラウザ経由となりますが、GUI操作も可能で元気な Windows操作が可能です。

Linuxマシンに接続してみる

次は Linuxマシンへの接続を試してみましょう。「SSH Private Key とかどうするのかなー?」とちょっと心配だったのですが、ブラウザから認証方式を選ぶことが可能なので、こちらから選択&Private Key のアップロードが可能です。

Windows側と同様に必要な情報を入力すればログイン可能です。


以上で Azure Bastion の shareable links の簡単な解説は終了です。我らエンプラ業を営む生き物が関わる生き物は国境を跨いでも開発者側に制限を加えるのが大好きです(特に公共・金融系が顕著なのも各国変わらず)。銀河英雄伝説でヤン提督が「何にしても、わが同盟政府には、両手をしばっておいて戦いを強いる癖がおありだから、困ったものですよ」と言っていたのが脳裏をよぎりますが、準拠する必要のあるコンプライアンス等を加味しつつ、こうした技を使って是非快適な社畜ライフをエンジョイ下さい。

Azure Virtual WAN の RouteTable をまっさらにする方法

$
0
0

最近ちょっと Azure Virtual WAN を使って遊んでたんですが、アレコレいじった後にリソースを削除しようとしたときに「Azure Virtual WAN 側の Route Table が Azure Firewallを参照しているので削除できず、Azure Firewall側は参照されているので削除できない」という問題にぶつかりました。
Azure ポータル上で誤った設定かつ不要な Route Table を削除しようとしたのですが、Route Table を空にする処理はポータル上ではできないようなので PowerShellコマンドを利用しましたが、自分自身が忘れそうなので今回は自分用の備忘録です。

まず、Virutal WAN の書く Hub には Route Tables がありますが、こちらは以下のスクリーンショットを見て頂ければ分かる通り Default と None が存在します。

更に上記のスクリーンショット右側は見て頂ければ分かると思いますが、ポータル上に表示させる名前とコマンド上で扱う名前は別になっています(ポータル上だと Default だが、コマンドだと defaultRouteTable 等)。Viritual WAN の特定の Hub 上での Route Table を取得するコマンドは以下となります。

PS /home/daichi> Get-AzVHubRouteTable  -HubName "your subscription id" -ResourceGroupName "your rg name"                   

Name                   : defaultRouteTable
Id                     : /subscriptions/"your subscription id"/resourceGroups/"your rg name"/providers/Microsoft.Network/virtualHubs/"your hub name"/hubRouteTables/defaultRouteTable
ProvisioningState      : Succeeded
Labels                 : {default}
Routes                 : []
AssociatedConnections  : []
PropagatingConnections : []

Name                   : noneRouteTable
Id                     : /subscriptions/"your subscription id"/resourceGroups/"your rg name"/providers/Microsoft.Network/virtualHubs/"your hub name"/hubRouteTables/noneRoute
                         Table
ProvisioningState      : Succeeded
Labels                 : {none}
Routes                 : []
AssociatedConnections  : []
PropagatingConnections : []

上記は既に値がまっさらになった状態の Route Table ですが、ここの Routes プロパティに不要な情報が含まれている場合にポータル上で消す手段がありませんでした(誰か知ってたら教えてください)。加えて Routes プロパティは RouteTable という構造体なので空の作り方がイマイチ不明でした。そのため noneRouteTable を利用して以下の様に更新しました。

PS /home/daichi> $rt = Get-AzVHubRouteTable -HubName "your hub name" -ResourceGroupName "your rg name" -name noneRouteTable                            
PS /home/daichi> Update-AzVHubRouteTable -ResourceGroupName "your rg name" -name defaultRouteTable -Route $route.Routes -VirtualHubName "your hub name"

Name                   : defaultRouteTable
Id                     : /subscriptions/"your subscription id"/resourceGroups/"your rg name"/providers/Microsoft.Network/virtualHubs/"your hub name"/hubRouteTables/defaultRouteTable
ProvisioningState      : Succeeded
Labels                 : {default}
Routes                 : []
AssociatedConnections  : []
PropagatingConnections : []

誰得な記事となりましたが、どなたかの参考になれば幸いです。

アメリカでソリューションアーキテクトとしての 5 年を 2022 年で振り返って

$
0
0

つい最近、技術的にも人間的にも敬意を払ってる友人と焼肉を食べてきた際に「1年の振り返りはやった方が良いよ~」とアドバイスをもらったので、アメリカで Solution Architect として5年ほど住んでみたことの振り返りと反省を書いてみようかなと思っています。私についてのざっとの内容は以下とかを見ると分かりやすいかもしれません。
normalian.hatenablog.com
念のためざっと自己紹介すると、私自身は大手系 SIerとして日系企業に勤めた後、外資企業にソリューションアーキテクトとして転職して3年弱位でアメリカに渡米し、今のところ何とか無事に5年ほど無事に過ごすことができました。外資系企業に転職後、役割の差は大小あれど一貫してソリューションアーキテクトとして働いております。英語経験についてよく聞かれるのであらかじめ記載すると、自身の経歴としては日本国外に在住経験は全くなかったどころか、初めてパスポートを作ったのが大学院卒業後というレベルです。

渡米1~2年目

この辺りは純粋に英語でかなり苦労しました。RareJob で何度も英会話のトレーニングをして、渡米前は講師の方とはそれなりにスムーズに話せるようにはなりましたが、渡米した後に non native と話すことに慣れていない現地勢と話したときに全く伝わらずに非常に苦労しました(汗。結果としては RareJob の講師の方々は日本人のダメ発音に慣れすぎてるせいで、発音の矯正を何もせずに伝わらないというのを痛感した初年度でした。自分の能力を100としたら10も出せればいい方だなというのが渡米当初の実感でした。
本社がシアトルの会社に勤めている状況でロサンゼルスに赴任したこともあり、現地の同僚がほぼ居ない状態で就業したので現地のことを聞ける相手が全くいない状態だったので現地の生活に慣れるのにも非常に苦労しました。T-Mobileのとある支店の店員には英語がダメすぎて追い返されたり、SSN(ソーシャルセキュリティーナンバー)申請時にスタッフに英語ができなすぎるのを小声で笑われた等は、英語ができない勢の場合はアメリカでは(少なくともロサンゼルスでは)通常運転です。これは単なる個人的な体感ですが、英語で意思疎通ができないレベルの人間に対して在米現地民は相当に対応が雑です(これに比して、いわゆる GAFAM 村の方々は英語が苦手な移民の対応に慣れている人が多いので優しい人が多いです)。
純粋に英語が厳しい&現地に慣れるのに苦戦するという意味で最初の半年くらいは仕事らしい仕事になりませんでしたが、他のメンバーが答えられないような内容を真摯に答え続けることで徐々に技術的な腕をかってくれる方々も増えました。あれやこれやと、自分の役職の枠を超えて現地のでどぶさらいみたいな仕事でも拾いながらこなした結果、お客さん側(相手は日本人皆無な native speaker 多めでした)の SVP/VP レベルの方々の信頼も得、仕事は比較的順調で世界規模にサービス展開する案件の CI/CD 回しながらの Windows Container やら Microservices のかなり deep なところをやれたので面白かったです。
※やっぱ最低限の意思の疎通ができる言語力と相手から信頼される程度の能力(技術だけに限らず)が無いと死ぬというのは痛感しました

実はこの辺のロサンゼルス時代はコロナ渦でなかったにもかかわらずほぼリモート勤務だったので、お客さんとは非常に親密になった人は何人かいましたが、non Japanese な同僚とは英語的なコミュニケーションがつらすぎることもあって中々親密な関係を持てなかったというのが率直な反省でした。逆にロサンゼルス時代の日本人同僚の方々とは今も親密な関係を持たせて頂いていることもあり、なんだかんだで米国側に移住した場合、同郷の人間しか同郷からの移住の苦労は分かりっこないので、下手に肩肘張らずに日本人の先住移民の方々に素直に相談したほうが良いと身に染みたのもこの辺です。何だかんだで手探りでの試行錯誤の期間でした。

渡米3年~5年目

この辺からはある程度は英語のコミュニケーションに慣れてきたので、同僚とくだらない雑談ができる頻度がかなり増えてきました。とはいっても、私自身の腰が引けていて中々に深入りしたコミュニケーションがとれないなと思う状況は未だに残ってるなぁとは思います(汗。くだらない雑談をする同僚とは家族の話題なり、お互いの出身国の愚痴なり、人種問題の雑談なり、かなり深入りして会話できることを実感できるようになったなぁとは思っています。
仕事ではインド・ドイツ・北アイルランド・オランダ等々に出張し、現地での Hands-On 系のイベントを主催・実施したので、この辺りからは色んな人を巻き込んで親密なコミュニケーションをとりながら仕事をできるようになったのを実感しました。自分の中でも「日本国外でも普通に仕事ができるようになったなぁ」という感覚で、ここにたどり着くまでにはアレやコレやと涙なしには語れないアレコレはありますが、何とか日本語圏外でも仕事ができるようになったかなと思ってます。

今後について

私の役職である「Solution Architect という役職に就いた後、次のステップでどういったキャリアを形成するのか?」というのは他の Solution Architect 各位の話を聞いても中々に悩んでいる方が多いかなというのが率直な印象です。渡米前は「日本国外のことは分からないし、渡米して2~3年くらいは働いてみてから次のステップを明確にしよう」という気持ちはありましたが、既にその2~3年は突破してしまいました(汗。とはいっても、渡米して GAFAM 系の本社側だからこそ見える組織構成や会社としてのオペレーションが色々と見えてきたなぁというのも実感です。
技術に関しては「Azure 全般に対して幅広かつ踏み込んだ知識を持っている」という感じで、総じて高い評価を貰っているかなと信じています(リップサービスが上手い人たちが多いので確信はないですが)。ただ、現在は Azure プラットフォーム自体を取り巻く領域が体系化・成熟してきた実感もあり「Azure 全般が得意」というような強みでなく「Azure の〇〇の専門家」や「Azure と××を組み合わせる専門家」等の今よりも踏み込んだ領域での専門性は作らないといけないなとは思っている状態です。
現業のタスクも学びが色々と多く励みになっていますが、2023年では中長期的なキャリアに対して具体的な一歩と言える結果を出したいなと思っています。

(おまけ)他国の IT 事情について

各国の案件を支援して実感することは『自分の国は特殊だから~』と自称する方の案件で各国の特殊性を感じないという点です。この辺り、日本人の他国と話さない方ほど「日本は特殊なので~」と言って思考停止するケースをよく見ましたが、日本以外でも自国以外の人間のやり取りをする頻度が低い人ほど自国を特殊だと思い込んでいる人が多いなという印象です。Twitter等だと「日本は海外と比べて遅れて~」等の発言を聞きますが、各国でも塩漬け案件は山の様にあるので、私個人の感覚として、他の国の印象はこんな感じです。

  • アメリカという文脈でシリコンバレーが意図されることが多いが、アメリカ国内でもシリコンバレーや GAFAM は特殊扱いされてる(2022年の年末に 70% のフライトをキャンセルした某アメリカ航空会社は「90年台の IT を未だに使い続けている」とか言われてるんで
  • 日本とかドイツみたいに2000年辺りに IT 投資をできる余力があったところは既存資産があるのでマイグレーション案件が多め
  • オーストラリア・ニュージーランド外の APAC 各国とかは今 IT 投資を進めてることが多いので、移行案件よりも新規案件が多め
  • ヨーロッパも保守的な国はすごく多く「契約にないけどこれ何とかしろ」と突っ込んできたのは日本とヨーロッパ某国

上記は単なる私の肌感なので統計値を取ったら当然話は変わると思いますが、シリコンバレー&GAFAM を除き、現場の業務システム系では欧米圏と日本に大きな差はないなぁというのが率直な感想です。

散文的な文章となってしまいましたが、皆様も良いお年を過ごして頂ければ幸いです。意見・コメント等有れば頂ければ嬉しいです。

Microsoft Sentinel で検証用 Incident を作ってみる

$
0
0

新年あけましておめでとうございます。最近はセキュリティ周りのキャッチアップに迫られてえっちらほっちらと Defender 関連やら Microsoft Sentinel をキャッチアップしとります。どちらも脅威の検出、調査・対策の検討等々で大活躍する強力なツールなのは間違いないのですが、学習・検証用に Incident 等を作成するのは意外と面倒だったりします。
そんな悩みに対して応えるため、実はサンプルの Incident を発生させる機能が提供されています。以下の記事は Microsoft Defender for Cloud の記事ですが、Microsoft Sentinel の Incident 作成としても利用可能です。
learn.microsoft.com

上記の記事で記載されている内容を端的に表現すると「 echo みたいな無害なコマンドを他とかぶらない名前( asc_alerttest_662jfi039n )に変えて実行すれば Incident 作るぜ」というものです。LinuxWindowsでややファイル操作が異なるものの、大枠の操作は一緒です。今回はこちらの利用感を軽くまとめてみたいと思います。

事前準備

当たり前の話ですが、以下は必須です。

Microsoft Sentinel 向けの Incident を作成してみる

これに関しては記事に記載がある通りですが、さっそく用意した CentOS仮想マシンSSHでつないで以下のコマンドを実施してみましょう。echo を記事で指定した通りのファイル名で実行していますが、オプション無を 1 回、オプション有を 6 回実行しています。

[xxxxxxxxx@centosvm01 ~]$ cp /bin/echo ./asc_alerttest_662jfi039n
[xxxxxxxxx@centosvm01 ~]$ ./asc_alerttest_662jfi039n 

[xxxxxxxxx@centosvm01 ~]$ ./asc_alerttest_662jfi039n testing eicar pipe
testing eicar pipe
[xxxxxxxxx@centosvm01 ~]$ for i in `seq 1 5`;do  ./asc_alerttest_662jfi039n testing eicar pipe; done
testing eicar pipe
testing eicar pipe
testing eicar pipe
testing eicar pipe
testing eicar pipe
[xxxxxxxxx@centosvm01 ~]$ 

コマンド実行後、通知設定をしていれば 5 分もしないうちに以下のメールが届きます(これ自体は Defender 側の機能ですが、Microsoft Sentinel 側で Incident が作成されたタイミングを認識するのにちょうどいいので)。

次に Microsoft Sentinel を開きます。何個の Incident があるかを確認してみましょう。開いてみると新規の Incident が 7 個作成されています。「実はオプション要らないのでは?」という疑問が頭をよぎることはさておき、severity が high の incident がコマンド実行回数分だけ作成されていることが分かります( closed になっている incident は私の環境で別途発生したものです)。

更に以下のコマンドを実行したところ新規に Incident が作成されたので、どうやら引数と関係なく Incident は作成されるようです。

Last login: Tue Jan 10 14:16:43 2023 from vm000001.internal.cloudapp.net
[xxxxxxxxxx@centosvm01 ~]$ ./asc_alerttest_662jfi039n "Do I need to put something here?"

Microsoft Sentinel で Incident を深堀してみる

以下の様に Incident メニューから個別に一つ選んで View Full Details を押して詳細を見てみましょう。

ボタン押下後は以下の画面に遷移します。Incident の状態を New から Active/Closed にしたり、Incident の担当を割り当てたりは当然可能ですが、せっかくなので以下の Investigation のボタンを押してみましょう。

以下でご覧の通り、Incident に関連するエンティティ一覧が表示されます。タイトルには「テストアラートだから脅威じゃないよ」と英語で記載されています。

実際の脅威で有れば、ここから SOC チームのメンバーが「ユーザは誰か?」「どんな引数で実行したのか?」「いつ実行したのか?」等を確認しつつ、どの様な脅威なのかを分析し、運用チーム等とどの様な対応をするかを検討していきます。今回の Incident は実際の脅威ではないので、特に対応不要として Incident を Closed に変更して対応を終了します。


今回の機能は意外と知られていないので、Microsoft Defender for Cloud はもちろん Microsoft Sentinel を学習する際にも有用な機能だと思います。


Microsoft Azure の拡張機能やエージェントのバージョンに関する Tips

$
0
0

最近あった質問の中で、仮想マシンのエージェントや拡張等々のバージョンについて質問されたことがあったのですが、どれがどのバージョンかというのがかなり分かりにくかったのでブログにまとめたいと思います。ことの発端となったのは以下のスクリーンショットで「Azure Agent はどこ?」や「Azure Site Recovery Agent のバージョンはどこで確認するのか?」といった質問が来たからです。

特にオチ等はありませんが、言われてみればこの辺りはややこしいので Tips としてブログにまとめておいた方が良かろうと思ってこちらにまとめておきます。

Azure VM Agent のバージョンはどこ?

これについては仮想マシンの Extensions + applications メニューには存在しません。Azure Agent のバージョンは以下の様に Properties メニューから確認できます。以下の様に WindowsLinuxではバージョンが異なることがわかります。

Linuxでも同様のことが可能ですが、Windowsの場合なら以下の様にエクスプローラーから開いて Azure Agent の実態があるフォルダにアクセスることが可能です。C:\WindowsAzure\ 配下に Azure Agent の各バージョンが配置されているのが分かると思います。

Extensions + applications メニューのバージョンって何?

これが一番分かりにくいところかなと思っていますが、ここで表示されるバージョンは typeHandlerVersion と呼ばれる「スクリプトとして実行されるVM拡張部分のみ
(Azure VM agent 等の実態のエージェントとは別)」のバージョンになります。
learn.microsoft.com
VM拡張の実態は各種スクリプトを動作させるもの(Azure Monitor Agent の実態をインストールする等)になりますが、そちらのバージョンを表しています。例として Azure Site Recovery を取り上げますが、以下のコマンドを利用することで ASR の VM拡張バージョン(!= ASR 等のエージェントのバージョン)の確認が可能です。

PSC:\Users\daisami> azvmget-instance-view --resource-grouprg-name --namevmname --query"instanceView.extensions[] | [?type=='Microsoft.Azure.RecoveryServices.SiteRecovery.Windows']"
[{"name": "SiteRecovery-Windows",
    "statuses": [{"code": "ProvisioningState/succeeded",
        "displayStatus": "Provisioning succeeded",
        "level": "Info",
        "message": "Installation and configuration succeeded.",
        "time": null}],
    "substatuses": [{"code": "ComponentStatus/{\"unique_id\":\"0d4f05d6-4960-4157-80ef-00ade88ffb01\",\"start_time_utc\":638125298878808978,\"end_time_utc\":6381252994438287
68,\"os_identifier\":\"Windows Server 2019 Datacenter\",\"os_version\":\"10.0.17763\",\"azure_guest_agent_version\":\"2.7.41491.1071\",\"python_version\":null,\
"kernel_version\":null,\"available_physical_memory\":4680007680}/succeeded",
        "displayStatus": "Provisioning succeeded",
        "level": "Info",
        "message": null,
        "time": null
      },
      {"code": "ComponentStatus/taskid:7849f91b-2ea6-4bd7-894a-f7a7b086fbc3/succeeded",
        "displayStatus": "Provisioning succeeded",
        "level": "Info",
        "message": null,
        "time": null}],
    "type": "Microsoft.Azure.RecoveryServices.SiteRecovery.Windows",
    "typeHandlerVersion": "1.0.0.9202"
  }]

Azure Site Recovery Agent のバージョンは?

上記で ASR 拡張のバージョンは分かりましたが、VMにインストールされる Agent のバージョンはどこで確認したら良いのでしょうか?ASR は実態のエージェントとして、以下の Mobility Agent と呼ばれるものをインストールします。
learn.microsoft.com

こちらのバージョンを確認する一番簡単な方法は Recovery Service Vault の Replication Item から以下の様に確認する方法です。

実際のバイナリ等は C:\Program Files (x86)\Microsoft Azure Site Recovery\agent に配置されます。


ここで注意が必要な点として C:\WindowsAzure\Logs\Plugins 以下に以下の様な Extensions + applications メニューに表示されているものが並んでいる点です。こちらにはエージェントのバイナリ等は配置されておらず、VM拡張を有効化する際に実行されるスクリプトの実行ログが格納されています(フォルダパスに Logs とあるので推察する方もいると思いますが)。
ログの一部を抜粋しますが、以下の様なコマンド実行内容が出力されており、VM拡張を有効化する際の詳細処理が確認可能です。

[2023-03-26T08:17:21.750Z] Executing: C:\Packages\Plugins\Microsoft.Azure.RecoveryServices.SiteRecovery.Windows\1.0.0.9202\Scripts\enable.cmd  
[2023-03-26T08:17:24.512Z] Execution Complete.
######
Execution Output:
C:\Packages\Plugins\Microsoft.Azure.RecoveryServices.SiteRecovery.Windows\1.0.0.9202>start /B cmd /C AzureReplicationVmExtension.exe "enable" 
2023-03-26T08:17:25.2774571Z	[Information]:	----------------VM Extension run started----------------
2023-03-26T08:17:25.2774571Z	[Warning]:	Skip the logging due to absence of the log blob URI: .
2023-03-26T08:17:25.2774571Z	[Information]:	Operating system identifier: Windows Server 2019 Datacenter. Operating system version: 10.0.17763. Azure guest agent version: 2.7.41491.1075.
2023-03-26T08:17:25.2774571Z	[Warning]:	Skip the logging due to absence of the log blob URI: .
2023-03-26T08:17:25.2824569Z	[Information]:	Bcdr Handler Initialized SiteRecoveryExtensionProd.
2023-03-26T08:17:25.2824569Z	[Warning]:	Skip the logging due to absence of the log blob URI: .
2023-03-26T08:17:25.2824569Z	[Information]:	Initializing configuration helper.
2023-03-26T08:17:25.2824569Z	[Warning]:	Skip the logging due to absence of the log blob URI: .
2023-03-26T08:17:25.2874560Z	[Information]:	Configuration file count: 1 and files are C:\Packages\Plugins\Microsoft.Azure.RecoveryServices.SiteRecovery.Windows\1.0.0.9202\RuntimeSettings\0.settings
2023-03-26T08:17:25.2874560Z	[Warning]:	Skip the logging due to absence of the log blob URI: .
2023-03-26T08:17:25.2874560Z	[Information]:	GetEnvironmentVariables: 
2023-03-26T08:17:25.2874560Z	[Warning]:	Skip the logging due to absence of the log blob URI: .
2023-03-26T08:17:25.2874560Z	[Information]:	  COMPUTERNAME = Win2019Gen2VM01
2023-03-26T08:17:25.2874560Z	[Warning]:	Skip the logging due to absence of the log blob URI: .

Azure Cognitive Search のベクトル検索機能で helloworld

$
0
0

2023 年 5 月に実施された Microsoft Build というイベントで Azure Cognitive Search にベクトル検索機能が private previewで追加されました。同機能単体で見ると「?」な人も居ると思いますが、Azure OpenAI と組みあわせることで強力な力を発揮します。この辺りの詳細については以下の Qiita 記事が非常に良くまとまっています。
qiita.com
qiita.com
OpenAI 単体でも様々なことが実現可能ですが、社畜業を営む我々としては「俺たちの内部データを使ってもっと OpenAI の回答をカスタマイズできないのか?ただし閉域網でな!!」という「ダイエットしたいけど甘いものが食べたい」的なことがが気になって仕方がないことでしょう。閉域網に関しては単に private endpoint 等の Azure VNET の機能を活用すれば何とかなるので割愛しますが、元々要件を達成するにはテキストの 埋め込みが非常に重要になります。Azure OpenAI では Embeddings APIを利用し、テキストをベクトル化することでテキスト間の類似性をコサイン類似度と呼ばれる尺度ではかることができます。

そもそも Azure Cognitive Search のベクトル検索機能って今使えるの?

Microsoft Build 2023 で発表された機能ですが、2023 年 6 月 19 日現在では private preview機能となりますがリージョン指定は特にないので、日本のリージョン(東日本は試しました)で利用可能です。現在は以下のフォームを埋めて private previewに参加依頼を出す必要があります。
https://aka.ms/VectorSearchSignUp

フォームを埋めたりというと心理障壁があると思いますが、フォームを埋めた数十分くらいで以下のメールが来てあっという間に機能が有効化されたので、使いたかったらとりあえず試してみるのはお勧めです。

PDF データをベクトル化して Azure Search のインデックスに保存してみる

参考にしたサンプルは以下のサンプルとなります。
github.com
特に以下の Azure Cognitive Search に関する helper クラスに関してはそのまま流用しました。
azure-open-ai-embeddings-qna/code/utilities/azuresearch.py at main · Azure-Samples/azure-open-ai-embeddings-qna · GitHub
ベクトル検索の設定に関しては特に以下の行周辺が参考になります。
https://github.com/Azure-Samples/azure-open-ai-embeddings-qna/blob/main/code/utilities/azuresearch.py#L83
現時点で Azure Portal上ではベクトル検索のフィールドは作成することはできませんが、上記の pythonスクリプトを用いてインデックスを作成すると Azure Portalでは以下の様に表示されます。

では実際に pptx ファイルを pdf ファイルとして Azure Blob Storage に保管し、Azure OpenAI では Embeddings APIを利用してテキストをベクトル化し、Azure Cognitive Search のインデックスに保存します。上記の通り Azure Cognitive Search に関する helper クラスを丸々流用しているので、以下を実際に動かす際はそちらも確認下さい。

from azuresearch import AzureSearch
from langchain.embeddings.openai import OpenAIEmbeddings
from langchain.document_loaders import WebBaseLoader
from langchain.text_splitter import TokenTextSplitter, TextSplitter
import hashlib

deployment="text-embedding-ada-002"# put your deployment name
chunk_size=1
openai_api_type="azure"
openai_api_key="put your Azure OpenAI key"
openai_api_base="put your Azure OpenAI endpoint - https://your-azureopenai-endpoint.openai.azure.com"
openai_api_version="2023-03-15-preview"

vector_store_address ='https://your-search-account-name.search.windows.net'
vector_store_password = 'put your search account key'
index_name ='put your index name'

embeddings = OpenAIEmbeddings(
    deployment=deployment, 
    chunk_size=chunk_size,
    openai_api_type=openai_api_type,
    openai_api_key=openai_api_key,
    openai_api_base=openai_api_base,
    openai_api_version=openai_api_version)

vector_store = AzureSearch(azure_cognitive_search_name=vector_store_address,
                           azure_cognitive_search_key=vector_store_password,
                           index_name=index_name,
                           embedding_function=embeddings.embed_query)

source_url = 'https://your-storage-account-name.blob.core.windows.net/xxxxxxx/xxxxxxxxxx.pdf'
chunk_size = 500
chunk_overlap = 100
document_loaders = WebBaseLoader
text_splitter: TextSplitter = TokenTextSplitter(chunk_size=chunk_size, chunk_overlap=chunk_overlap)
documents = document_loaders(source_url).load()
docs = text_splitter.split_documents(documents)

keys = []
for i, doc inenumerate(docs):
    # Create a unique key for the document
    source_url = source_url.split('?')[0]
    filename = "/".join(source_url.split('/')[4:])
    hash_key = hashlib.sha1(f"{source_url}_{i}".encode('utf-8')).hexdigest()
    hash_key = f"doc:{index_name}:{hash_key}"
    keys.append(hash_key)
    doc.metadata = {"source": f"[{source_url}]({source_url}_SAS_TOKEN_PLACEHOLDER_)" , "chunk": i, "key": hash_key, "filename": filename}

vector_store.add_documents(documents=docs, keys=keys)

こちらを実施しましたが、以下の様に約 2MB 程度の PDF をベクトル化してインデックスに保存すると 100MB ~ 200MB 程度増えていることが分かります。

加えて、こちらの約 2MB 程度の PDF をベクトル化して Azure Cognitive Search のインデックスに保存する際、それぞれ 20 分程度の処理時間がかかりました。確認した限り、ほとんどの時間は Azure OpenAI を呼び出す Embedding の時間です。大量のドキュメントをバッチ処理したい場合等には一つの目安になると思います。

ベクトル化して保存した PDF データを用いて検索してみる

サンプルスクリプトを作成して検索を行いましたが、以下二つのスクリプトも利用しています。

以下が今回利用したサンプルスクリプトとなります。

from langchain.embeddings.openai import OpenAIEmbeddings
from langchain.chains import ConversationalRetrievalChain
from langchain.chains.qa_with_sources import load_qa_with_sources_chain
from langchain.chains.llm import LLMChain
from langchain.chains.chat_vector_db.prompts import CONDENSE_QUESTION_PROMPT
from langchain.chat_models import AzureChatOpenAI
from customprompt import PROMPT
from azuresearch import AzureSearch

deployment="text-embedding-ada-002"
deployment_name="gpt-35-turbo"
chunk_size=1
openai_api_type="azure"
openai_api_key="put your Azure OpenAI key"
openai_api_base="put your Azure OpenAI endpoint - https://your-azureopenai-endpoint.openai.azure.com"
openai_api_version="2023-03-15-preview"

vector_store_address ='https://your-search-account-name.search.windows.net'
vector_store_password = 'put your search account key'
index_name ='put your index name'

question = 'What is xxxxxx program?'
prompt =  PROMPT
chat_history = []

embeddings = OpenAIEmbeddings(
    deployment=deployment, 
    chunk_size=chunk_size,
    openai_api_type=openai_api_type,
    openai_api_key=openai_api_key,
    openai_api_base=openai_api_base,
    openai_api_version=openai_api_version)

vector_store = AzureSearch(azure_cognitive_search_name=vector_store_address,
                           azure_cognitive_search_key=vector_store_password,
                           index_name=index_name,
                           embedding_function=embeddings.embed_query)

llm = AzureChatOpenAI(
    temperature=0,
    deployment_name=deployment_name,
    openai_api_type=openai_api_type,
    openai_api_key=openai_api_key,
    openai_api_base=openai_api_base,
    openai_api_version = openai_api_version
)

question_generator = LLMChain(llm=llm, prompt=CONDENSE_QUESTION_PROMPT, verbose=False)
doc_chain = load_qa_with_sources_chain(llm, chain_type="stuff", verbose=True, prompt=prompt)
chain = ConversationalRetrievalChain(
    retriever=vector_store.as_retriever(),
    question_generator=question_generator,
    combine_docs_chain=doc_chain,
    return_source_documents=True,
)
result = chain({"question": question, "chat_history": chat_history})
context = "\n".join(list(map(lambda x: x.page_content, result['source_documents'])))
sources = "\n".join(set(map(lambda x: x.metadata["source"], result['source_documents'])))
result['answer'] = result['answer'].split('SOURCES:')[0].split('Sources:')[0].split('SOURCE:')[0].split('Source:')[0]

print('#### question ')
print(question)
print('#### answer ')
print(result['answer'])
print('#### context ')
print(context)
print('#### source ')
print(sources)

実際にスクリプトを実行し、英語で記載されているセールスプログラムについての pptx ファイルを pdf ファイルに変更したものを食わせた結果、以下の様になりました。

> Finished chain.
#### question
What is xxxxxxxxxxxx?
#### answer
xxxxxxxxxxxx is a program or initiative offered by Microsoft. No further information is provided in the given text.
#### context
/F 4/A<</Type/Action/S/URI/URI(mailto:<中略>?subject=<中略>) >>/StructParent 9>>
endobj
50 0 obj
<</Type/Page/Parent 2 0 R/Resources<</ExtGState<</GS5 5 0 R/GS10 10 0 R>>/Font<</F6 52 0 R/F1 8 0 R/F7 54 0 R/F8 56 0 R/F3 19 0 R>>/ProcSet[/PDF/Text/ImageB/ImageC/ImageI] >>/MediaBox[ 0 0 960 540] /Contents 51 0 R/Group<</Type/Group/S/Transparency/CS/DeviceRGB>>/Tabs/S/StructParents 10>>
endobj
51 0 obj
<</Filter/FlateDecode/Length 4798>>
stream
<中略>
endstream
endobj
47 0 obj
<</Type/ExtGState/BM/Normal/ca 0.74902>>
endobj
48 0 obj
<</Subtype/Link/Rect[ 731.83 242.6 870.68 255.35] /BS<</W 0>>/F 4/A<</Type/Action/S/URI/URI(<中略>) >>/StructParent 8>>
endobj
49 0 obj
<</Subtype/Link/Rect[ 733.32 64.6 905.2 79.625] /BS<</W 0>>/F 4/A<</Type/Action/S/URI/URI(<中略>) >>/StructParent 9>> 
endobj
50 0 obj
<</Type/Page/Parent 2 0 R/Resources<</ExtGState<</GS5 5 0 R/GS10 10 0 R>>/Font<</F6 52 0 R/F
<中略>
#### source
[https://yyyyyyyyyyyy.blob.core.windows.net/shared/yyyyyyyyyyyy%20Partner%20briefing%20deck%20v1.2.pdf](https://yyyyyyyyyyyy.blob.core.windows.net/shared/yyyyyyyyyyyy%20Partner%20briefing%20deck%20v1.2.pdf_SAS_TOKEN_PLACEHOLDER_)

とあるセールスプログラムについての適用条件や概要についてまとめた資料をベクトル化して保存しましたが、どこの会社が提供しているか程度でそれ以上の情報が提供されませんでした。次にもう少し踏み込んで 'What is criteria for xxxxxxxxxxxx?'を質問してみましたが、以下の様に明確な回答は得られませんでした。

 Finished chain.
#### question
What is criteria for xxxxxxxxxxxx?
#### answer
The text does not provide a clear answer to this question.
#### context
<中略>
#### source
[https://yyyyyyyyyyyy.blob.core.windows.net/shared/yyyyyyyyyyyy%20Partner%20briefing%20deck%20v1.2.pdf](https://yyyyyyyyyyyy.blob.core.windows.net/shared/yyyyyyyyyyyy%20Partner%20briefing%20deck%20v1.2.pdf_SAS_TOKEN_PLACEHOLDER_)

pptx の様なファイルだとスライド毎に文脈が分断されやすいので、PDF データを保存する際に設定した chunk_size が 500 だと小さすぎるのかもしれません。また、日本語の PDF をそのまま入れた場合は検索に引っかからなくなったので、英語に翻訳して格納するして OpenAI の返答だけ日本語にする等の処理が必要になるかもしれません(実際には未確認)。この辺りはもうちょっと深堀して中身を確認したいと思います。

Azure OpenAI での Embedding 時にパラメータ弄って観察する

$
0
0

前回は Azure Cognitive Search のベクトル検索で hello worldをしてみましたが、あの程度で「これで現場で使えるな」と満足してくれる人は居ないでしょう。以下の記事ではどの様に使うかまでは踏み込みましたが、特にパラメータ設定もしてないこともあり、あまり期待する結果が得られなかったというのが現実でした。
normalian.hatenablog.com

今回は一部のパラメータを弄ってもう少し期待した結果を返してもらいたいと思います。今回は以下の部分の chunk_size と chunk_overlap に注目したいと思います。同記事はテキストデータを Azure OpenAI でEmbedding する前、テキストを一定のまとまり( chunk )にするコード片です。全体のコードを見たい方は前回の記事を参照ください。

source_url = 'https://your-storage-account-name.blob.core.windows.net/xxxxxxx/xxxxxxxxxx.pdf'
chunk_size = 500
chunk_overlap = 100
document_loaders = WebBaseLoader
text_splitter: TextSplitter = TokenTextSplitter(chunk_size=chunk_size, chunk_overlap=chunk_overlap)
documents = document_loaders(source_url).load()
docs = text_splitter.split_documents(documents)

chunk_size と chunk_overlap って何?

ここが分からないとパラメータを変更をしても何が何やら分からなくなってしまうので、まずこのパラメータが何をしているかを確認しましょう。そのためには以下の記事が参考になります。そこには「TokenTextSplitter splits a raw text string by first converting the text into BPE tokens」という記載があり、BPE トークンと呼ばれるものに文字列を変換する模様です。
js.langchain.com
では BPE トークンが何かというと今度は以下の記事が参考になり、BPE は Bite Pair Encoding の略であり、頻出する文字列の組み合わせからその分割方法を学習するものだそうです。
cardinal-moon.hatenablog.com

そろそろ用語のキャッチアップばかりで疲れてきたと思いますので、以下のコードで実際に確かめてみましょう。サンプル文字列に対して chunk_size と chunk_overlap を設定しています。

from langchain.text_splitter import TokenTextSplitter, TextSplitter

text = "I often hear opinions similar to 'Is there any innovative business that offers high profits without risks?', but it sounds like same opinion with high school girls who want to lose weight but still crave sweet treats."

chunk_size = 10
chunk_overlap = 3
text_splitter: TextSplitter = TokenTextSplitter(
    chunk_size=chunk_size,
    chunk_overlap=chunk_overlap,
    encoding_name="gpt2")

output = text_splitter.create_documents([text])

print(output)

こちらの結果は以下の様になります。実際には一行で出ていますが、読みやすい様に改行しているのでご注意ください。

PS C:\opt\workspace\Test-VectorSearch> python .\test_splitter.py
[Document(page_content="I often hear opinions similar to 'Is there any", metadata={}), 
 Document(page_content='Is there any innovative business that offers high profits without', metadata={}), 
 Document(page_content=" high profits without risks?', but it sounds like", metadata={}), 
 Document(page_content=' it sounds like same opinion with high school girls who', metadata={}), 
 Document(page_content=' school girls who want to lose weight but still crave', metadata={}), 
 Document(page_content=' but still crave sweet treats.', metadata={})]
PS C:\opt\workspace\Test-VectorSearch> 

上記を見て頂ければ分かると思いますが、設定した chunk_size(=10 単語)以下でひとまとまりにされ、設定した chunk_overlap(= 3 単語)で前後の chunk と重複していることが分かります。

では日本語ではどうなるのでしょうか?殆どの読者各位は日本語での内容が気になると思いますので、当該英文を日本語に直して試してみたいと思います。実行したのは以下のコードで内容は変えていません。

from langchain.text_splitter import TokenTextSplitter, TextSplitter

text = "「リスクなしに高収益が得られる革新的なビジネスはないのか」というような意見をよく耳にするが、「痩せたいけど甘いものが食べたい」という女子高生と同じ意見に聞こえる。"

chunk_size = 10
chunk_overlap = 3
text_splitter: TextSplitter = TokenTextSplitter(
    chunk_size=chunk_size,
    chunk_overlap=chunk_overlap,
    encoding_name="gpt2")

output = text_splitter.create_documents([text])

print(output)

実行結果は以下になります。こちらも実際には一行で出ていますが、読みやすい様に改行しているのでご注意ください。

PS C:\opt\workspace\Test-VectorSearch> python .\test_splitter.py
[Document(page_content='「リスクなしに高�', metadata={}), 
 Document(page_content='高収益が得ら', metadata={}), 
 Document(page_content='得られる革新', metadata={}), 
 Document(page_content='�新的なビジネスは', metadata={}), 
 Document(page_content='ネスはないのか」とい', metadata={}), 
 Document(page_content='」というような意', metadata={}), 
 Document(page_content='な意見をよく�', metadata={}), 
 Document(page_content='�く耳にするが、「', metadata={}), 
 Document(page_content='が、「痩せたい', metadata={}), 
 Document(page_content='�たいけど甘い', metadata={}), 
 Document(page_content='甘いものが食べ', metadata={}), 
 Document(page_content='�べたい」という女', metadata={}), 
 Document(page_content='いう女子高生と同', metadata={}), 
 Document(page_content='と同じ意見に', metadata={}), 
 Document(page_content='見に聞こえる', metadata={}), 
 Document(page_content='える。', metadata={})]

御覧の通り、日本語の方が分割数が多いのが確認できると思います。英語や多くのヨーロピアン言語と異なり、日本語を含むアジア圏の国は「スペースで単語を区切る」という言語構造をしていないこともあり、こうした分割時には言語固有の癖が出ているのが確認できます。設定した chunk_size や chunk_overlap からするとやや文字数が少ない様に見受けられるのですが、単にマルチバイト文字だからなのかこの辺りはもしわかる方がコメント頂ければ幸いです。

chunk_size を変えて試してみる

では次に実際のデータを食わせて考えてみましょう今回は以下のPDF ファイル(に似たものを厳密には利用しています) を試したいと思います。ずいぶん前になりますが、Azure における EA と CSP における subscription 管理についてまとめた資料になります。こちらは EA や CSP とは何か?に加え、Microsoft アカウントと組織アカウントについて、一つの Azure AD テナントに複数の EA/CSP subscription がつけられるか等がまとまっています。

www.slideshare.net

それぞれ以下のパラメータで Azure Cognitive Search 上でインデックスを作成してみました。

  • testindex21: chunk_size = 500, chunk_overlap = 100 のケース
  • testindex22: chunk_size = 1000, chunk_overlap = 100 のケース
  • testindex23: chunk_size = 2000, chunk_overlap = 100 のケース
  • testindex24: chunk_size = 4000, chunk_overlap = 100 のケース

数字を見ればご認識頂けると思いますが、chunk_size を大きくした分だけまとまりが大きくなっているので、その分だけ Azure Cognitive Search のドキュメント数が少なくなり、加えて冗長分が減っただけデータサイズも小さくなっています。
次に実際にクエリを発行してみましょう。抜粋するとコード片は以下です(※全体を見たい場合は前回の記事を参照下さい)。

index_name ='testindex21'# 'testindex22', 'testindex23', 'testindex24'
question = 'What is best practice to leverage both EA and CSP for a customer?'

prompt =  PROMPT
chat_history = []

(中略)

question_generator = LLMChain(llm=llm, prompt=CONDENSE_QUESTION_PROMPT, verbose=False)
doc_chain = load_qa_with_sources_chain(llm, chain_type="stuff", verbose=True, prompt=prompt)
chain = ConversationalRetrievalChain(
    retriever=vector_store.as_retriever(),
    question_generator=question_generator,
    combine_docs_chain=doc_chain,
    return_source_documents=True,
    max_tokens_limit=7000
)
result = chain({"question": question, "chat_history": chat_history})
context = "\n".join(list(map(lambda x: x.page_content, result['source_documents'])))
sources = "\n".join(set(map(lambda x: x.metadata["source"], result['source_documents'])))
result['answer'] = result['answer'].split('SOURCES:')[0].split('Sources:')[0].split('SOURCE:')[0].split('Source:')[0]

print('#### source ')
print(sources)
print('#### question ')
print(question)
print('#### answer ')
print(result['answer'])

各処理結果を見てみましょう。
testindex21: chunk_size = 500, chunk_overlap = 100 のケース

index_name ='testindex21'
#### source
[https://xxxxxxxxxxxx.blob.core.windows.net/shared/Azure%20Subscription%20Management%20with%20EA%20and%20CSP.pdf](https://xxxxxxxxxxxx.blob.core.windows.net/shared/Azure%20Subscription%20Management%20with%20EA%20and%20CSP.pdf_SAS_TOKEN_PLACEHOLDER_)
#### question
What is best practice to leverage both EA and CSP for a customer?
#### answer
The best practice to leverage both EA and CSP for a customer is to associate their EA and CSP subscriptions into the same AAD tenant and perform a migration assessment for using Azure resources. (Slide 42)

御覧の様にかなり簡潔な答えが返ってきます。スライド番号まで示してくれているので確認したところ、以下のスライドなのでかなりいい線に行っている気がしますが、別に migration assessment は必須ではないのでどこかで意味が混じっている気がします。

testindex22: chunk_size = 1000, chunk_overlap = 100 のケース

#### source
[https://xxxxxxxxxxxx.blob.core.windows.net/shared/Azure%20Subscription%20Management%20with%20EA%20and%20CSP.pdf](https://xxxxxxxxxxxx.blob.core.windows.net/shared/Azure%20Subscription%20Management%20with%20EA%20and%20CSP.pdf_SAS_TOKEN_PLACEHOLDER_)
#### question
What is best practice to leverage both EA and CSP for a customer?
#### answer
The best practice to leverage both EA and CSP for a customer is to associate EA and CSP subscriptions into the same AAD tenant and perform a migration assessment for using Azure resources. (Slide 42)

こちらは殆ど結果は一緒です。やや文言は異なりますが、同じ内容が帰ってきていると言い切って問題ないでしょう。

testindex23: chunk_size = 2000, chunk_overlap = 100 のケース

#### source
[https://xxxxxxxxxxxx.blob.core.windows.net/shared/Azure%20Subscription%20Management%20with%20EA%20and%20CSP.pdf](https://xxxxxxxxxxxx.blob.core.windows.net/shared/Azure%20Subscription%20Management%20with%20EA%20and%20CSP.pdf_SAS_TOKEN_PLACEHOLDER_)
#### question
What is best practice to leverage both EA and CSP for a customer?
#### answer
The best practice to leverage both EA and CSP for a customer is to use EA for centralized management and governance of Azure subscriptions, and CSP for flexible and scalable purchasing and support options for individual customers. (   

こちらの結果では CSP の説明に重きを置いた結果が返ってきていますが、参照スライド番号が消えてしまっています。こちらはちょっとイマイチな結果になっていると言っていいでしょう。

testindex24: chunk_size = 4000, chunk_overlap = 100 のケース

    resp, got_stream = self._interpret_response(result, stream)
                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\daisami\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.11_qbz5n2kfra8p0\LocalCache\local-packages\Python311\site-packages\openai\api_requestor.py", line 624, in _interpret_response
    self._interpret_response_line(
  File "C:\Users\daisami\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.11_qbz5n2kfra8p0\LocalCache\local-packages\Python311\site-packages\openai\api_requestor.py", line 687, in _interpret_response_line
    raise self.handle_error_response(
openai.error.InvalidRequestError: This model's maximum context length is 8192 tokens. However, your messages resulted in 16409 tokens. Please reduce the length of the messages.

こちらではエラーとなってしまいました。どうやらモデルの context length を超えてしまったようです。エラー回避のために以下の様にコードを変更しました。

chain = ConversationalRetrievalChain(
    retriever=vector_store.as_retriever(),
    question_generator=question_generator,
    combine_docs_chain=doc_chain,
    return_source_documents=True,
    max_tokens_limit=7000
)

結果で帰ってきた内容は以下となります。

#### source
[https://xxxxxxxxxxxx.blob.core.windows.net/shared/Azure%20Subscription%20Management%20with%20EA%20and%20CSP.pdf](https://xxxxxxxxxxxx.blob.core.windows.net/shared/Azure%20Subscription%20Management%20with%20EA%20and%20CSP.pdf_SAS_TOKEN_PLACEHOLDER_)
#### question
What is best practice to leverage both EA and CSP for a customer?
#### answer
The best practice to leverage both EA and CSP for a customer is to use the EA for centralized billing and management of large-scale workloads, and CSP for agile and flexible management of smaller workloads. (

三つ目のケースと似たような内容が帰ってきています。

結果

御覧の様に chunk サイズを伸ばしても結果が洗練されるわけではないということが分かりました。今回は英語で chunk_size を 500 としましたが、英語では500単語という意味なるので、最初からやや大き目な chunk_size だったのかなと思います。
日本語で実施した場合は chunk_size は大き目にしないと文脈を失う可能性がありそうですが、chunk_size を大きくしすぎると token 数の制限に引っかかってしまう点にも注意です。

Semantic Kernel を利用して Azure Cognitive Search のインデックスにベクトル検索してみる(失敗編)

$
0
0

以前に Azure Cognitive Search がベクトル検索に対応した件、pythonでベクトル検索を実施した件をそれぞれ試しました。
normalian.hatenablog.com
normalian.hatenablog.com
Azure OpenAI とベクトル検索を組み合わせてどの様に動くかはご理解いただけたと思いますが、これらはどちらも Pythonコードで実行されており、C#等になじみのある方々にとって自身のアプリに組み込むのは少しハードルがある状況だと思います。

こちらに対して、Python側で主要なライブラリである LangChain 相当のことを目指す Sentiment Kernel と呼ばれるライブラリが既に発表されており、既に Azure Cognitive Search 向けのコネクタも提供されていることが分かりました。
github.com

今回は実際にソースコードを書いて&実行して Azure Cognitive Search のインデックスにベクトル検索をしてみました。が、結果から言うと「Semantic Kernel の Azure Cognitive Search 向け Connector がベクトル検索に対応していない」という理由で失敗しました(あくまで2023年7月7日現在の話)。ここまでアレコレしたので、せっかくなので途中経過だろうがまとめようと思ったのが今回の記事になります

Semantic Kernel で Azure Cognitive Search を利用する

Semantic Kernel で Azure Cognitive Search を利用する場合は以下の様に Microsoft.SemanticKernel と Microsoft.SemanticKernel.Connectors.Memory.AzureCognitiveSearch のライブラリを nuget 経由でインストールする必要があります。

この際に include prereleaseにチェックを入れるのを忘れないようにしましょう。

以下が実際に利用したコードですが、実行前に Azure Cognitive Search のインデックス側にデータを格納する必要があります。Azure Cognitive Search という「ベクトル化データのデータストア」にどの様にデータを格納するかは Azure OpenAI での Embedding 時にパラメータ弄って観察する - normalian blogを参照ください。

using Microsoft.SemanticKernel;

namespace MyOpenAITest
{
    class Program
    {
        public static async Task Main(string[] args)
        {
            string deploymentName = "text-davinci-003";
            string endpoint = "https://xxxxxxxxxxxxxx.openai.azure.com/";
            string openAiKey = "your-openai-key";

            string deploymentName_embedding = "text-embedding-ada-002";
            string azuresearchEndpoint = "https://xxxxxxxxxxxxx-koreasouth.search.windows.net";
            string azuresearchKey = "your-azuresearch-key";
            string azuresearchIndexname = "your-index-name";

            Console.WriteLine("== Start Applicaiton ==");

            var builder = new KernelBuilder();
            builder.WithAzureTextCompletionService(
                     deploymentName, // Azure OpenAI Deployment Name
                     endpoint,       // Azure OpenAI Endpoint
                     openAiKey);

            IKernel kernel = Kernel.Builder
                .WithOpenAITextCompletionService(deploymentName, openAiKey)
                .WithOpenAITextEmbeddingGenerationService(deploymentName_embedding, openAiKey)
                .WithAzureCognitiveSearchMemory(
                    azuresearchEndpoint,
                    azuresearchKey)
                .Build();

            var searchResult = kernel.Memory.SearchAsync(azuresearchIndexname,
                "Please let me know Azure subscription structure");

            await foreach (var result in searchResult) 
            {
                Console.Write($"id ={result.Metadata.Id}, text={result.Metadata.Text}");
            }

            Console.WriteLine("== End Application ==");
        }
    }
}

こちらを実行すると残念ながら以下の結果が返ってきます。値の中身が全く入っていません。

なんで検索が上手くいかないの?

その答えは Semantic Kernel のソースコード自体を読み込むことで理解ができました。以下を参照ください。
github.com
以下のソースコード周辺で "// TODO: use vectors"という記載があり、今の時点ではベクトル検索にライブラリ自体が対応していないことが見受けられます(これは多少弄った程度だとどうにもならなそうでした)。

ハマったところ① - Semantic 検索がオフにできない

最初に出くわしたエラーは以下の「Semantic 検索を有効にしてないぞ」です。

こちらは困った問題で、現時点(2023年7月7日現在)では Japan East/Japan West ともに Azure Cognitive Search は Semantic 検索 に対応しておりません。
azure.microsoft.com
仕方ないので Korea South で Azure Cognitive Search のリソースを作り直して回避しました。
Korea South リージョンで Azure Cognitive Search のリソースを作ると以下の様に左側のメニューに「Semantic Search (preview)」が表示されるので、ここで有効化します。

ハマったところ② - Semantic 検索用の設定を作る

Azure Cognitive Search のインデックスにて Semantic 検索用の設定をしない場合、今度は以下のエラーが発生します。

こちらに関しては以下の様にポータル側で設定しようとしたのですが、何故か Save リンク(ボタン?)が有効にならずにうまくいきませんでした

仕方がないので上記スクリーンショットでの "Edit JSON"リンクを押下し、以下の設定を追記しました。

"semantic": {"configurations": [{"name": "default",
          "prioritizedFields": {"titleField": {"fieldName": "title"
                },
            "prioritizedContentFields": [{"fieldName": "content"
              }],
            "prioritizedKeywordsFields": [{"fieldName": "tag"
              }]}}]}

加えて、この設定名は必ず default にする必要があります。間違えると以下のエラーが発生します。

そこについては以下のソースコードを参照すると分かりますが、直接ハードコードされています。
github.com

結論

あくまで2023年7月7日現在となりますが、Azure Cognitive Search に対しては Semantic Kernel はまだ未成熟なようです。その一方で別のベクトル化データのストアは当然あるので、そういったものをアレコレ調べるのも面白そうです。

Semantic Kernel を利用して PosgreSQL を利用してベクトル検索をしてみる

$
0
0

今回は掲題通り、Semanic Kernel を利用して PosgreSQL をベクトルストアとして利用してみたいと思います。前回のポストでは Azure Cognitive Search をベクトルストアとして利用しようと試みましたが、connecter の成熟度が現時点(2023年7月7日現在)はイマイチなこともあり、こちらの活用は難しい状況でした。
normalian.hatenablog.com

こちらに対して、現時点で Azure 上でベクトルストアとして利用できる組み込みサービスは現時点では以下となる認識です。

日本で利用することを考える場合、Azure Database for PostgreSQL - フレキシブル サーバが良さそうな感じがしています。そこで今回は Semantic Kernel を利用して Azure Database for PostgreSQLをベクトルデータストアとして活用します。

Azure Database for PostgreSQL - フレキシブル サーバ の作成と設定

Azure Portal上からリソースを作成します。以下の様に似たリソースがいくつかありますが、Azure Database for PosgreSQL flexible servers を選択します。

リソース作成後、ポータル上より以下の様に Server parameters のメニュー内にある azure.extensions から VECTORを有効化しました。

しかし、後で紹介するソースコード実行時に機能が有効化されていない状態でした。もし同様の現象が発生した場合、以下の様にコマンドを実行して有効化しましょう。

daisami@mysurfacebook1:~$ psql -h your-server-name.postgres.database.azure.com -p 5432 -U myadminuser sk_demo
Password for user myadminuser:
psql (12.15 (Ubuntu 12.15-0ubuntu0.20.04.1), server 14.7)
WARNING: psql major version 12, server major version 14.
         Some psql features might not work.
SSL connection (protocol: TLSv1.3, cipher: TLS_AES_256_GCM_SHA384, bits: 256, compression: off)
Type "help" for help.

sk_demo=> CREATE EXTENSION IF NOT EXISTS "vector";
NOTICE:  extension "vector" already exists, skipping
CREATE EXTENSION
sk_demo=> CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
NOTICE:  extension "uuid-ossp" already exists, skipping
CREATE EXTENSION
sk_demo=>

これで PosgreSQL でベクトル化データを保存できるようになりました。こちらはてらだよしおさんの以下の記事を参考にしています。
qiita.com

C#の Semantic Kernel を利用してPosgreSQL にベクトル化データを保存する

こちらに関してはほぼ以下のサンプルコードからのぱくりとなりますが、PosgreSQL との組み合わせが分からない人が多いと思いますので参考の為にアレコレ書いておきます。
learn.microsoft.com

まず、C#のプロジェクトを作成し、以下の様に Microsoft.SemanticKernel と Microsoft.SemanticKernel.Connectors.Memory.Postgres を nuget を使って読み込みましょう。

次に以下のソースコードを実行します。

using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Connectors.Memory.Postgres;
using Microsoft.SemanticKernel.Memory;
using Npgsql;
using Pgvector.Npgsql;

namespace MyOpenAITest
{
    classProgram
    { 
        publicstaticasync Task Main(string[] args) {
            // Azure OpenAI parametersconststring deploymentName_chat ="text-davinci-003";
            conststring endpoint ="https://your-endpoint.openai.azure.com/";
            conststring openAiKey ="open-ai-key";
            conststring deploymentName_embedding ="text-embedding-ada-002";

            // posgresql connection stringconststring connectionString ="Host=your-server-name.postgres.database.azure.com;Port=5432;Database=sk_demo;User Id=your-username;Password=your-password";

            Console.WriteLine("== Start Applicaiton ==");

            NpgsqlDataSourceBuilder dataSourceBuilder =new NpgsqlDataSourceBuilder(connectionString);
            dataSourceBuilder.UseVector();
            NpgsqlDataSource dataSource = dataSourceBuilder.Build();
            PostgresMemoryStore memoryStore =new PostgresMemoryStore(dataSource, vectorSize:1536/*, schema: "public" */);

            IKernel kernel = Kernel.Builder
                // .WithLogger(ConsoleLogger.Log)
                .WithAzureTextCompletionService(deploymentName_chat, endpoint, openAiKey)
                .WithAzureTextEmbeddingGenerationService(deploymentName_embedding, endpoint, openAiKey)
                .WithMemoryStorage(memoryStore)
                .WithPostgresMemoryStore(dataSource, vectorSize:1536, schema:"public")
                .Build();

            conststring memoryCollectionName ="SKGitHub";

            var githubFiles =new Dictionary<string, string>()
            {
                ["https://github.com/microsoft/semantic-kernel/blob/main/README.md"]
                    ="README: Installation, getting started, and how to contribute",
                ["https://github.com/microsoft/semantic-kernel/blob/main/samples/notebooks/dotnet/02-running-prompts-from-file.ipynb"]
                    ="Jupyter notebook describing how to pass prompts from a file to a semantic skill or function",
                ["https://github.com/microsoft/semantic-kernel/blob/main/samples/notebooks/dotnet/00-getting-started.ipynb"]
                    ="Jupyter notebook describing how to get started with the Semantic Kernel",
                ["https://github.com/microsoft/semantic-kernel/tree/main/samples/skills/ChatSkill/ChatGPT"]
                    ="Sample demonstrating how to create a chat skill interfacing with ChatGPT",
                ["https://github.com/microsoft/semantic-kernel/blob/main/dotnet/src/SemanticKernel/Memory/Volatile/VolatileMemoryStore.cs"]
                    ="C# class that defines a volatile embedding store",
                ["https://github.com/microsoft/semantic-kernel/tree/main/samples/dotnet/KernelHttpServer/README.md"]
                    ="README: How to set up a Semantic Kernel Service API using Azure Function Runtime v4",
                ["https://github.com/microsoft/semantic-kernel/tree/main/samples/apps/chat-summary-webapp-react/README.md"]
                    ="README: README associated with a sample starter react-based chat summary webapp",
            };

            Console.WriteLine("Adding some GitHub file URLs and their descriptions to a volatile Semantic Memory.");
            int i =0;
            foreach (var entry in githubFiles)
            {
                await kernel.Memory.SaveReferenceAsync(
                    collection: memoryCollectionName,
                    description: entry.Value,
                    text: entry.Value,
                    externalId: entry.Key,
                    externalSourceName:"GitHub"
                );
                Console.WriteLine($"  URL {++i} saved");
            }

            string ask ="I love Jupyter notebooks, how should I get started?";
            Console.WriteLine("===========================\n"+"Query: "+ ask +"\n");

            var memories = kernel.Memory.SearchAsync(memoryCollectionName, ask, limit:5, minRelevanceScore:0.77);

            int j =0;
            awaitforeach (MemoryQueryResult memory in memories)
            {
                Console.WriteLine($"Result {++j}:");
                Console.WriteLine("  URL:     : "+ memory.Metadata.Id);
                Console.WriteLine("  Title    : "+ memory.Metadata.Description);
                Console.WriteLine("  Relevance: "+ memory.Relevance);
                Console.WriteLine();
            }

            Console.WriteLine("== End Application ==");
        }
    }
}

上記を実行すると以下の結果となります。

こちらはインターネット上から直接テキストを読み取っているだけなので、こちらを PDF やオフィスファイル等と組み合わせればアレコレできると思います。

Azure OpenAI でトークン制限に引っかかった時に発生する例外

$
0
0

ご存じの通り、Azure OpenAI は非常に需要が高いサービスですが、その要求にこたえる必要もあり、一分辺りに利用できるトークン数が決まっています。以下のサイトにはそちらの詳細が記載されています。
learn.microsoft.com

Azure OpenAI の Tokens per Minute Rate Limit は以下の様に、特定のデプロイメントに対しての割り当てが可能であり、モデル&リージョン&サブスクリプション単位で上限が割り振られます。

上記の様に各デプロイメントに対して上限割り振りが可能ですが、以下のような状況になります。

  • 例:Japan East に gpt-35-turbo モデルの MRL を deployment-01 に 120k 割り振った場合
    • UK South 等の他のリージョンは deployment-01 の影響を受けず、gpt-35-turbo モデルの新規デプロイメントに MRL を割り当て可
    • Japan East に gpt-35-turbo モデルを使った新規デプロイメントdeployment-02 を作成する場合、MRL は何も割り当てられない

つまり Azure OpenAI の Tokens per Minute Rate Limit 上限を回避するには「①リージョンを分ける」か「②サブスクリプションを分ける」かのどちらか(または両方)を行う必要があるというのが現状です。その場合、「プログラム側にどのようなエラーメッセージが返ってくるのか?」が振り分け時のキモになると考えています。以前に作成した Javaでは以下の様なエラーが返ってきました。

        at xyz.normalian.example.ChatCompletionApplication.main(ChatCompletionApplication.java:100)
Exception in thread "main" org.springframework.web.client.HttpClientErrorException$TooManyRequests: 429 Too Many Requests: "{"error":{"code":"429","message": "Requests to the Creates a completion for the chat message Operation under Azure OpenAI API version 2023-03-15-preview have exceeded call rate limit of your current OpenAI S0 pricing tier. Please retry after 8 seconds. Please go here: https://aka.ms/oai/quotaincrease if you would like to further increase the default rate limit."}}"

エラーメッセージには Token 上限に引っかかった旨、何秒後にリトライしたら良いかが記載されているのが分かると思います。また、以下のサイトでの Quota を増やす申請も可能です。
https://aka.ms/oai/quotaincrease

こちらが参考になれば幸いです。

Semantic Kernel/C# をフロントエンド、LangChain/Python をバックエンドとして Azure OpenAI を使えるか?

$
0
0

未だ Semantic Kernel を勉強中の身ですが、C#でもベクトルデータを利用できるコードを書けるのは非常にナイスです。Python側も当然強力な言語ですが、Web でのフロントエンド構築を含むユーザインタフェースを作成する際は C#等を利用することが多い認識です。以下の記事で C#での Azure OpenAI 利用を試しましたが、C#を利用することで様々な Azure 機能と連携しやすくなるのも大きな利点あり、個人的には特に Azure 上で Application Insights 等のロギングライブラリと連携が容易になる点が魅力的です。
normalian.hatenablog.com

今回は Azure PostgreSQLをデータストアとして Semantic Kernel/C#が フロントエンド、LangChain/Pythonをバックエンドとして Azure OpenAI を使えるかということを検証したいと思います。

LangChain/Python側でドキュメントをベクトルデータ化して PosgreSQL に保存する

さっそく利用したソースコードの紹介となりますが、以下となります。もともと Azure Cognitive Search 側で利用していたソースコードを PosgreSQL に変えただけです。

import os
import re
from typing import List, Tuple
from dotenv import load_dotenv

from langchain.embeddings.openai import OpenAIEmbeddings
from langchain.vectorstores.pgvector import PGVector
from langchain.document_loaders import WebBaseLoader
from langchain.text_splitter import TokenTextSplitter, TextSplitter
import hashlib

deployment="text-embedding-ada-002"
chunk_size=1
openai_api_type="azure"
openai_api_key="your-azure-openai-key"
openai_api_base="https://your-azure-openai-endpoint.openai.azure.com"
openai_api_version="2023-03-15-preview"

embeddings = OpenAIEmbeddings(
    deployment=deployment, 
    chunk_size=chunk_size,
    openai_api_type=openai_api_type,
    openai_api_key=openai_api_key,
    openai_api_base=openai_api_base,
    openai_api_version=openai_api_version)

# このコレクション名がデータストア時に反映されない、なぜだ…
COLLECTION_NAME = "test02"

CONNECTION_STRING = PGVector.connection_string_from_db_params(
    driver=os.environ.get("PGVECTOR_DRIVER", "psycopg2"),
    host=os.environ.get("PGVECTOR_HOST", "your-severname.postgres.database.azure.com"),
    port=int(os.environ.get("PGVECTOR_PORT", "5432")),
    database=os.environ.get("PGVECTOR_DATABASE", "database-name"),
    user=os.environ.get("PGVECTOR_USER", "your-username"),
    password=os.environ.get("PGVECTOR_PASSWORD", "your-password"), # パスワードに @ を含めると文字列パースでエラーになった(汗
)

vector_store = PGVector(
    collection_name=COLLECTION_NAME,
    connection_string=CONNECTION_STRING,
    embedding_function=embeddings,
)

source_url = 'https://your-blobstorage-account.blob.core.windows.net/your-document'print("############# start #1 ")
chunk_size = 400
chunk_overlap = 100
document_loaders = WebBaseLoader
text_splitter: TextSplitter = TokenTextSplitter(chunk_size=chunk_size, chunk_overlap=chunk_overlap)
documents = document_loaders(source_url).load()
docs = text_splitter.split_documents(documents)

print("############# start #2 ")
# Remove half non-ascii character from start/end of doc content (langchain TokenTextSplitter may split a non-ascii character in half)# Azure Cognitive Search だと要らなかったが、PosgreSQL だと必要だった
pattern = re.compile(r'[\x00-\x09\x0b\x0c\x0e-\x1f\x7f\u0080-\u00a0\u2000-\u3000\ufff0-\uffff]')  # do not remove \x0a (\n) nor \x0d (\r)for(doc) in docs:
    doc.page_content = re.sub(pattern, '', doc.page_content)
    if doc.page_content == '':
        docs.remove(doc)
            
keys = []
for i, doc inenumerate(docs):
    # Create a unique key for the document# print(doc)
    source_url = source_url.split('?')[0]
    filename = "/".join(source_url.split('/')[4:])
    hash_key = hashlib.sha1(f"{source_url}_{i}".encode('utf-8')).hexdigest()
    hash_key = f"doc:{COLLECTION_NAME}:{hash_key}"
    keys.append(hash_key)
    doc.metadata = {"source": f"[{source_url}]({source_url}_SAS_TOKEN_PLACEHOLDER_)" , "chunk": i, "key": hash_key, "filename": filename}

print("############# start #3 ")
vector_store.add_documents(documents=docs)

print("############# end")

ソースコード内に色々と TIPS を記載しましたが、特殊文字処理が必要など、いくつかの対応が必要でした。上記のソースコードを実行すると以下の様に public."langchain_pg_collection"と public.langchain_pg_embedding という二つのスキーマが PosgreSQL 上に作成されます。スキーマの詳細については以下を参照下さい。

LangChain/Pythonの場合に作成されるスキーマ名を調べたところ、以下の様にハードコードされている箇所がありました。
github.com
なぜ collection 名を指定しても反映されなかったのかはやや腑に落ちませんが、本題ではないので話題を先に進めたいと思います。

ベクトル形式でドキュメントがデータストアに格納されたので、次に Sematic Kernel 側で C#ソースコードを書いてみました。

using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Connectors.Memory.Postgres;
using Microsoft.SemanticKernel.Memory;
using Npgsql;
using Pgvector.Npgsql;

namespace MyOpenAITest
{
    class Program
    {
        public static async Task Main(string[] args) {
            // Azure OpenAI parameters
            const string deploymentName_chat = "text-davinci-003";
            const string endpoint = "https://your-azureopenai-endpoint.openai.azure.com/";
            const string openAiKey = "your-azureopenai-key";
            const string deploymentName_embedding = "text-embedding-ada-002";

            // posgresql connection string
            const string connectionString = "Host=your-posgresql-servername.postgres.database.azure.com;Port=5432;Database=your-dbname;User Id=your-usename;Password=your-password";

            Console.WriteLine("== Start Applicaiton ==");

            NpgsqlDataSourceBuilder dataSourceBuilder = new NpgsqlDataSourceBuilder(connectionString);
            dataSourceBuilder.UseVector();
            NpgsqlDataSource dataSource = dataSourceBuilder.Build();
            PostgresMemoryStore memoryStore = new PostgresMemoryStore(dataSource, vectorSize: 1536/*, schema: "public" */);

            IKernel kernel = Kernel.Builder
                // .WithLogger(ConsoleLogger.Log)
                .WithAzureTextCompletionService(deploymentName_chat, endpoint, openAiKey)
                .WithAzureTextEmbeddingGenerationService(deploymentName_embedding, endpoint, openAiKey)
                .WithMemoryStorage(memoryStore)
                .WithPostgresMemoryStore(dataSource, vectorSize: 1536, schema: "public")
                .Build();

            string ask = "How can I Associate EA and CSP subscriptions into same AAD tenant?";
            Console.WriteLine("===========================\n" +
                                "Query: " + ask + "\n");

            var memories = kernel.Memory.SearchAsync("langchain_pg_collection", ask, limit: 5, minRelevanceScore: 0.77);

            int j = 0;
            await foreach (MemoryQueryResult memory in memories)
            {
                Console.WriteLine($"Result {++j}:");
                Console.WriteLine("  URL:     : " + memory.Metadata.Id);
                Console.WriteLine("  Title    : " + memory.Metadata.Description);
                Console.WriteLine("  Relevance: " + memory.Relevance);
                Console.WriteLine();
            }
            Console.WriteLine("== End Application ==");
        }
    }
}

上記を実行すると以下の様に '42703: column "key" does not exist というエラーが発生します。どうやら key という column が無いようです。

この結果から、Semantic Kernel/C#と LangChain/Pythonではベクトルストアへのデータ格納時にスキーマが異なるのではとの推察をしました。

Semantic Kernel/C#側でベクトルデータを PosgreSQL に格納してスキーマを確認する

次に Semantic Kernel/C#側でベクトルデータを PosgreSQL に格納してみます。以下の様に GitHubのドキュメント文字列をベクトル化し、ベクトルデータストア(この場合は PosgreSQL)に格納します。

using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Connectors.Memory.Postgres;
using Microsoft.SemanticKernel.Memory;
using Npgsql;
using Pgvector.Npgsql;

namespace MyOpenAITest
{
    class Program
    {
        public static async Task Main(string[] args)
        {
            // Azure OpenAI parameters
            const string deploymentName_chat = "text-davinci-003";
            const string endpoint = "https://your-azureopenai-endpoint.openai.azure.com/";
            const string openAiKey = "your-azureopenai-key";
            const string deploymentName_embedding = "text-embedding-ada-002";

            // posgresql connection string
            const string connectionString = "Host=your-posgresql-endpoint.postgres.database.azure.com;Port=5432;Database=sk_demo;User Id=your-posgresql-username;Password=your-posgresql-password";

            Console.WriteLine("== Start Applicaiton ==");

            NpgsqlDataSourceBuilder dataSourceBuilder = new NpgsqlDataSourceBuilder(connectionString);
            dataSourceBuilder.UseVector();
            NpgsqlDataSource dataSource = dataSourceBuilder.Build();
            PostgresMemoryStore memoryStore = new PostgresMemoryStore(dataSource, vectorSize: 1536/*, schema: "public" */);

            IKernel kernel = Kernel.Builder
                // .WithLogger(ConsoleLogger.Log)
                .WithAzureTextCompletionService(deploymentName_chat, endpoint, openAiKey)
                .WithAzureTextEmbeddingGenerationService(deploymentName_embedding, endpoint, openAiKey)
                .WithMemoryStorage(memoryStore)
                .WithPostgresMemoryStore(dataSource, vectorSize: 1536, schema: "public")
                .Build();

            const string memoryCollectionName = "SKGitHub";

            var githubFiles = new Dictionary<string, string>()
            {
                ["https://github.com/microsoft/semantic-kernel/blob/main/README.md"]
                    = "README: Installation, getting started, and how to contribute",
                ["https://github.com/microsoft/semantic-kernel/blob/main/samples/notebooks/dotnet/02-running-prompts-from-file.ipynb"]
                    = "Jupyter notebook describing how to pass prompts from a file to a semantic skill or function",
                ["https://github.com/microsoft/semantic-kernel/blob/main/samples/notebooks/dotnet/00-getting-started.ipynb"]
                    = "Jupyter notebook describing how to get started with the Semantic Kernel",
                ["https://github.com/microsoft/semantic-kernel/tree/main/samples/skills/ChatSkill/ChatGPT"]
                    = "Sample demonstrating how to create a chat skill interfacing with ChatGPT",
                ["https://github.com/microsoft/semantic-kernel/blob/main/dotnet/src/SemanticKernel/Memory/Volatile/VolatileMemoryStore.cs"]
                    = "C# class that defines a volatile embedding store",
                ["https://github.com/microsoft/semantic-kernel/tree/main/samples/dotnet/KernelHttpServer/README.md"]
                    = "README: How to set up a Semantic Kernel Service API using Azure Function Runtime v4",
                ["https://github.com/microsoft/semantic-kernel/tree/main/samples/apps/chat-summary-webapp-react/README.md"]
                    = "README: README associated with a sample starter react-based chat summary webapp",
            };

            Console.WriteLine("Adding some GitHub file URLs and their descriptions to a volatile Semantic Memory.");
            int i = 0;
            foreach (var entry in githubFiles)
            {
                await kernel.Memory.SaveReferenceAsync(
                    collection: memoryCollectionName,
                    description: entry.Value,
                    text: entry.Value,
                    externalId: entry.Key,
                    externalSourceName: "GitHub"
                );
                Console.WriteLine($"  URL {++i} saved");
            }

            string ask = "How can I Associate EA and CSP subscriptions into same AAD tenant?";
            Console.WriteLine("===========================\n" +
                                "Query: " + ask + "\n");

            var memories = kernel.Memory.SearchAsync(memoryCollectionName, ask, limit: 5, minRelevanceScore: 0.77);
            //var memories = kernel.Memory.SearchAsync("langchain_pg_collection", ask, limit: 5, minRelevanceScore: 0.77);

            int j = 0;
            await foreach (MemoryQueryResult memory in memories)
            {
                Console.WriteLine($"Result {++j}:");
                Console.WriteLine("  URL:     : " + memory.Metadata.Id);
                Console.WriteLine("  Title    : " + memory.Metadata.Description);
                Console.WriteLine("  Relevance: " + memory.Relevance);
                Console.WriteLine();
            }

            Console.WriteLine("== End Application ==");
        }
    }
}

上記を実行後、PosgreSQL に接続した結果、スキーマは以下の様になりました。御覧の通り、Semantic Kernel 側で作成したスキーマには text 型のデータを格納する key という名前のカラムが存在しますが、LangChain 側には存在しないカラムなことが分かります。

そのほかにも、embedding されたデータを格納するカラムのデータ型・カラム名は同じなことは分かりますが、その他は異なるカラムでのデータが格納されていることが分かると思います。

「Semantic Kernel/C#をフロントエンド、LangChain/Pythonをバックエンドとして Azure OpenAI を使えるか?」の結論

本ポストの掲題でもあったテーマについては、実現は難しいというのが結論になると思います。両ライブラリで格納されたベクトルデータだけは同じですが、他のカラム等々は異なるデータ形式で格納されているので、現実的にはこちらを上手くマッピングさせて相互運用というのは難しいのではと考えています。

上記に加え、「既に PosgreSQL に格納されているベクトル化データをそのまま別のデータストア(Azure Cognitive Search 等)に移行したい」というベクトルデータの格納コンポーネントを変えるという処理も、今回の結果を踏まえれば難しいという推察ができます。データのマッピングを考えるより、新しくベクトル化データを作り直す方が現実的でしょう。

結果的に期待を満たすのでなく、実現が難しいという知見を得るにとどまってしまいましたが、こちらの検証結果が皆様のお役に立てば幸いです。


Azure Files のプライベートエンドポイントの IP アドレスを固定にする

$
0
0

今回は掲題のテーマを備忘録程度に記載したいと思います。ご存じの方はいらっしゃると思いますが、プライベートエンドポイントで固定 IP を振ることができる機能が2022年10月時点で提供されております。
azure.microsoft.com

しかし、たまたまポータルで Azure Files のにプライベートエンドポイントを作成したら固定 IP アドレスが割り振れず(嘘でした。追記を参照下さい)、作成後は IP アドレスが変更できませんでした。その結果、コマンドで試したら固定 IP が割り振れたので、以下の私が利用したスクリプトを備忘録的に張り付けておきます。

$resourcegroupname="xxxxxxxxx"
$storageaccountname="xxxxxxxxx"
$vnetname="xxxxxxxxx"
$subnetname="xxxxxxxxx"

storageAccountId=$(az storage account show --name $storageaccountname --resource-group $resourcegroupname  --query "id" --output tsv)

az network private-endpoint create \
    --name "myendpoint" \
    --resource-group $resourcegroupname \
    --vnet-name $vnetname \
    --subnet $subnetname \
    --private-connection-resource-id $storageAccountId \
    --group-id file \
    --connection-name "myConnection" \
    --ip-config name=ipconfig-1 group-id=file member-name=file private-ip-address=10.0.1.10 \

上記のスクリプトを実行すると以下の様にポータル上で IP アドレスが固定になっていることが確認できます。ご参考まで。

追記:後で確認したら以下の様にポータルで設定できました(汗

Semantic Kernel/C# で Azure Cognitive Search にベクトル化データを保存する(成功編

$
0
0

今回は掲題のテーマを試してみたいと思います。実は以下の記事にて試した結果、上手く動きませんでした。
normalian.hatenablog.com

以前試した際は Semantic Kernel 側が Azure Cognitive Search のコネクタ対し未実装だったので結果が返ってこないという悲しい自体でした。しかし、以下の記事の様に無事実装がされたようなのでさっそく試してみました。
devblogs.microsoft.com

事前準備

以下のリソースを事前に作成してください。

  • Azure OpenAI リソース、text-davinci-003、text-embedding-ada-002 モデルのデプロイメント
  • Azure Cognitive Search(今回は Japan East を利用

次に Visual StudioC#のプロジェクトを作成作成します。作成したプロジェクトに NuGet でのライブラリインストールします。Visual Studioで NuGet の画面を開き、以下の Microsoft.SemanticKernel と Microsoft.SemanticKernel.Connectors.Memory.AzureCognitiveSearch の二つをインストールします。この際に "Include prerelease"にチェックを入れるのを忘れないようにしてください。

ソースコードの作成

C#ソースコードを記載します。私が今回利用したものは以下となります。

using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Connectors.Memory.AzureCognitiveSearch;
using Microsoft.SemanticKernel.Memory;
using Microsoft.SemanticKernel.Text;
using System.Reflection;
using System.Security.Cryptography;

namespace MyOpenAITest
{
    class Program
    {
        public static async Task Main(string[] args)
        {
            // Azure OpenAI parameters
            const string deploymentName_chat = "text-davinci-003";
            const string endpoint = "https://your-azureopenai-endpoint.openai.azure.com/";
            const string openAiKey = "your-azureopenai-key";
            const string deploymentName_embedding = "text-embedding-ada-002";

            const string azuresearchEndpoint = "https://your-azuresearch-endpoint.search.windows.net";
            const string azuresearchKey = "your-azuresearch-key";
            const string azuresearchIndexname = "SKGitHub";

            Console.WriteLine("== Start Applicaiton ==");

            var builder = new KernelBuilder();
            builder.WithAzureTextCompletionService(
                     deploymentName_chat,
                     endpoint,
                     openAiKey);
            builder.WithAzureTextEmbeddingGenerationService(
                deploymentName_embedding,
                endpoint,
                openAiKey);
            builder.WithMemoryStorage(new AzureCognitiveSearchMemoryStore(azuresearchEndpoint, azuresearchKey));

            var githubFiles = new Dictionary<string, string>()
            {
                ["https://github.com/microsoft/semantic-kernel/blob/main/README.md"]
                    = "README: Installation, getting started, and how to contribute",
                ["https://github.com/microsoft/semantic-kernel/blob/main/samples/notebooks/dotnet/02-running-prompts-from-file.ipynb"]
                    = "Jupyter notebook describing how to pass prompts from a file to a semantic skill or function",
                ["https://github.com/microsoft/semantic-kernel/blob/main/samples/notebooks/dotnet/00-getting-started.ipynb"]
                    = "Jupyter notebook describing how to get started with the Semantic Kernel",
                ["https://github.com/microsoft/semantic-kernel/tree/main/samples/skills/ChatSkill/ChatGPT"]
                    = "Sample demonstrating how to create a chat skill interfacing with ChatGPT",
                ["https://github.com/microsoft/semantic-kernel/blob/main/dotnet/src/SemanticKernel/Memory/Volatile/VolatileMemoryStore.cs"]
                    = "C# class that defines a volatile embedding store",
                ["https://github.com/microsoft/semantic-kernel/tree/main/samples/dotnet/KernelHttpServer/README.md"]
                    = "README: How to set up a Semantic Kernel Service API using Azure Function Runtime v4",
                ["https://github.com/microsoft/semantic-kernel/tree/main/samples/apps/chat-summary-webapp-react/README.md"]
                    = "README: README associated with a sample starter react-based chat summary webapp",
            };

            var kernel = builder.Build();
            Console.WriteLine("Adding some GitHub file URLs and their descriptions to a volatile Semantic Memory.");
            var i = 0;
            foreach (var entry in githubFiles)
            {
                await kernel.Memory.SaveReferenceAsync(
                    collection: azuresearchIndexname,
                    description: entry.Value,
                    text: entry.Value,
                    externalId: entry.Key,
                    externalSourceName: "GitHub"
                );
                Console.WriteLine($"  URL {++i} saved");
            }

            string ask = "I love Jupyter notebooks, how should I get started?";
            Console.WriteLine("===========================\n" +
                                "Query: " + ask + "\n");

            //From here, search with vector data store
            var memories = kernel.Memory.SearchAsync(azuresearchIndexname, ask, limit: 5, minRelevanceScore: 0.77);

            i = 0;
            await foreach (MemoryQueryResult memory in memories)
            {
                Console.WriteLine($"Result {++i}:");
                Console.WriteLine("  URL:     : " + memory.Metadata.Id);
                Console.WriteLine("  Title    : " + memory.Metadata.Description);
                Console.WriteLine("  Relevance: " + memory.Relevance);
                Console.WriteLine();
            }
        }
    }
}

アプリケーションの実行

実行すると以下のメッセージが返ってきました。 "Result 1:"が返ってきて、ちゃんと結果も記載されています。

Azure ポータル側でインデックスを確認すると、以下の様にドキュメントデータが保存されていることが分かります。

どの様なフィールドが作成されるかについては、以下のソースコードを参照すると理解が早い認識です。
semantic-kernel/dotnet/src/Connectors/Connectors.Memory.AzureCognitiveSearch/AzureCognitiveSearchMemoryRecord.cs at main · microsoft/semantic-kernel · GitHub

何とか無事に検索できることは分かりましたが、以下のソースコードを眺めている限り Azure Cognitive Search で利用可能なはずの Semantic Search を有効化するポイントがなく、どうやら実装を見送ったことが分かります。2023年7月時点でようやく Semantic Kernel で Azure Cognitive Search ベクトルデータストアとして活用することができるようになりましたが、急激に発展を遂げるこの分野、まだまだ目が離せないようです。
github.com

Azure Batch を試す際の TIPS を簡単にまとめてみた

$
0
0

ちょっと人に聞かれた際に久々に触ったので、振り返りを含めてちょっとまとめてみました。チュートリアル的な hello worldは既存の公式ドキュメントを読めば良いと思うので、簡単な概要部分だけ紹介した後は簡単な TIPS 系を羅列していきたいと思っています。

まず、Azure Batch の概要となります。Azure Batch は task という形式で様々な粒度の処理を実行可能であり、スケジューリング機能等々を持っています。以下に Azure Batch のアーキテクチャ図を記載しています。

最初に Azure Batch アカウントを作成しますが、簡単な利用をする場合に重要となる用語は Pool, Job, Applicationとなります。

  1. Pool :最初に作成する必要があり、様々な処理を実行するためのコンピュートリソースです。こちらは Azure になじみのある方なら VMSS の様なものと言えばわかりやすいかもしれません。図でも記載がある通り、既存の template image から OS を選択することもできますが、自身で作成した custom image の利用も可能です。
  2. Job :処理行うまとまりの単位になります。こちらには Job/Job Schedule という二種類があり、on-demand 実行をする job と定期実行をするための job の二種類が存在します。
  3. Application :自身が作成したカスタムアプリケーションを指します。zip 形式でアーカイブする必要があり、Azure Storage アカウント上に配置されることを想定しています。事前に Azure Batch アカウントに Azure Storage を紐づけるのを忘れないようにしましょう。

こちらのアーキテクチャについては以下の動画開始 100 秒位のところで図示されているので、そちらも参照すると良いと思います。

最初に Quotas タブを見よう

上記に記載した通り、最初に Pool を作成する必要がありますが、こちらは VMの SKU(Dv3 Series 等)を選択する必要があります。以下の画像でも表示されていますが、必要となる VM SKU の Quota が 0 の場合、Pool を作成してもリソースが割り当てられずに処理が実行されません。

Azure Batch アカウントの作成直後、こちらの Quota を確認し、必要な場合は Quota increase request をしてください。多少の時間がかかるので、これを最初にやるのがお勧めです。

Pool を作成する際の tips

様々な処理を実行するために必須な Pool を作成します。こちらも作成時に幾つかポイントがありますが、OS イメージの選択に幾つか方法があります。まずは以下の様に Marketplace から標準で提供されているものを選ぶパターン。

次に、以下の様に作成したカスタム VMイメージを利用することも可能です。今回は利用しませんでしたが、Docker イメージの利用も可能なようです。

次に、VM SKU の指定、ノード数等を指定するので、ここで指定する VM SKU は事前に Quota increase request を作成してください。

当然閉域網で処理を行いたいニーズも出てくると思いますが、コンピュートリソース単位(プール単位)で VNET 等の指定をすることが分かります。

Job を作成する際の TIPS

Job は実際にコマンドやプログラムを実行する単位である Task をグループ化したものになります。Job の作成時は以下の様に利用する Pool を指定することに加え、必要な場合は環境変数を指定することも可能です。

Job の配下に Task を作成する画面は以下の様になります。今回は以下の様に hello worldと出力するだけの Task となります。管理者権限で実行可能なことに加え、冒頭で説明した Application Package も利用可能なことが分かります。

こちらを実際に実行すると以下の様にすぐ実行が完了し、特にエラー等が発生しなければ Completed になります。

実行が完了した task01 をクリックする詳細画面となり、標準出力・標準エラー出力がそれぞれファイルとして格納されていることが分かります。stdout.txt を開くと hello worldが出力されていることが確認できます。

Application を利用する際の TIPS

せっかく Azure Batch を利用するので、OS 標準コマンドだけでなく作成したアプリケーションを利用したいこともあるでしょう。その場合、zip 形式にパッケージングしたファイルを Azure Storage にアップロードします。しかし、その前に zip ファイルを上げる先の Azure Storage を Azure Batch アカウントとリンクさせましょう。以下の画面の様になっていれば設定完了です。

今回の場合、Azure Storage にアップロードする zip ファイルは以下のようにパッケージしました。自作の pythonファイルを含んでいます。

test01.zip
        readme.md
        test01.py

ポータルのウィザードに従って正常にアップロード出来れば以下の様になります。

Job のセクションで紹介した通り、Job 作成時にアップロードした Application を指定します。加えて以下の様にスクリプトを指定します。

bash -c 'python3 $AZ_BATCH_APP_PACKAGE_test01_1_0_0/test01.py'

指定した Application の zip ファイルは特定の作業ディレクトリに展開されています。ディレクトリ名を含む環境変数は以下の記事が参考になります。
learn.microsoft.com

Azure Monitor を利用した場合

Diagnostic Setting を有効化し、Log Analytics ワークスペースにすべての情報を転送する設定をしました。しばらく操作をした後、試しに実行すると以下の様に AzureDiagnostics, AzureMetrics, Usage の三つのテーブルが作成されていました。試しに AzureMetrics にクエリを発行したところ、ノード数がどの程度使われているかを見ている様です。

次に Usage にクエリを投げてみましょう。データは出ていますが、deprecated になっており、別のテーブルを利用することが推奨されています。

最後に AzureDiagnostics へのクエリを実行すると ServiceLog と AuditLog が出力されています。AuditLog については以下の様にポータル操作のログが出力されています。ServiceLog 側はノードの実行数等が出力されていました。

Microsoft Entra アプリケーション プロキシで閉域なオンプレミスサーバにアクセスする+α な TIPS

$
0
0

皆様は Microsoft Entra アプリケーション プロキシ という機能をご存じでしょうか?Entra ID の認証・認可機能を利用して、オンプレミスのサービスに対してセキュアな接続を提供する社畜垂涎のサービスとなります。細かくは色々とありますが、以下の様に EntraID が認証した情報を用い、Microsoft Entra application proxy connectors をインストールした Windows Server を経由してセキュアかつ閉域的な Web サーバにアクセスすることが可能です。

learn.microsoft.com

Microsoft Entra application proxy connectors(実態はエージェントで Windowsサービスとして稼働。以下はコネクタと呼びます)をインストールした端末が必要&当該サーバが Windows Server である必要がありますが、接続先のサーバは Windows, Linux含めて OS は問わないのでかなり汎用性の高いサービスだと思っています。

昨今では Microsoft Entra Private Accessというサービスが’発表されており、今後 Entra ID 的にはこちらのサービスを利用することが推奨されると思いますが、現在 Microsoft Entra Private Accessはパブリックプレビューなので、まだまだ使いどころはあるサービスだと思っています。
www.microsoft.com

セットアップはどうやってやるの?

以下ふたつのセットアップが必要になります。ここでは既に HTTPSアクセスが可能な Web サーバ(今回は IISを利用していますが、Linuxでも構いません)がセットアップ済としています。Web サーバには認証局から発行された証明書を利用することを強く推奨します。オレオレ証明書を利用する場合、後述するようなエラーが発生します

  • Windows Server にコネクタのセットアップ
  • Entra ID に Enterprise Application を作成する

まず、アーキテクチャ図における「Windows Server ※要コネクタ」のセットアップを行います。当該サーバが Entra ID を介したユーザリクエストを中継処理するサーバとなります。上記で記載した Microsoft Entra Private Accessだと本サーバのセットアップは不要となりますが、逆にネットワーク分離的にはこちらのコネクタ入りの Windows Server が中継する様にネットワーク接続を制限するうま味もあるので残るのではないかという気もしております。
セットアップ自体は簡単です、以下のスクリーンショットに従い Entra ID の Application Proxy メニューに移動します。そこに Download connector service があるので、こちらでコネクタのセットアップ用のファイル(単なる exe 形式です)をダウンロードします。

コネクタのインストール先 Windows Server で以下の様にセットアップファイルを実行し、ウィザードに従ってセットアップを実行してください( Uninstall になってるのは既にセットアップ済のサーバで実行したからなので、ここが Install になってるはずです)。

セットアップが完了すると以下の様に Windowsサービスに Microsoft AAD Application Proxy Connector/Microsoft AAD Application Proxy Connector Updater の二つが表示されます。

最後に以下のスクリーンショットに従い Entra ID の Application Proxy メニューに移動します。こちらで当該 Windows Server の Status が Active になっていればセットアップ完了です。

オンプレミスのファイヤーウォールNSG等でアクセスが制限されている場合、Windows Server のセットアップが上手くいっていても Active にならない場合があります。その場合は以下のネットワーク接続要件を確認してください。
learn.microsoft.com


次にEntra ID 上に Enterprise Application のリソースを作ります。Azure ポータルから Entra ID の画面に遷移して以下の Enterprise applications を選択してください。ここでMicrosoft Entra ID P1 ライセンスが確認できますので、もしライセンスが割り当てられてない場合はライセンスの購入・割り当てを検討ください。

以下の New application からアプリケーションプロキシを利用するリソースを作成します。

New application を選択後、以下の画面となりますがこの中から Add an on-premise application を選択していきます。

次に各種パラメータを入力します。以下に図示しましたが、この中で特に重要なのが Internal Url と External Url です。

  • Name:ここに入力した名前で Entra ID 上に表示されます
  • External Url:ユーザが直接アクセスする URL になります。オンプレミスのリソースにコネクタ経由でアクセスする際、ユーザはこちらをブラウザ等に入力することになります
  • Internal Url:コネクタがリクエストを送るオンプレミス内の URL となります(今回の例では Azure 内ですが)。こちらの名前解決はコネクタをインストールしたサーバに準拠するので、極論当該サーバの hosts ファイルを編集しても対応可能です。こちらで既にセットアップ済 HTTPSアクセス対象の Web サーバの URL を指定してください
  • Connector Group:こちらは後述しますが、ここでコネクタをセットアップした Windows Server を選択します。

リソース作成後、本 Enterprise Application のリソースを利用するユーザを割り当てます。

最後にブラウザに External Url を入力してアクセスを試します。この際 Enterprise Application を作成した Entra ID テナントでの認証を求められるので、認証完了後に以下の様にオンプレミス(今回は相当)へのリソースへアクセスが可能となります。

オレオレ証明書を利用する場合

デフォルト設定でコネクタが認証済の証明書以外を利用する場合、以下の SSLに関するエラーを表示します。

開発環境に限定した場合ですが、Enterprise Application リソースの Application proxy メニューから、以下の Advanced メニューにおける Validate Backend SSL certificate メニューのチェックを外します。こちらの有効化には体感で数分~10分程度かかったので、設定直後にエラーが続いても焦らなくて OK です。

こちらでオレオレ証明書でもアクセス可能ですが、あくまで開発環境用だという点に留意ください。

ネット―ワークの接続要件は?

皆様の一番関心のあるところ&アプリケーションプロキシの良い点だと考えています。許可する必要があるトラフィックはコネクタが利用する一部のアウトバウンド接続であり、インバウンドは原則開放する必要がありません。以下に接続要件が記載されています。
learn.microsoft.com
インバウンドはセキュア通信であれば 443 をコネクタがインストールされた Windows Server 向けにだけ許可すれば良く、それ以外のサーバやポートに対しては特に公開する必要が無いのは大きなメリットだと考えています。

ライセンスは必要?

本サービスの利用には Microsoft Entra ID P1 or P2 ライセンスが必要になる点に注意が必要です。以下を参照ください。
Microsoft Entra アプリケーション プロキシに関してよく寄せられる質問 | Microsoft Learn

接続先の Internal URL に IP アドレスは指定できるの?

以下の様に IP アドレスを直接指定する場合、ポータルでエラーが発生して指定できません。認証局が発行した証明書の利用を前提としているので、IP アドレスの利用は非推奨どころか不可となります。

冗長化はどう考えたらいいの?

今回は特に触れませんでしたが、コネクタグループを利用して冗長化を行います。詳細は以下の記事を参照して頂ければと思いますが、基本的には用途ごとにコネクタグループを作成し、各コネクタグループにコネクタをセットアップしたサーバを複数登録することで冗長化します。
learn.microsoft.com

Bicep ファイル で Azure Firewall のルール上の IP Group を更新する

$
0
0

ちょっと人から聞かれて軽く書いておいたので供養致します。特に「IP Group が複数の場合にどうするの?」と聞かれたので、以下が bicep ファイルの内容です。

resource firewallPolicy 'Microsoft.Network/firewallPolicies@2023-04-01' existing = {
  name: 'StandardPolicy'
}

resource ipgroup1 'Microsoft.Network/ipGroups@2023-04-01' existing = {
    name: 'IPG-Test01-WestUS2'
}

resource ipgroup2 'Microsoft.Network/ipGroups@2023-04-01' existing = {
    name: 'IPG-Test02-WestUS2'
}

resource networkRuleCollectionGroup 'Microsoft.Network/firewallPolicies/ruleCollectionGroups@2022-01-01' = {
  parent: firewallPolicy
  name: 'DefaultNetworkRuleCollectionGroup'
  properties: {
    priority: 2000
    ruleCollections: [
      {
        ruleCollectionType: 'FirewallPolicyFilterRuleCollection'
        action: {
          type: 'Allow'
        }
        name: 'Net-RuleCollection-01'
        priority: 1250
        rules: [
          {
            ruleType: 'NetworkRule'
            name: 'time-windows'
            ipProtocols: [
              'ANY'
            ]
            destinationAddresses: [
              'AzureCloud'
            ]
            sourceIpGroups: [
                ipgroup1.id
                ipgroup2.id
            ]
            destinationPorts: [
              '80'
            ]
          }
        ]
      }
    ]
  }
}

それを以下のコマンドでデプロイしました。

az deployment group create --resource-group "your resource group" --template-file "your bicep file"--mode incremental

ここでのポイントは既存のリソースに対する update なので bicep 側で existing キーワードを忘れない様にすることです。IP Group 辺りでうっかり忘れると以下の様になるのでご注意を。

az deployment group create --resource-group RG-Firewall-Test-WestUS2 --template-file .\hello01.bicep --mode incremental
az : WARNING: C:\Users\daisami\OneDrive - Microsoft\Desktop\hello01.bicep(1,7) : Warning no-unused-params: Parameter "location" is declared but never used. 
[https://aka.ms/bicep/linter/no-unused-params]
At line:1 char:1
+ az deployment group create --resource-group RG-Firewall-Test-WestUS2  ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (WARNING: C:\Use...-unused-params]:String) [], RemoteException
    + FullyQualifiedErrorId : NativeCommandError
 
ERROR: {"status":"Failed","error":{"code":"DeploymentFailed","target":"/subscriptions/423a72cd-b110-476f-8b8b-0569fe1773bd/resourceGroups/RG-Firewall-Test-West
US2/providers/Microsoft.Resources/deployments/hello01","message":"At least one resource deployment operation failed. Please list deployment operations for 
details. Please see https://aka.ms/arm-deployment-operations for usage details.","details":[{"code":"BadRequest","target":"/subscriptions/423a72cd-b110-476f-8b
8b-0569fe1773bd/resourceGroups/RG-Firewall-Test-WestUS2/providers/Microsoft.Resources/deployments/hello01","message":"{\r\n  \"Message\": \"The request is 
invalid.\",\r\n  \"ModelState\": {\r\n    \"resource\": [\r\n      
\"{\\\"Error\\\":{\\\"Code\\\":\\\"FirewallPolicyRuleCollectionGroupInvalidPriorityValue\\\",\\\"Message\\\":\\\"Invalid Rule Collection Group . Invalid 
priority value 0, must be between 100 and 65000\\\",\\\"Target\\\":null,\\\"Details\\\":null},\\\"Status\\\":\\\"Failed\\\"}\"\r\n    ]\r\n  }\r\n}"}]}}

Microsoft Azure で pfSense アプライアンスを仮想マシンとインターネットの間に配置して疎通を取る

$
0
0

皆様は pfSense というネットワークアプライアンス製品はご存じでしょうか?日本の社畜業界で生きていた身からすると F5 さんの BIG-IPが有名かなという気もしますが、アメリカに限らず各国で幅広く利用されています。今回はこちらを Azure に配置する簡単な手順を紹介します。
www.pfsense.org
実は仮想マシンを作成後、裏側に配置した別の仮想マシンからのネットワークトラフィックが通らずどこが悪いのか、しばくら悶々としながら調査しておりました。自分の備忘も兼ねて何をしたかを簡単にまとめておきたいと思います。

日本の方が試す場合の注意点

注意点として、pfSense は Azure Marketplace 上に公開されていますが、実は日本向けには公開されていません。日本リージョン向けに作成したアカウントで pfSense の Azure Marketplace イメージを利用した VMを作成しようとすると以下のエラーがでます。

また、上記を解消する為にアカウントの国情報を変えることができず、私は今回の検証をするために新規にアメリカ側でのアカウントを作成しました。公式ドキュメントとしては以下を参照ください。
learn.microsoft.com

今回作る構成のアーキテクチャ

今回は以下の様に pfSense VMには Public IP 経由で接続し、SSHは Azure Bastion 経由で pfSense VMと裏にある Kali Linuxに接続します。Route Table を利用して Kali Linux側のネットワークを pfSense VM経由する様にしている点に留意してください。

具体的に作成するリソース一覧は以下です。

  • Virtual Network x 1
  • Azure Bastion
  • Kali Linux x 1
  • pfSense network appliance x 1
  • Route Table x 1

本番環境を考える場合、冗長化等は必須になります。今回はあくまで検証用であるという点には留意してください。

実際に作る手順

まずは仮想ネットワークを作ります。今回は West US3 リージョンを選びました。

次の画面で "Enable Azure Bastion"にチェックを入れて Azure Bastion を作成するようにしておきましょう。

次に以下の画像を参考にして front-subnet、back-subnet、AzureBastionSubnet の三つのサブネットが含まれる仮想ネットワークを作成します。サブネットの大きさは自由に設定しても構いませんが、AzureBastionSubnet については公式ドキュメントの About Azure Bastion configuration settings | Microsoft Learnを参照してください。

上記の作成は Azure Bastion インスタンスの作成に時間がかかるので、数分程度待つ必要があります。

次にバックエンド相当になる Kali Linuxを作成します。ここではお安めに抑えたいので B シリーズインスタンスを選びます。B-series burstable - Azure Virtual Machines | Microsoft Learn

ここで SSHキーを作成し、後で Azure Bastion で接続する際に利用します。

以下の様に backend-subnet に配置します。

VM作成時に以下の様に private key をダウンロードする画面が表示されるので、ダウンロードしてください。こちらは後に利用します。

同様のステップで pfSense の仮想マシンも作成します。pfSense Plus Firewall/VPN/Router TAC Lite という仮想マシンイメージがあるので、こちらを選びます。front-subnet をデプロイ先のサブネットとして選び、SSHキーは Kali Linuxを作成した際のものをそのまま利用しましょう。

pfSense の VM作成後、同仮想マシンのネットワークインターフェースを開き、IP configuration から Enable IP forwarding にチェックを入れてください。こちらに合わせて IP アドレスも 10.0.0.4 に固定するように変更をしておきましょう。
Hybrid connection with two-tier application | Microsoft Learn

最後に Route Table を作成し、Kali Linuxが配置された back-subnet にアタッチします。まずは新規に Route Table を以下の様に作成します。

次に back-subnet 側の Next hop を全て pfSense 側にトラフィックを流すように以下の様にルートを設定します。

最後に back-subnet 側に本 RouteTable は関連付けて Azure リソースの作成は完了です。

pfSense ファイヤウォールの設定

まずは管理画面にアクセスします。pfSense の Public IP をブラウザに開くと以下の画面が表示されるので、デフォルトのユーザ名・パスワードである admin/pfsense を利用して管理画面にログインします。

ログインすると以下の画面が表示されるので Change the password in the User Manager リンクからパスワードを変更しておきましょう。

次に Firewall / NAT / Outbound をメニューから開き、新規に outbound を許可する以下のルールを作成します。

更に以下の Hybrid Outbound NAT rule generation.(Automatic Outbound NAT + rules below) をチェックします。これにより Kali Linuxから WAN インターフェース経由での outbound 接続自体がルーティングされます。最後に Apply Changes を押下するのを忘れない様にしてください。

pfSense デフォルトの設定では Kali Linux側から HTTP/HTTPSの outbound 接続を許可していません。Firewall / Rules / WAN メニューを開き、以下のルールを追加します。こちらは back-subnet(10.2.0.0./24)からの HTTPSによる outbound を許可する設定です。こちら加えて HTTP のルールも加えた後、Apply Changes を押下してルールを適用します。

最終的に Firewall / Rules / WAN の画面は以下の様になります。

Kali Linuxから HTTPSで outbound 接続をしてみる

さっそく Azure Bastion 経由で Kali Linuxに接続してみましょう。この際に保存済の SSHキーを利用します。

ログイン後 wgetコマンドを使って任意の URL にアクセスし、レスポンスが帰ってくれば成功です。

Kali Linuxから HTTPSで outbound 接続を許可しない場合

次に以下の様に HTTPSの outbound 接続を不許可にしてみましょう。

その後に Kali Linux側で wgetを実行すると以下の様にレスポンスが返ってきません。

pfSense 側の Diagnostics / States / States を確認すると FIN_WAIT_2: FIN_WAIT_2 でステップが止まっているのを確認できます。

Azure KeyVault に格納した SSH Key を使って、Azure Bastion 経由で Linux VM にアクセスする

$
0
0

掲題の内容、SSH Key をローカルのファイルとして管理することなく Key Vault で一元管理したいというのは良くあるニーズだと思いますが、意外とまとまった記事が無かったので備忘録も兼ねて書いてみました。試してみてアレコレ詰まったところはありますが、実態としては簡単な内容です。
以下に簡単なアーキテクチャを記載しましたが、VM作成時にポータル上で作成した SSH Key を Key Vault 側に登録し、同 SSH Key を利用して Azure Bastion 経由で VMにアクセスします。

作業手順としては手順になります。仮想ネットワーク、Key Vault、Azure Bastion はリソース作成済なことを前提とします。

  • SSH Key をポータル上で作成する( 今回は VM作成時の物を利用
  • Key Vault に Secrets として SSH Key の private key を登録する
  • Azure Bastion 経由で VMにアクセスする

SSH Key をポータル上で作成する( 今回は VM作成時の物を利用

今回は VM作成時に SSH Key を新規に作成します。以下の様にポータル上で SSH Key を作成可能です。パラメータを入力して VM作成を行った際、SSH Key の private key 側をダウンロードできるので手元に保存しておきましょう。

既存の SSH Key を利用する場合は Reference のリンク一つ目に参考情報がありますが、仮想マシンのポータル側のメニューから Reset password を選び、Add SSH public key を利用して当該仮想マシンに利用する SSH Key の情報を別途設定する必要がある点に注意してください。

Key Vault に Secrets として SSH Key の private key を登録する

次に Key Vault の Secrets に作成した SSH Key の private key を登録します( Keys や Certificates 側は Azure Bastion だと選択できません)。ここで一つ罠があり、private key は複数行のファイルになりますが Key Vault のポータル操作は複数行の文字列登録に対応してないのでコマンドで実行する必要があります。以下にコマンド実行例と実行結果の例を記載します。

az keyvault secret set --name "your-secret-name" --vault-name "your-key-name" --file "my-ssh-private-key.pem"

コマンドが無事に成功していれば以下の様に Key Vault 上に登録された private key が確認できるはずです。

Azure Bastion 経由で VMにアクセスする

ここまでくれば後は Azure Bastion 経由で LinuxVMにアクセスするのみです。接続したい LinuxVMを選択し、Authentication Type から SSH Private Key from Azure Key Vault をえらんだ後、先ほど登録した SSH Key の private key を利用することで接続できます。


University of Washington の Certificate コースを受けてみた感想(日本からでも OK

$
0
0

最近 University of Washington の講義を取っているのですが、何人かと雑談した際に話題にしたら「それ良いですね」「どうやって受けるのか教えてください」等々の話があったので、こちらもせっかくなのでまとめてみたいと思います。保証はしかねますが以前にあるコースを受講している際に日本やフィリピン(うろ覚え)からも参加している方がいたのも記憶しているので、今回のポストで興味があれば自分で受講することはできると思います。さすがに保証はしかねるので不安な場合は University of Washington 直に確認ください。

あんたが受講してるのは何なの?

私が受けているのは以下の CERTIFICATE IN CYBERSECURITY というコースです。日本人的に CERTIFICATE はあんまり聞きなれない方も多いと思いますが(私が最初分からんかったので他の人もそうだろうと想定)、いわゆる修士号や博士号の様な学位をとるコースをは違っており、単に「このコース受講してちゃんと課題も一通り水準以上にこなせましたよ」という様なものです。
www.pce.uw.edu
CERTIFICATE にした理由について、私の場合は「CERTIFICATE か修士か博士かなんて分かった人じゃないと踏み込んでこないし、日本国外の大学で専門知識の受講実績を持っていると箔がつく」と思ったのが主な動機です(中身が分かる方には普通に実態としての技術的な話をすればいいので)。CERTIFICATE は1年未満で修了可能なことに加え、身近にアメリカ側の修士に通っている日本人の知り合いがいますが課題量が半端じゃないので業務と並行は難しいと思って断念しました。
色々と専門分野をどれにしようかと迷いましたが、セキュリティはなんだかんだで需要は尽きないし、コンプライアンスと実務の折り合いをつけながら話せる人材は中々見かけないので、深堀分野としては悪くないかなと思った次第です。

日本からでも受けられるの?&お高いんじゃないの?

こちらのコース、アメリカ時間で夕方の午後6時~午後9時に実施されるので、日本時間としては金曜の午前11時~午後2時となります(時期によってサマータイムでやや変わるはずですが、1時間程度の差なので目安として)。平日の午前中なので調整が難しい方もいるかと思いますが、時間的には不可能な参加時間ではありません。

最新情報はリンク先を確認して欲しいですが、2023年12月23日現在での価格は8カ月コースで $4,197 となり、約60万円弱となります。国立大学の年間授業料が50万円を超えるくらいなので、安いと言える金額ではないですが物価差等々を考えるとそこまで高い値段でもないと思います。
www.chibakogyo-bank.co.jp

また、CERTIFICATE のコースは以下を見てもらえればと思いますが様々なコースがあります。こちらからコースを選択して apply すれば受講申請は可能です。
www.pce.uw.edu

技術分野でいうなら以下のコースが $3,786 とやや安めです。
www.pce.uw.edu

英語は大丈夫なの?

私自身は普段ソリューションアーキテクトとしてアメリカ側で業務しており英語でのコミュニケーション頻度が高いので、講義をちゃんと理解する&グループディスカッション等でもそこまでは困っていません。ただ、渡米当初 or 渡米直後だとかなりの負荷になったであろうことは想像に難くない程度には英語を使います。私の受講しているコースでは英語が SCORES FOR CREDIT PROGRAMS REQUIRING INTERMEDIATE ENGLISH SKILLS レベルの水準を要求されており、以下で確認すると TOEFL iBT で 92点 が目安となります。
www.pce.uw.edu

こういう換算がどこまで当てになるかは分かりませんが、TOEIC換算だと以下によると 820 点程度らしいです。目安としてはこの程度となりますが、特に審査があるわけではないので「俺は前のめりに英語を学んでやるぜ」という人間にはお勧めです。
www.conversation.jp

実際に受けてみてどう?

セキュリティ関連について、私自身が過去に実プロジェクトで閉域網やら WAF やら PCI-DSS やら FICS やらと雑多なプロジェクトに多々放り込まれてきたので、純技術的に新しいトピックが多いかと言われると決して多くはないです。とは言っても Security Development Lifecycle や Thread Modeling 等は SI 現場だとあまり話題にならないので、その点では勉強になっています。
グループワークに限らず講師の方が話題にする内容は当然ながら「アメリカでの常識」となるので「公共案件と言ったら軍隊(日本だったら官公庁を指すことが多い)」や「なんでアメリカには GDPRみたいな統一ルールがないのか(州ごとの権限が強すぎて統一ルールが作りにくい)」等の日本に住んでるとあんまり話題にならないことが話題になるのが参考になったりします。
また、当然ながら自分の専門分野での英会話になるので、英会話の実践という意味でも良いなと思っています(自分の興味分野なら続きますし)。以下とかを試したのは今回の講義で出た課題の一部だったりします。
normalian.hatenablog.com


色々と書きましたが、英会話がある程度できる方が興味のある技術領域等々(分野は色々とあります)で受けてみるのは悪くないと思います。

コマンドで Azure Application Gateway を止めたり開始したりして課金を抑える

$
0
0

皆さんは Azure Application Gatewayが起動や停止の制御ができるのはご存じでしょうか?使っていない仮想マシンを止めるのは良く行う節約術だと思いますが、Azure Application Gatewayは管理ポータルだと開始・停止の制御ができないのでご存じではない方もいるのではないかと思っています。
実際に止めた場合は Properties の画面で状態を確認でき、以下の様に Operational State が Stopped になっています。この場合に Azure Application Gatewayは課金されませんが当然リクエストは通しません(課金されない旨は参考リンクの GitHub上でその旨記載があります)。

実際にどのようなコマンドを利用すればよいかというと、以下のコマンドで Azure Application Gatewayの開始・停止が可能です。

az network application-gateway start --name YOUR-APPGW-NAME --resource-group YOUR-RESOURCE-GROUP-NAME
az network application-gateway stop --name YOUR-APPGW-NAME --resource-group YOUR-RESOURCE-GROUP-NAME

起動・停止のコマンド実行完了には体感では2~3分程度かかったので、ご参考までに。

Azure Network Watcher の Troubleshoot connections を使って Azure Application Gateway -> Azure Firewall -> Azure VM の接続性を確認する

$
0
0

今回は掲題の Troubleshoot connections を触ってみたいと思います。概要は以下のサイトに詳細はありますが、VM間等の通信を試してどこに問題があるか等を確認することができます。太古の昔は pingや traceroute に始まり、色んな方法でトラブルシュートしたものですが、まずはの疎通でこちらを試すのは悪くないと思っています。
learn.microsoft.com
アレコレ触った結果で、ちょっと癖もあるなと思ったので今回は Troubleshoot connections の動作を見てみたいと思います。

疎通を取る環境のアーキテクチャ

以下に今回のアーキテクチャ図を記載しています。同一リージョンに二つの VNET を作成していますが、VNET Peering や VPN等で接続しているわけではないので、互いに独立した VNET になっています。

Public IP アドレスを Azure Application Gatewayに付与し、Azure Application Gateway -> Azure Firewall -> Azure VMのリクエストフローを想定しています。Troubleshoot connections は接続元の VM/VMSS/Azure Bastion/Azure Application Gatewayのどれかを指定する必要があるので、今回はそもそもの接続元として Client 相当の VMを別 VNET に作成したという想定です。

クライアント VMアーキテクチャ図 ClientCent01)から宛先 VMアーキテクチャ図 CentVM01 or 02)を直接指定する場合

掲題通り、Troubleshoot connections に始端・終端の VMを直接指定するとどの様な結果になるかを確認したいと思います。図に答えが書いてありますが、これは上手くいきません。

実際にどのようなエラーが出るか確認しましょう。具体的にはポータルで Network Watcher を開いて以下の様に指定してください。

結果としては以下の様になり、接続に失敗しています。

更に詳しく見ると始端である ClientCent01 側の Outbound 規則に弾かれていることが分かります。実は NSGにはデフォルトで「VNET内 or インターネット側の outbound は許可する」という設定があるのですが、今回は接続されていない VNET となるのでどちらのルールにも適用されずにパケットが破棄されていることが分かります。

これをコマンドで実行すると以下の様になります。終端側の VMに対して、Public IP ベースで直接アクセスしようとして失敗していることが分かると思います。

$ az network watcher test-connectivity --resource-group YOUR-RESOURCE-GROUP-NAME --source-resource ClientCent01 --dest-resource CentVM01 --dest-port 80
az : WARNING: This command is in preview and under development. Reference and support levels: https://aka.ms/CLI_refstatus
At line:1 char:1
+ az network watcher test-connectivity --resource-group RG-Network-Test ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (WARNING: This c...s/CLI_refstatus:String) [], RemoteException
    + FullyQualifiedErrorId : NativeCommandError
 
{
  "connectionStatus": "Unreachable",
  "hops": [
    {
      "address": "10.1.0.4",
      "id": "b4db2e0c-f996-473f-a703-3c014d0987bb",
      "issues": [],
      "links": [
        {
          "context": {},
          "issues": [],
          "linkType": "Internet",
          "nextHopId": "67baece5-b655-46ba-b149-2655eb251aaa",
          "resourceId": ""
        }
      ],
      "nextHopIds": [
        "67baece5-b655-46ba-b149-2655eb251aaa"
      ],
      "previousHopIds": [],
      "previousLinks": [],
      "resourceId": "/subscriptions/YOUR-SUBSCRIPTION-ID/resourceGroups/YOUR-RESOURCE-GROUP-NAME/providers/Microsoft.Compute/virtualMachines/ClientCent01",
      "type": "Source"
    },
    {
      "address": "20.163.24.1",
      "id": "67baece5-b655-46ba-b149-2655eb251aaa",
      "issues": [
        {
          "context": [],
          "origin": "Outbound",
          "severity": "Error",
          "type": "VMNotAllocated"
        }
      ],
      "links": [],
      "nextHopIds": [],
      "previousHopIds": [
        "b4db2e0c-f996-473f-a703-3c014d0987bb"
      ],
      "previousLinks": [
        {
          "context": {},
          "issues": [],
          "linkType": "Internet",
          "nextHopId": "b4db2e0c-f996-473f-a703-3c014d0987bb",
          "resourceId": ""
        }
      ],
      "resourceId": "/subscriptions/YOUR-SUBSCRIPTION-ID/resourceGroups/YOUR-RESOURCE-GROUP-NAME/providers/Microsoft.Compute/virtualMachines/CentVM01",
      "type": "VirtualMachine"
    }
  ],
  "probesFailed": 30,
  "probesSent": 30
}

クライアント VMアーキテクチャ図 ClientCent01)から宛先 Azure Application Gatewayにアクセスする(※リソース名指定

直接終端 VMを指定するのは無理だと分かったので、次は始端 VMから Azure Application Gatewayを指定してみましょう。以下のコマンドを実施します(ポータルで行っても構いません)。

$ az network watcher test-connectivity --resource-group YOUR-RESOURCE-GROUP-NAME --source-resource ClientCent01 --dest-address AppGw-Network-Test01-TestUS3 --dest-port 80
az : WARNING: This command is in preview and under development. Reference and support levels: https://aka.ms/CLI_refstatus
At line:1 char:1
+ az network watcher test-connectivity --resource-group RG-Network-Test ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (WARNING: This c...s/CLI_refstatus:String) [], RemoteException
    + FullyQualifiedErrorId : NativeCommandError
 
{
  "connectionStatus": "Unreachable",
  "hops": [
    {
      "address": "20.169.48.200",
      "id": "a94a2aa0-07a7-4c32-abdc-3cd139d064a6",
      "issues": [
        {
          "context": [],
          "origin": "Local",
          "severity": "Error",
          "type": "DNSResolution"
        }
      ],
      "links": [
        {
          "context": {},
          "issues": [],
          "nextHopId": "d996b6e0-dee4-49a8-ba70-f0b7a1c55e03",
          "resourceId": ""
        }
      ],
      "nextHopIds": [
        "d996b6e0-dee4-49a8-ba70-f0b7a1c55e03"
      ],
      "previousHopIds": [],
      "previousLinks": [],
      "resourceId": "/subscriptions/YOUR-SUBSCRIPTION-ID/resourceGroups/YOUR-RESOURCE-GROUP-NAME/providers/Microsoft.Compute/virtualMachines/ClientCent01",
      "type": "Source"
    },
    {
      "address": "AppGw-Network-Test01-TestUS3",
      "id": "d996b6e0-dee4-49a8-ba70-f0b7a1c55e03",
      "issues": [],
      "links": [],
      "nextHopIds": [],
      "previousHopIds": [
        "a94a2aa0-07a7-4c32-abdc-3cd139d064a6"
      ],
      "previousLinks": [
        {
          "context": {},
          "issues": [],
          "nextHopId": "a94a2aa0-07a7-4c32-abdc-3cd139d064a6",
          "resourceId": ""
        }
      ],
      "type": "Destination"
    }
  ],
  "probesFailed": 0,
  "probesSent": 0
}

上記は失敗していますが 20.169.48.200 は ClientCent01 の Public IP であり、始端 VMから Azure Application Gatewayでアクセスしようとした結果で、名前解決で失敗(DNSResolution)していることが分かります。リソースタイプ側でコマンド実行すればよいのか、念のためコマンド自体を確認してみると、以下の様に現時点でコマンドラインでは VMしかサポートされていないことが分かりました。

$ az network watcher test-connectivity -h

...

Arguments
    --source-resource [Required] : Name or ID of the resource from which to originate traffic.
                                   Currently only Virtual Machines are supported.
    --no-wait                    : Do not wait for the long-running operation to finish.  Allowed
                                   values: 0, 1, f, false, n, no, t, true, y, yes.
    --protocol                   : Protocol to test on.  Allowed values: Http, Https, Icmp, Tcp.
    --resource-group -g          : Name of the resource group the target resource is in.
    --source-port                : Port number from which to originate traffic.

Destination Arguments
    --dest-address               : IP address or URI at which to receive traffic.
    --dest-port                  : Port number on which to receive traffic.
    --dest-resource              : Name or ID of the resource to receive traffic. Currently only
                                   Virtual Machines are supported.

クライアント VMアーキテクチャ図 ClientCent01)から宛先 Azure Application Gatewayにアクセスする(※ IP アドレス指定

Azure Application Gateway側できちんと名前解決できるように設定してあげれば問題ないと思いますが、検証環境ではそんな設定をしたくないということが多いでしょう。その場合は直接 Azure Application Gatewayのグローバル IP を指定すればアクセス可能です。本節では以下の部分の疎通を取ります。

ポータルで以下の様に Destination type を Specify manually を選択し、Azure Application Gatewayのグローバル IP を入力してください。

正常に設定されていれば以下の様にアクセスが可能なはずです。

詳細を確認すると 10.0.0.4 や 10.0.0.6 といった IP がありますが、こちらは終端側の VMのプライベート IP でなく、Azure Application Gateway側のサブネットの範囲なので、Azure Application Gatewayインスタンス(という表現が適切か分かりませんが)までしか届かないことが分かります。

これをコマンドで実行すると以下の様になります。

$ az network watcher test-connectivity --resource-group YOUR-RESOURCE-GROUP-NAME --source-resource ClientCent01 --dest-address 20.150.136.xxx --dest-port 80
az : WARNING: This command is in preview and under development. Reference and support levels: https://aka.ms/CLI_refstatus
At line:1 char:1
+ az network watcher test-connectivity --resource-group RG-Network-Test ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (WARNING: This c...s/CLI_refstatus:String) [], RemoteException
    + FullyQualifiedErrorId : NativeCommandError
 
{
  "avgLatencyInMs": 1,
  "connectionStatus": "Reachable",
  "hops": [
    {
      "address": "10.1.0.4",
      "id": "ea04f6fb-fb7e-4aad-9cc8-6c19106dd861",
      "issues": [],
      "links": [
        {
          "context": {},
          "issues": [],
          "linkType": "Internet",
          "nextHopId": "72256fb6-35ce-4442-80ba-0453f63f3b0f",
          "resourceId": ""
        }
      ],
      "nextHopIds": [
        "72256fb6-35ce-4442-80ba-0453f63f3b0f"
      ],
      "previousHopIds": [],
      "previousLinks": [],
      "resourceId": "/subscriptions/YOUR-SUBSCRIPTION-ID/resourceGroups/YOUR-RESOURCE-GROUP-NAME/providers/Microsoft.Compute/virtualMachines/ClientCent01",
      "type": "Source"
    },
    {
      "address": "20.150.136.xxx",
      "id": "72256fb6-35ce-4442-80ba-0453f63f3b0f",
      "issues": [],
      "links": [
        {
          "context": {},
          "issues": [],
          "linkType": "VirtualNetwork",
          "nextHopId": "f3308930-64a5-4657-a70c-ba5d7aa719c0",
          "resourceId": ""
        },
        {
          "context": {},
          "issues": [],
          "linkType": "VirtualNetwork",
          "nextHopId": "8f8d04fa-5c91-4b0d-9e5c-b1d96fb912a8",
          "resourceId": ""
        }
      ],
      "nextHopIds": [
        "f3308930-64a5-4657-a70c-ba5d7aa719c0",
        "8f8d04fa-5c91-4b0d-9e5c-b1d96fb912a8"
      ],
      "previousHopIds": [
        "ea04f6fb-fb7e-4aad-9cc8-6c19106dd861"
      ],
      "previousLinks": [
        {
          "context": {},
          "issues": [],
          "linkType": "Internet",
          "nextHopId": "ea04f6fb-fb7e-4aad-9cc8-6c19106dd861",
          "resourceId": ""
        }
      ],
      "type": "PublicLoadBalancer"
    },
    {
      "address": "10.0.0.4",
      "id": "f3308930-64a5-4657-a70c-ba5d7aa719c0",
      "issues": [],
      "links": [],
      "nextHopIds": [],
      "previousHopIds": [
        "72256fb6-35ce-4442-80ba-0453f63f3b0f"
      ],
      "previousLinks": [
        {
          "context": {},
          "issues": [],
          "linkType": "VirtualNetwork",
          "nextHopId": "72256fb6-35ce-4442-80ba-0453f63f3b0f",
          "resourceId": ""
        }
      ],
      "type": "VirtualNetwork"
    },
    {
      "address": "10.0.0.6",
      "id": "8f8d04fa-5c91-4b0d-9e5c-b1d96fb912a8",
      "issues": [],
      "links": [],
      "nextHopIds": [],
      "previousHopIds": [
        "72256fb6-35ce-4442-80ba-0453f63f3b0f"
      ],
      "previousLinks": [
        {
          "context": {},
          "issues": [],
          "linkType": "VirtualNetwork",
          "nextHopId": "72256fb6-35ce-4442-80ba-0453f63f3b0f",
          "resourceId": ""
        }
      ],
      "type": "VirtualNetwork"
    }
  ],
  "maxLatencyInMs": 4,
  "minLatencyInMs": 1,
  "probesFailed": 0,
  "probesSent": 66
}

Azure Application Gatewayから終端 VMアーキテクチャ図 CentVM01)にアクセスする(※リソース名指定

先ほどまでの検証だと始端 VM -> Azure Application Gatewayまでの疎通しか取れていないので、Azure Application Gateway -> Azure Firewall -> Azure VM部分の疎通を通したいと思います。アーキテクチャ図でいうと以下の部分になります。

こちらの疎通を通すには以下の様にポータルで入力してください。Source type で Application Gatewayを選択し、当該リソースを選択してください。

設定に問題が無ければ以下の様に疎通が通るはずです。

詳細を確認すると Azure Firewall(10.0.200.5)のアドレスを経由しており、ネットワークが適切に経由されていることが分かります。

こちらをコマンドで実行してみたかったのですが、コマンドの場合は --source-resource の指定が必須であり、VM以外は受け付けていません。コマンド実行は REST APIを直接叩く等の処理が必要になると思います。

続:Azure Key Vault に格納した SSH Key を使って、Azure Bastion 経由で Linux VM にアクセスする

$
0
0

掲題通り、Azure Key Vault と Azure Bastion を併用して LinuxVMに接続する話の続編を紹介したいと思います。以下の記事の続編となるので、こちらを事前に通読頂くことが前提となる点にご注意ください。
normalian.hatenablog.com

以下の様に 第1回 Azure Travelers 勉強会 札幌の旅 - connpassで久しぶりにコミュニティで登壇させてもらったので、そちらのフォローアップも兼ねています。

これって何がメリットなの?

こちらに関しては以下だと思っています。

  • Key Vault に SSH private key が格納できるので、ローカルでの管理が不要
  • RBAC で Key Vault へのアクセスを制御できる

一つ目に関しては、SSH private key を Azure Portal上で作成&ダウンロード後、ローカルで管理していると紛失したり、どの VM向けか分からなくなったりするので、分かりやすいと思います。加えて、開発要員が会社を辞めたり離脱した場合でもファイルを持ち出しされるリスクを低減できるというメリットがあります。
二つ目に関しては、VMアクセスの制御を Key Vault を利用してアクセス管理できるので、Key Vault のインスタンスを複数活用すれば特定の VMにのみアクセスさせる制御等も可能な認識です。

どの RBAC を利用したらいいの?

こちらについては以下の記事に関連 RBAC があるので参照して頂きたいと思います。
learn.microsoft.com

まず試したのは subscription の Owner 権限のみを持つユーザです。結果は以下の様になり、Key Vault の secret に対する List 権限が足りないとエラーが発生しました。subscription の Owner 権限のみではアクセスできない点に注意してください。

ならばと次は Key Vault Reader の RBAC を同ユーザに追加したところ、今度は以下の様に secret に対する Get アクセスができないというエラーが発生しました。

ならばと以下の Key Vault Secrets Officer の RBAC を付与したところ、無事に Key Vault を利用して Azure Bastion 経由での LinuxVMへのアクセスが無事に行えました。

Custom RBAC を活用する方法もあると思いますが、皆様の環境にあった RBAC を利用ください。

既存の VMに適用する場合

元となった記事では VM作成時に SSH Key を作成しています。これが何を意味するかというと、LinuxVM側に SSH key が登録されていることを意味しており、だからこそ Key Vault 側に格納されている SSH key でアクセスできるようになっています。
一方で、既存で作成済の LinuxVMには利用したい SSH key が登録されていません。これを解決するためには以下の様に当該 VMの左メニューより Reset password を選び、利用したい Key Vault に格納された SSH Key を選択して登録します。以下のスクリーンショットを参考にしてください。

Network Watcher の IP flow verify と Next hop を試してみる

$
0
0

Network Watcher は豊富な機能が提供されており、ひと月ほど前に投稿した Troubleshoot connections もその一つです。今回も以下の記事で利用したアーキテクチャで検証するので、事前に一読頂ければ幸いです。
normalian.hatenablog.com

掲題通り今回は以下の IP flow verify と Next hop を試してみます(本当は一つだけ試そうと思ったら、思ったよりもシンプルだったので、二つまとめようかなと)。
learn.microsoft.com
learn.microsoft.com

IP flow verify を試す

IP flow verify は Azure Network Watcher の一機能ですが、Azure VMからのネットワークトラフィックが許可されているか否かを確認することができる機能です。まずは実際に試してみましょう。疎通を取るために利用するアーキテクチャに関しては以下を踏襲するので、IP アドレス等の指定で不明瞭なところがあれば参照頂ければ幸いです。
normalian.hatenablog.com

詳細は上記の記事を参照して頂きたいですが、Application Gateway - 10.0.0.20 -> Azure Firewall - 10.0.200.4 -> Azure VM - 10.0.1.4 の構成となっています。まずは Azure VMからの Outbound を試してみましょう。以下の様に元となる VMを選択し、特定のグローバル IP に対して HTTP リクエストを送れるかのチェックをします。

結果は以下の様に「Network Security Group のルール上は OK」と返してきます。

なぜ上記の様な言い方をしているかというと、今回の環境では Azure Firewallで Outbound の接続を許可しておらず、実際に当該 VM上で試したグローバル IP に対して traceroute を実行すると以下の様に実際には接続できないからです。これからも分かるとおり、あくまで「Azure 上での Network Security Group のルールのチェック用」という位置づけだろうということが分かります。

もうちょっと詳細な情報が欲しいなと思うので、試しに以下を参考に Azure Cliでも試してみます。実行例と合わせて以下になりますが、ポータル以上の情報は取れないことが分かります。
learn.microsoft.com

$ az network watcher test-ip-flow --direction 'outbound' --protocol 'TCP' --local '10.0.1.4:60000' --remote '172.56.105.37:80' --vm 'CentVM01' --nic 'centvm01261_z1' --resource-group 'RG-Network-Test01-WestUS3'
{
  "access": "Allow",
  "ruleName": "defaultSecurityRules/AllowVnetOutBound"
}

PS C:\Users\daisami> 

動作原理さえ分かればあまり難しいものではないと思うので、本記事で述べた Tips を元に活用頂ければと思います。

Next hop を試す

次は Next hop の紹介です。こちらは Azure VMを起点としたネットワークアクセスを行う際、次の network hop がどこになるかを教えてくれる機能です。かなり単純な機能なので、試しに CentVM01 からのインターネットアクセスを試してみましょう。
前述の通り、こちらは Azure Firewallでアクセスがブロックされるため、UDR が正しく動いていれば CentVM01 の Next hop は Azure Firewallになるはずです。試した結果は以下となります。御覧の通り、Next hop type は VirutalAppliance かつ IP アドレスは Azure Firewallのプライベート IP が表示されています。

もっと詳細な情報が取れるかなと思い、同じく Azure Cliを試しましたが、こちらもポータルと取れる情報は大差ないようです。
learn.microsoft.com

PS C:\Users\daisami> az network watcher show-next-hop --dest-ip 172.56.105.37 --resource-group RG-Network-Test01-WestUS3 --source-ip 10.0.1.4 --vm CentVM01 --nic centvm01261_z1 
{
  "nextHopIpAddress": "10.0.200.4",
  "nextHopType": "VirtualAppliance",
  "routeTableId": "/subscriptions/YOUR-SUBSCRIPTION-ID/resourceGroups/RG-Network-Test01-WestUS3/providers/Microsoft.Network/routeTables/RT02-Network-
Test01-WestUS3"
}

参照記事にもありますが、特定の NICリソースに対するルーティング一覧を表示する場合は以下の様に az network nic show-effective-route-table を利用する方が詳細な情報が取得可能です。

$ az network nic show-effective-route-table --resource-group RG-Network-Test01-WestUS3 --name centvm01261_z1 
{
  "value": [
    {
      "addressPrefix": [
        "10.0.0.0/16"
      ],
      "disableBgpRoutePropagation": false,
      "nextHopIpAddress": [],
      "nextHopType": "VnetLocal",
      "source": "Default",
      "state": "Active"
    },
    {
      "addressPrefix": [
        "0.0.0.0/0"
      ],
      "disableBgpRoutePropagation": false,
      "nextHopIpAddress": [],
      "nextHopType": "Internet",
      "source": "Default",
      "state": "Invalid"
    },
    {
      "addressPrefix": [
        "0.0.0.0/0"
      ],
      "disableBgpRoutePropagation": false,
      "name": "VMs-to-Any",
      "nextHopIpAddress": [
        "10.0.200.4"
      ],
      "nextHopType": "VirtualAppliance",
      "source": "User",
      "state": "Active"
    },
    {
      "addressPrefix": [
        "10.0.0.0/24"
      ],
      "disableBgpRoutePropagation": false,
      "name": "VMs-to-AppGW",
      "nextHopIpAddress": [
        "10.0.200.4"
      ],
      "nextHopType": "VirtualAppliance",
      "source": "User",
      "state": "Active"
    }
  ]
}

Semantic Kernel 触りつつ ChatGPT の口調を変える

$
0
0

今となっては猫も杓子も ChatGPT なのでやや手あかのついた方法かと思いますが、ふと興味本位で調べて試した結果を放流致します。ご存じじゃない方も居るかと思いますがスマホ向けに👇のゲームがあり、私は iPadでかれこれ長い間このゲームをやってます。
www.azurlane.jp

こちらのゲームに出てくるキャラクターの口調で ChatGPT が会話してくれるのかなと思い、以下のキャラクターは結構口調が特徴的なので試してみました。
dic.pixiv.net

ソースコードとしては以下になりますが、実際のキャラはもっと間延びした口調で話すので、試した結果でかなり勘違いした口調になっています。

using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Connectors.OpenAI;
using System.Text;

Console.OutputEncoding = Encoding.GetEncoding("utf-8");

string charname =@"アンカレッジ";
string question =@"効率よく好感度を上げる";

Console.WriteLine("========================================== Start application");
var builder = Kernel.CreateBuilder();
builder.AddAzureOpenAIChatCompletion(
         "your-deployment-model",                                    // Azure OpenAI Deployment Name"https://your-endpoint-name.openai.azure.com/",    // Azure OpenAI Endpoint"your-openai-key");                   // Azure OpenAI Keyvar kernel = builder.Build();

var prompt =@"貴方はアズールレーンの {{$charname}} というキャラです。特に語尾を気を付けて。アズールレーンの攻略を手助けしてください。{{$question}}にはどうしたらいいですか?";

var summarize = kernel.CreateFunctionFromPrompt(prompt, executionSettings:new OpenAIPromptExecutionSettings { MaxTokens =1000 });
Console.WriteLine(await kernel.InvokeAsync(summarize, new() { ["charname"] = charname, ["question"] = question }));
Console.WriteLine("========================================== End of Application ");
========================================== Start application
アホ?アンカレッジが教えるまでもないやろう。まず、「攻略手帳」で好感度上げに必要な情報を確認しろや。
それから、秘書艦としてキャラと触れ合える「散策」機能を活用するんやな。時間をかけて何回か触れ合うこと
で好感度が上がるで。それと、キャラの「愛情度」を上げるためには、キャラ固有の「特殊出撃」を頻繁にこな
すか、任務をクリアすることも効果的やで。ただし、好感度を上げるために一心不乱にやるのはアホらしい。楽
しんでやれや。
========================================== End of Application


こちらをどうするか調べたところ、既に既存の knowledge の様で以下の記事を発見しました。
hatarakupuro.com

今回は以下の記事に記載されているアンカレッジのセリフをスクレイピングして何とかしたいと思います。こちらに関しては okazuki さんが良い記事を公開してくださっているので、こちらを参考にします。
azurlane.wikiru.jp
qiita.com

こちらを参考に wikiから scarping をしつつ返答するソースコードは以下になります。

using AngleSharp.Html.Parser;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Connectors.OpenAI;
using System.Text;

Console.OutputEncoding = Encoding.GetEncoding("utf-8");

string charname =@"アンカレッジ";
string question =@"効率よく好感度を上げる";

Console.WriteLine("========================================== Start applicaion");
var client =new HttpClient();
var res =await client.GetStringAsync("https://azurlane.wikiru.jp/index.php?"+ charname);
var parser =new HtmlParser();
var doc =await parser.ParseDocumentAsync(res);
var nodes = doc.QuerySelectorAll("#rgn_content3 > div > table > tbody > tr > td");
var quotes =string.Join(Environment.NewLine, nodes.Select(x => x.TextContent.Trim()));
Console.WriteLine(quotes);
Console.WriteLine("========================================== End of scraping");

var builder = Kernel.CreateBuilder();
builder.AddAzureOpenAIChatCompletion(
         "your-deployment-model",                                    // Azure OpenAI Deployment Name"https://your-endpoint-name.openai.azure.com/",    // Azure OpenAI Endpoint"your-openai-key");                   // Azure OpenAI Keyvar kernel = builder.Build();

var prompt =@"貴方はアズールレーンの {{$charname}} というキャラです。##で囲まれた口調を真似してしゃべってください。特に語尾を気を付けて。#{{$quotes}}#アズールレーンの攻略を手助けしてください。{{$question}}にはどうしたらいいですか?";

var summarize = kernel.CreateFunctionFromPrompt(prompt, executionSettings:new OpenAIPromptExecutionSettings { MaxTokens =1000 });
Console.WriteLine(await kernel.InvokeAsync(summarize, new() { ["quotes"] = quotes, ["charname"] = charname, ["question"] = question }));
Console.WriteLine("========================================== End of Application ");

こちらの実行結果は以下のようになります。実際のゲームをやっている方や、wiki側でのキャラのメッセージを確認頂ければ違和感が残るものの、大幅に表現が改善されたのが分かると思います。

ローカル端末、Entra ID テナント跨ぎ、組織アカウント・マイクロソフトアカウントが入り混じったら RBAC 制御で帰ってくるのが大変だった話

$
0
0

今回のポストは「適切に運用された Entra ID テナント上で、適切に運用された組織アカウントで開発をしている方」や Entra ID テナントの闇(テナント複数あったりマイクロソフトアカウントが入り混じったり)に触れたことのない人には縁のない話です。この文章だけで痛みが分かる方には有益な情報を追記したつもりなので、引き続き通読してください。「タイトルみたいな複雑な運用するのが間違っている」という方へのアドバイスはこちらになります。

今回私がハマった元ネタは以下の記事に記載のある「Cosmos DB へのデータアクセスを RBAC で制御しようとした」です。本来はハマりどころは大してないのですが、掲題の件が絡むと厄介なネタが増えてきます。
learn.microsoft.com
今回は「以下のすべてに該当しない人」は読む必要はありませんが、どこかでは引っかかる人が多いと思います。最初の項目だけなら大して問題にならないのですが、二・三・四つ目が混じってくると Entra ID の複雑さが猛威をふるってくるので、この辺りはノウハウをまとめた方が良いなと思ったのでこちらで紹介します。

  1. 自端末でのローカル開発をする
  2. Entra ID が複数あり guest invite を利用している
  3. 利用しているアカウントが組織アカウントでなくマイクロソフトアカウント
  4. むしろ組織アカウントとマイクロソフトアカウントが入り混じって自分の環境で利用している

今回はそれぞれで節を区切って「こういう風に試したらこう上手くいった(または失敗した)」をそれぞれ紹介します。

EntraID テナントが一つ、同 EntraID テナントにサブスクリプションが存在し、同 EntraID テナントの組織アカウントを使う場合(上手くいくケース)

念のため、公式ドキュメントでも紹介されているこちらの復習をしましょう。こちらは図に表すと以下になります。

CosmosDB の場合、リソース制御向けの built-in role はあってもデータアクセス向けの built-in role は無いので、まずはこちらをカスタムロールとして作成します。今回はデータの読み込み・書き込み・削除も想定して以下の JSONとしました。

{"RoleName": "CosmosDBDataAccessRole",
    "Type": "CustomRole",
    "AssignableScopes": ["/"],
    "Permissions": [{"DataActions": ["Microsoft.DocumentDB/databaseAccounts/readMetadata",
            "Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers/items/*",
            "Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers/*"
        ]}]}

次にこちらのカスタムロールを自分の CosmosDB 向けに Azure 上に作成します。ここで出力されるカスタムロールのリソースIDである "XXXXXXX-XXX-XXX-XXXX-XXXXXXXXX"は後で使います(実際はUUIDのランダムな値が出力されます)。

$rgName = "your-resource-group-name"
$cosmosdbName = "your-cosmosdb-name"
az cosmosdb sql role definition create -a $cosmosdbName -g $rgName -b role-definition-rw.json  
{
  "assignableScopes": [
    "/subscriptions/your-subscription-id/resourceGroups/your-resource-group-name/providers/Microsoft.DocumentDB/databaseAccounts/your-cosmosdb-name"
  ],
  "id": "/subscriptions/your-subscription-id/resourceGroups/your-resource-group-name/providers/Microsoft.DocumentDB/databaseAccounts/your-cosmosdb-name/sqlRoleDefinitions/XXXXXXX-XXX-XXX-XXXX-XXXXXXXXX",
  "name": "XXXXXXX-XXX-XXX-XXXX-XXXXXXXXX",
  "permissions": [
    {
      "dataActions": [
        "Microsoft.DocumentDB/databaseAccounts/readMetadata",
        "Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers/items/*",
        "Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers/*"
      ],
      "notDataActions": []
    }
  ],
  "resourceGroup": "your-resource-group-name",
  "roleName": "CosmosDBDataAccessRole",
  "type": "Microsoft.DocumentDB/databaseAccounts/sqlRoleDefinitions",
  "typePropertiesType": "CustomRole"
}

次に自身の組織アカウントの ID を得ます。以下の az ad user show コマンドを実行します。

az ad user show --id "myuser@normalian.xxx"
{
  "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#users/$entity",
  "businessPhones": [],
  "displayName": "aduser-normalian",
  "givenName": "aduser",
  "id": "YYYYYYY-YYY-YYY-YYYY-YYYYYYYYY",
  "jobTitle": "Principal Administrator",
  "mail": "myuser@normalian.xxx",
  "mobilePhone": null,
  "officeLocation": null,
  "preferredLanguage": null,
  "surname": "normalian",
  "userPrincipalName": "myuser@normalian.xxx"
}

最後に作成したカスタムロールを当該ユーザに割り当てて終了です。

$ az cosmosdb sql role assignment create -a $cosmosdbName -g $rgName -p "YYYYYYY-YYY-YYY-YYYY-YYYYYYYYY" -d "XXXXXXX-XXX-XXX-XXXX-XXXXXXXXX" -s "/"
{
  "id": "/subscriptions/your-subscription-id/resourceGroups/your-resource-group-name/providers/Microsoft.DocumentDB/databaseAccounts/your-cosmosdb-name/sqlRoleAssignments/787a36f9-
a7f3-40c8-a860-99d4c6ae5fd9",
  "name": "787a36f9-a7f3-40c8-a860-99d4c6ae5fd9",
  "principalId": "b0bde25a-d588-410c-a16d-30fc001661c4",
  "resourceGroup": "your-resource-group-name",
  "roleDefinitionId": "/subscriptions/your-subscription-id/resourceGroups/your-resource-group-name/providers/Microsoft.DocumentDB/databaseAccounts/your-cosmosdb-name/sqlRoleDefinit
ions/26eeca83-1fd8-4f40-8a5e-b66dac7d3e08",
  "scope": "/subscriptions/your-subscription-id/resourceGroups/your-resource-group-name/providers/Microsoft.DocumentDB/databaseAccounts/your-cosmosdb-name",
  "type": "Microsoft.DocumentDB/databaseAccounts/sqlRoleAssignments"
}

最後に C#側のソースコードで当該ユーザの認証情報を利用します。少しだけ抜粋すると以下になりますが、詳細は
Use system-assigned managed identities to access Azure Cosmos DB data | Microsoft Learnの記事を参照すると良いでしょう。組織アカウントを活用した secret string を利用しないアクセスが可能になります。

using Azure.Identity;
using Microsoft.Azure.Cosmos;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddControllersWithViews();
builder.Services.AddSingleton<CosmosClient>(serviceProvider =>
{
    return new CosmosClient(
        accountEndpoint: builder.Configuration["AZURE_COSMOS_DB_NOSQL_ENDPOINT"]!,
        tokenCredential: new DefaultAzureCredential()
    );
});

var app = builder.Build();

こちらに関しては原理さえ理解していれば特にハマりどころはないでしょう。

EntraID テナントが二つ以上存在し、サブスクリプションと組織アカウントの EntraID テナントが異なる場合(一応上手くいくケース)

ここからはややこしい例を挙げていきたいと思います。大きな会社に限らずで良くある構成だと思いますが、本番環境の EntraID に組織アカウントが登録されているが、guest invite で開発用の EntraID に当該組織アカウントが招待され、開発用の EntraID 配下の Azure subscription を利用する場合です。図に表すと以下になります。

この場合に大事なポイントは以下の二つになります。

  1. どうやって guest invitation されたアカウントにカスタムロールを割り当てるのか?
  2. DefaultAzureCredential で認証される Entra ID テナント(本番環境相当)を 開発用の EntraID に指定するか

一つ目に関しては割とシンプルです。念のためですが、何も考えずに guest user 招待したユーザを入力すると以下の様になります。

$ az ad user show --id "myuser01@normalian.xxx"
az : ERROR: Resource 'myuser01@normalian.xxx' does not exist or one of its queried reference-property objects are not present.
At line:1 char:1
+ az ad user show --id "myuser01@normalian.xxx"
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (ERROR: Resource...re not present.:String) [], RemoteException
    + FullyQualifiedErrorId : NativeCommandError

この理由については Entra ID 自体にアカウント情報を見に行けば問題は解決します。以下の様に #EXT#@ を含む自 Entra ID テナントを付与されたものになっています。

こちらで Object ID を取得しても当然構わないのですが、念のためにコマンドで実行するとちゃんと ID が取得できることが分かります。

az ad user show --id "daichi_mycompany.com#EXT#@normalianxxxxx.onmicrosoft.com"
{
  "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#users/$entity",
  "businessPhones": [],
  "displayName": "daichi",
  "givenName": null,
  "id": "4bf33ec0-dc63-4468-cc7d-edd4c9820fee",
  "jobTitle": "CLOUD SOLUTION ARCHITECT",
  "mail": "daichi@mycompany.com",
  "mobilePhone": null,
  "officeLocation": null,
  "preferredLanguage": null,
  "surname": null,
  "userPrincipalName": "daichi_mycompany.com#EXT#@normalianxxxxx.onmicrosoft.com"
}

上記を利用して最初の様に az cosmosdb sql role assignment create を実行すれば割り当ては完了です。

次に二つ目の開発用の Entra ID を見に行く設定を追加する必要があります。Entra ID テナントを特に指定しない場合、組織アカウントは自分が所属している Entra ID テナントの情報を取得しようとするので、今回のような「組織アカウントは本番 Entra ID、Azure サブスクリプションは開発 Entra ID」の場合には以下のようなエラーが出力されます(例は ASP.NET Core の場合)。

こちらを回避するためには C#側のソースコードで以下の様に Tenant ID を指定することで対応が可能です。

using Azure.Identity;
using Microsoft.Azure.Cosmos;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddControllersWithViews();
builder.Services.AddSingleton<CosmosClient>(serviceProvider =>
{
//    var connectionString = builder.Configuration.GetConnectionString("CosmosDB");//    return new CosmosClient(connectionString);var option =new DefaultAzureCredentialOptions()
    {
        TenantId ="your-entraid-tenant-id",
    };

    returnnew CosmosClient(
        accountEndpoint: builder.Configuration["AZURE_COSMOS_DB_NOSQL_ENDPOINT"]!,
        tokenCredential:new DefaultAzureCredential(option)
    );
});

var app = builder.Build();

こちらで Entra ID テナントを指定すればアクセスが可能になるはずです。

EntraID テナントに guest 招待されたマイクロソフトアカウントを利用するケース(これも上手くいくケース)

このケースは実は guest invitation と考えは同じです。図に表すと以下になります。

以下の様に #EXT#@ を含む自 Entra ID テナントを付与されたユーザ名を指定して az ad user show コマンドを実行(または当該 Entra ID のユーザを見に行く)し、カスタムロールを割り当てる az cosmosdb sql role assignment create を実行しましょう。

az ad user show --id "warito_abnormal_hotmail.com#EXT#@normalianxxxxx.onmicrosoft.com"
{
  "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#users/$entity",
  "businessPhones": [],
  "displayName": "Daichi",
  "givenName": "Daichi",
  "id": "94bfd636-b2e9-4d44-b895-8d51277e7abe",
  "jobTitle": "Principal Normal",
  "mail": null,
  "mobilePhone": null,
  "officeLocation": null,
  "preferredLanguage": null,
  "surname": "Isamin",
  "userPrincipalName": "warito_abnormal_hotmail.com#EXT#@normalianxxxxx.onmicrosoft.com"
}

az cosmosdb sql role assignment create -a $cosmosdbName -g $rgName -p "your-user-object-resourceid" -d "your-customrole-resourceid" -s "/"
{
  "id": "/subscriptions/your-subscription-id/resourceGroups/your-resource-group-name/providers/Microsoft.DocumentDB/databaseAccounts/your-cosmosdb-name/sqlRoleAssignments/7adc585c-
74d6-4979-a3ed-3d968de2d27e",
  "name": "7adc585c-74d6-4979-a3ed-3d968de2d27e",
  "principalId": "b0bde25a-d588-410c-a16d-30fc001961c4",
  "resourceGroup": "your-resource-group-name",
  "roleDefinitionId": "/subscriptions/your-subscription-id/resourceGroups/your-resource-group-name/providers/Microsoft.DocumentDB/databaseAccounts/your-cosmosdb-name/sqlRoleDefinit
ions/7adc585c-74d6-4979-a3ed-3d968de2d27e",
  "scope": "/subscriptions/your-subscription-id/resourceGroups/your-resource-group-name/providers/Microsoft.DocumentDB/databaseAccounts/your-cosmosdb-name",
  "type": "Microsoft.DocumentDB/databaseAccounts/sqlRoleAssignments"
}

EntraID テナントに Service Principal を作って利用するケース(これは上手くいかないケース)

色んなパターンを並べたので、そろそろ「めんどくせぇ!だったら開発用の Entra ID テナントで Service Principal を作ってやんよ!」と思った人もいるでしょう。これは図に表すと以下になりますが、実はこれは上手くいきません。

ちなみに Service Principal の Client ID と Object ID は異なりますので注意が必要ですが、コマンドを実行すると以下の様になります。

$ az cosmosdb sql role assignment create -a $cosmosdbName -g $rgName -p "your-serviceprincipal-clientied" -d "your-customrole-resourceid" -s "/"
az : ERROR: (BadRequest) The provided principal ID ["your-serviceprincipal-clientied"] was not found in the AAD tenant(s) [b4301d50-52bf-43f0-bfaa-915234380b1a] which are associated 
with the customer's subscription.
At line:1 char:1
+ az cosmosdb sql role assignment create -a $cosmosdbName -g $rgName -p ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (ERROR: (BadRequ...s subscription.:String) [], RemoteException
    + FullyQualifiedErrorId : NativeCommandError
 
ActivityId: b1b3a860-c244-11ee-9296-085bd676b6c2, Microsoft.Azure.Documents.Common/2.14.0, Microsoft.Azure.Documents.Common/2.14.0, Microsoft.Azure.Documents.Common/2.14.0, 
Microsoft.Azure.Documents.Common/2.14.0, Microsoft.Azure.Documents.Common/2.14.0, Microsoft.Azure.Documents.Common/2.14.0
Code: BadRequest
Message: The provided principal ID ["your-serviceprincipal-clientied"] was not found in the AAD tenant(s) [b4301d50-52bf-43f0-bfaa-915234380b1a] which are associated with the 
customer's subscription.
ActivityId: b1b3a860-c244-11ee-9296-085bd676b6c2, Microsoft.Azure.Documents.Common/2.14.0, Microsoft.Azure.Documents.Common/2.14.0, Microsoft.Azure.Documents.Common/2.14.0, 
Microsoft.Azure.Documents.Common/2.14.0, Microsoft.Azure.Documents.Common/2.14.0, Microsoft.Azure.Documents.Common/2.14.0

$ az cosmosdb sql role assignment create -a $cosmosdbName -g $rgName -p "your-serviceprincipal-objectid" -d "your-customrole-resourceid" -s "/"
az : ERROR: (BadRequest) The provided principal ID ["your-serviceprincipal-objectid"] was found to be of an unsupported type : [Application]
At line:1 char:1
+ az cosmosdb sql role assignment create -a $cosmosdbName -g $rgName -p ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (ERROR: (BadRequ...: [Application]:String) [], RemoteException
    + FullyQualifiedErrorId : NativeCommandError
 
ActivityId: f24dccfd-c244-11ee-9712-085bd676b6c2, Microsoft.Azure.Documents.Common/2.14.0, Microsoft.Azure.Documents.Common/2.14.0, Microsoft.Azure.Documents.Common/2.14.0, 
Microsoft.Azure.Documents.Common/2.14.0, Microsoft.Azure.Documents.Common/2.14.0, Microsoft.Azure.Documents.Common/2.14.0
Code: BadRequest
Message: The provided principal ID ["your-serviceprincipal-objectid"] was found to be of an unsupported type : [Application]
ActivityId: f24dccfd-c244-11ee-9712-085bd676b6c2, Microsoft.Azure.Documents.Common/2.14.0, Microsoft.Azure.Documents.Common/2.14.0, Microsoft.Azure.Documents.Common/2.14.0, 
Microsoft.Azure.Documents.Common/2.14.0, Microsoft.Azure.Documents.Common/2.14.0, Microsoft.Azure.Documents.Common/2.14.0

上記で分かる通り、Service Principal の Client ID を指定すると「そもそもそんな ID ないぞ」と怒られます(ここは object id を指定していないから当たり前)。次に Object ID を指定すると「Application に割り振るのは unsupport なんじゃい!」と怒られます。ご注意を。

異なるEntraID テナント向けに複数のアカウントを利用している(上手くいくが設定が必要なケース)

こちらは複数プロジェクトを掛け持ちしている場合等に良くあるパターンではないでしょうか。今回では以下の例を想定しましょう。念のためですがマイクロソフトアカウントをアレコレ開発に用いるのは本来は非推奨なのでご注意を

  • プロジェクト①のアカウント:myuser@normalian.xxx - 会社の組織アカウント
  • プロジェクト②のアカウント:personalxxxx@outlook.com - 暫定的に割り当てられたマイクロソフトアカウント

こちらを解決するには DefaultAzureCredential を利用する認証情報の優先順位を確認する必要があります。詳細はこちらを参照下さい。
learn.microsoft.com

今回の場合は AzureCliCredential つまり Azure Cliの認証を使ってアカウントを切り替えたいと思います。まずは以下のコマンドを実行します。

az login

ブラウザが起動して利用するアカウントを選ぶ必要があるので、当該アカウントを選択します。次に C#側のソースコードで以下の例を参考に、他の優先順位の高い認証情報を利用しない設定をすることで対応可能です。

using Azure.Identity;
using Microsoft.Azure.Cosmos;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddControllersWithViews();
builder.Services.AddSingleton<CosmosClient>(serviceProvider =>
{
//    var connectionString = builder.Configuration.GetConnectionString("CosmosDB");//    return new CosmosClient(connectionString);var option =new DefaultAzureCredentialOptions()
    {
       ExcludeEnvironmentCredential =true,
       ExcludeWorkloadIdentityCredential =true,
       ExcludeManagedIdentityCredential =true,
       ExcludeSharedTokenCacheCredential =true,
       ExcludeVisualStudioCredential =true,
       ExcludeVisualStudioCodeCredential =true,
       TenantId ="your-entraid-tenant-id",
    };

    returnnew CosmosClient(
        accountEndpoint: builder.Configuration["AZURE_COSMOS_DB_NOSQL_ENDPOINT"]!,
        tokenCredential:new DefaultAzureCredential(option)
    );
});

Kestrel ワイナリーのワインが一本 $4 未満の激安で美味しいというお話

$
0
0

普段はあんまり書かないのですが、先日に Kestrel Vintners というワイナリーに行ってワインをガッツリ箱買してきたので「こういう場所もあるんだよ」という程度の情報共有です。まずは Kestrel Vintners の場所ですが、以下の様に Woodinville にあります。この辺はぱっと見かけるだけでも10個以上のワイナリーがある場所なので、シアトル在住でワインが好きならご存じの方も多いと思います。日本から出張でくる方は Bellevue/Redmond/Kirkland 辺りで接待ごはんを食べることが多い認識ですが、先日に日本から来た方を Woodinville ワイナリーに招待したら喜んでもらえました。

ワイナリーというとブドウ畑が広がっているのを想像するかたもいるかもしれませんが、この Kestrel Vintners ワイナリーに限らず、この辺は販売所なのでかなりこじんまりとした場所です。Kestrel Vintnersでは(たしか)$20 位で 5 種類のワインが試飲でき、以下の写真の様にグラスに注がれて出てきます。高いワインを頼んでも安いワインを頼んでも試飲の値段は変わらないので、つい高い方を試してみたくなる人情は発動します。

いつも通りに試飲しようと思ったら外に「ケース一つで $40」 の張り紙が。単純換算でワイナリーのワインが一本 $4 未満という衝撃(ケース一つで12本と想定)。

ワイナリーの中ではこんな感じでケースが積まれているので、中々に圧巻。

以下の様に4種類ほどは「ケース一つで$40」としている様です。

購入して飲みましたが飲み口はあっさりしており癖も少ないので、ワイナリーのワインを気軽に飲みたい方にはぴったりだと思います。記憶が確かなら Kastrel は去年も似たようなことをやっていたので「この時期に毎回ワインを買い込む方が、下手にトレダジョーズで買うよりお得なのでは。。。。」と思っています。

まだケース売りをしていると思うので、どなたかのご参考になれば幸いです。





Latest Images