tech.guitarrapc.cóm

Technical updates

Azure SDK for .NET の認証を DefaultAzureCredential にそろえる

Azure SDK for .NET はなぜか AzureCredentialsDefaultAzureCredential という2つの認証方法があります。

今回は AzureCredentials しか受け付けない Azure SDK のクライアントに、DefaultAzureCredential で得た認証を使いたいというメモです。

tl;dr;

  • DefaultAzureCredential でとった認証はTokenを経由してAzureCredentials で利用できる。
  • Token は 10分で切れるので長時間実行に使いまわすには 再度認証をとるなり工夫が必要。
  • 多少の手間があってもDefaultAzureCredential のほうが認証の取り方が楽なので使っていきたい。

AzureCredentials をなぜ使いたくないのか

AzureCredentials は古い Azure SDK (あるいは移行されていないSDK) でのみ利用されている (?) 気配です。AzureCredentials を使うには、個別サービスパッケージ Microsoft.Azure.Management.Xxxxx 以外にMicrosoft.Azure.Management.ResourceManager.Fluent を参照します。

例えばこんな感じで使えます。

gist.github.com

AzureCredentials は触ってみると使いにくい点がいくつかあります。

  • ローカルでの認証 (例: ServicePrincipal) や Managed Service Identity (System Assigned Identity / UserAssigned Identity) ごとにインスタンスの生成方法が異なる。
  • NewtonSoft.Json 10.0.3 に依存していて脆弱性が解消されていない。1
  • 生成方法が SdkContext.AzureCredentialsFactory.FromXxxnew AzureCredentials(new XxxInformation の2つがありAPIから推測する情報が多い。

Newtonsoft.Json の脆弱性 https://github.com/advisories/GHSA-5crp-9r3c-p9vr
medium.com

次に示す DefaultAzureCredential や他クラウドでの認証を考えると触りたくないと感じます。

DefaultAzureCredential でどう変わるのか

DefaultAzureCredential は新しい Azure SDK for .NET で利用が進んでいる気配で、新しいSDK やインターフェースでは DefaultAzureCredential が利用されています。(全部じゃない) DefaultAzureCredential を使うには、Azure Identity SDK を参照します。

例えばこんな感じで使えます。

gist.github.com

DefaultAzureCredential は、new DefaultAzureCredential() あるいは new DefaultAzureCredential(new DefaultAzureCredentialOptions())とインスタンスを生成するだけで動作環境から自動的に認証を取得します。例えば、環境変数、Azure環境上で Managed Service Identity (System Assigned Identity / UserAssigned Identity) 、Visual Studio、Visual Studio Code、az login など一通りカバーされており便利です。

docs.microsoft.com

AzureCredentials での開発体験を振り返ると、DefaultAzureCredential の「同じインスタンス生成で動作環境に応じた認証をいい感じに取れる」というのは、環境ごとの書き分けがいらないため書き心地が安定しており好ましく感じます。2

特に UserAssignedIdentity と System AssignedIdentity が 環境変数 AZURE_CLIENT_ID の有無で切り替わるのは、利用側で自由に調整できるので非常に都合がいいです。3

docs.microsoft.com

DefaultAzureCredential で得た認証を AzureCredentials で利用する

DefaultAzureCredentialAzureCredentials と型レベルでは互換性がないため代わりに利用することができません。 しかし、DefaultAzureCredential でとった認証のトークンは、Microsoft.Rest.TokenCredentials を経由することでAzureCredentials に差すことができます。

こうすることで、認証の取り方はDefaultAzureCredential に任せつつ、AzureCredentials を必要とするクライアントを利用することができます。 例えば次のように書くことができます。

gist.github.com

TokenRequestContext は 2022年現在は、https://management.azure.com/.default 固定で問題ないはずです。(Azureはちょいちょい変わるので何も信用できない)

まとめ

ちょっと Azure Database for なんとかのSDK を使って操作しようとしたら AzureCredentials で面食らったのでした。 DefaultAzureCredential は普通な使い心地でいいのですが、AzureCredentials は厳しいものがあるので今後も避けていきたいところです。


  1. Newtonsoft.Json を明示的にパッケージ参照して解消しましょう

  2. 認証の取得の書き心地という意味では、AWS SDK for .NET は AWS IAM との紐づけを含めてもっと洗練されていると感じます。なお AWS SSO の認証は全然ダメなのでよくなってほしい。

  3. 環境変数 AZURE_CLIENT_ID があれば User Assigned Identity が参照される。

Pulumi リソースのプロパティが変わってしまい create-replacement が発生する場合の対処方法

Pulumi のパッケージ更新をしたら、プロパティ名が変わっており craete-replacement が生じてしまう。

Pulumi SDK 側でプロパティ名が変更されると発生するのですが、普段出会うことはまずありません。 しかし稀に、プレビューパッケージを使ったり、プレビューから GA した直後のサービスで起こることがあります。

Azure の Container Apps で最近起こったので備忘録に挙げておきます。

Warning: この対処は、リソースの再作成がかかる 必須プロパティでのみ必要になります。オプショナルなプロパティでは、単純にプロパティ名変更に追随するだけでokです。

tl;dr;

リソースのプロパティ名変更に対応するには、2つの方法が考えられます。

  1. 該当 URN をステートから削除してリソースをインポートしなおす。
  2. スタックをエキスポートしてステートJSON を直接編集してスタックにインポートしなおす。

残念ながら、リソースの必須プロパティ名の変更で生じる reate-replacement に対処できる Custom Resource Options は存在しません。 類似ケースのIssue があるのですが、プロパティに Alias がつけられるようになれば Terraform の moved ブロックのように何とかなりそうですね。

github.com

問題の概要

Pulumi はリソース URN 自体の移動に対しては CustomResourceoption で Alias を指定することで移動が可能です。 リソースプロパティの無視は Ignore で可能です。

しかし、リソースのプロパティ名の変更が起こった時に、旧プロパティ名から新プロパティ名に変更を追随するためのマーカーを渡す方法がありません。 オプショナルなプロパティであればプロパティ名を変えればいいのですが、必須プロパティの場合 create-replacement が生じてしまいます。

対処方法

要はステートのプロパティ名が違っているので、一度 URN を削除してから同じ URN にインポートしなおすことでプロパティ名の変更に追随できます。 ステートを直接JSONで触るか/触らないかで、2つのワークアラウンドが考えられます。

  1. 該当 URN をステートから削除してリソースをインポートしなおす。
  2. スタックをエキスポートしてステートJSON を直接編集してスタックにインポートしなおす。

JSONを直接いじりたくないので、1について説明します。

該当 URN をステートから削除してリソースをインポートしなおす。

この方法は JSON を直接触るより手間がかかります。 代わりに、JSON で万が一にも変なところを触ってインポート時におかしくなるという状況は避けられます。

該当ステートの状態をブラウザで確認しておく (オプショナル)

このステップは必須ではありませんが、やることをお勧めします。

この後、Pulumi から URN を削除すると今の Pulumi での状態が見れなくなります。

できれば ブラウザで Pulumi Console の街頭 URN を開いて、プロパティの状態をタブで開いておきましょう。 タブで開いていおくことで、「構築時のコード」と「インポート時に必要なプロパティ」の暗黙の指定が明確に把握できます。 インポート時にコードとリソースの差分が出ても、このタブを見ればどんな違いがあるか推測するのに役立ちます。

該当コードの URN を見つける

Pulumi Console のリソース一覧 (グラフからでも一覧でもよし) を開き、該当の URN を見つけてください。 あるいは pulumi cli であれば pulumi stack --show-urns から探してもいいでしょう。

pulumi stack --show-urns | grep "リソース名"

URN: urn:pulumi:xxx:Stack名::Provider名:なんとかかんとか といったフォーマットの URN が見つかったらメモします。

ステートから URN を削除する

URN を ステートから削除することによって、クラウドプロバイダー上にリソースを残したまま Pulumi の対応を消すことができます。 pulumi cli をつかって、該当の URN 消します。

pulumi state delete <先ほど見つけたURN>

確認されるので y で削除を実行します。

クラウド環境からリソースをインポートする

Terraform同様に、Pulumi もクラウド環境の状態をコードに取り込むことができます。 Terraform は cli で terraform import クラウドリソースID で取り込みましたが、Pulumi はコード上の CustomResourceOptions.ImportId で指定します。

www.pulumi.com

例えば、Azure の Managed Environment であれば次のようになります。

Note: Pulumi.AzureNative を .NET で使っている場合、NuGet パッケージ 1.64.1 から 1.65.0 に更新すると発生します。

_ = new ManagedEnvironment("FooBar", new ManagedEnvironmentArgs
{
    EnvironmentName = "foo", // ここが AzureNative nuget パッケージの更新で Name から EnvironmentName に変わった
    AppLogsConfiguration = new AppLogsConfigurationArgs
    {
        Destination = "log-analytics",
        LogAnalyticsConfiguration = new LogAnalyticsConfigurationArgs
        {
            CustomerId = "ログアナリティクスID"
            // SharedKey = "シェアドキー" <- インポート時に SharedKey は入らないのでコメントアウト必須
        },
        ZoneRedundant = false, // 暗黙のプロパティ で false だが、インポート時は 明示的に指定しないと Diff が出てインポートが失敗する
        ResourceGroupName = "Foo-Group",
        Location = "japan-east",
        Tags = .... // あればちゃんと指定する
    }
}, new CustomResourceoptions
{
    Parent = this, // インポート時のリソースの Parent 指定になるので、コンポーネントと親子関係持たせるなら必須
    ImportId = "/subscriptions/xxxxxxx/resourceGorups/Foo-Group/providers/Microsoft.App/managedEnvironment/foo" // Azure Resource Id
});

インポート時の注意は、現在のリソースとの Diff が生じてはいけないということです。 このため、暗黙のプロパティで与えられる値も明示的に指定が必要です。(例では ZoneRedundant = false は暗黙で与えられるが明示的に指定が必要) インポート時のリソースは、 CustomResourceoptions.ImportId で指定しましょう。Azure の場合はリソースID です。

あとは差分がないか pulumi cli で確認します。

差分があるとインポートは 100% 失敗します、差分がないように気を付けてください。 ただし差分は Diff ではプロパティ名までしか確認できず値がわかりません。 「該当ステートの状態をブラウザで確認しておく」のステップで開いておいたタブでプロパティ値とコードを見比べるといいでしょう。

pulumi preview

差分がなければインポートを実行します。

pulumi up

インポートが成功すればokです。

コードから ImportId を削除する

コードから CustomResourceoptions.ImportId セクションを消して、 pulumi を実行してみて差分が出ず実行完了すれば終わりです。

まとめ

Pulumi の人からリプライがついていますが、Alias がリソースのプロパティに対しても使えれば解決するので近々できるようになったりするといいですね。

相談を受けて、そういえば記事にしていなかったので起こしました。

Windows 11 で Microsoft Teams がログオン時に勝手に起動するのを止めたい

Windows 11 をインストールしたときに戸惑ったのが Microsoft Teams がログオン時に勝手に起動することでした。

使ってないこともあって、無意識に無効にしたのですが先日困っている人がいたので記事にしておきます。

tl;dr;

Windows 11 では、スタートアップアプリ に Microsoft Teams が登録されています。 これを無効にすると、ログオン時に自動的に Microsoft Teams が起動してくるのを止めることができます。

Settings ->

スタートアップ アプリから Microsoft Teams を無効にする

Windows 11 のスタートアップアプリがどうなっているか確認する

Windows 11 の場合、2か所を確認するといいでしょう。

  1. タスクマネージャーの スタートアップアプリ
  2. スタートアップフォルダのショートカット

どちらもこれまでのWindows でも同じものがありますが、Windows 11 では少しアクセスしやすくなった気がします。

タスクマネージャーの スタートアップアプリ

タスクマネージャーのスタートアップ で確認できます。 ユーザーがログオンしたときに起動するアプリが登録されています。

これらのアプリをログオン時に起動するかどうかは、タスクマネージャーで有効/無効の切り替えできます。

Ctrl + Shift + Esc

タスクマネージャーのスタートアップアプリ

また、タスクマネージャーではなく 設定 (Settings) から行うこともできます。

Settings > Apps > Startup Apps に移動

スタートアップアプリ

なお、以前のWindows にあったシステムコンフィグのスタートアップ設定は、前述のタスクマネージャーに移動しています。

スタートアップメニュー や Ctrl +R で起動した実行ウィンドウで msconfig を入力

システムコンフィグのスタートアップ

スタートアップ

Windows の持つ専用フォルダ %appdata%\Microsoft\Windows\Start Menu\Programs\Startup を開くと確認できます。 ユーザーがログオンしたときに実行するアプリのショートカットが登録されています。

このパスにショートカットがおいてあれば起動するので、起動を無効にするにはショートカットを消すか移動します。

Ctrl +R で起動した実行ウィンドウで、 shell:startup を入力

shell:startup

スタートアップフォルダにはショートカットが登録されている

そのほか

Windos サービスやレジストリでの登録などもありますが、直接触る機会はかなり減ったように思います。 その背景には、アプリが自分自身でスタートアップを設定するか設定に持つことが増えたこともある気がしますね。

Ubuntu 22.04 と WSL

Ubuntu 20.04 on WSL は非常に安定しており、また便利で好んで利用しています。 さて、Ubuntu 22.04 がリリースされ、WSL にも来ました。

そこで今回、今の Ubuntu 20.04 on WSL な環境を Ubuntu 22.04 にするにあたり対応したことをメモしておきます。

tl;dr;

  • Ubuntu 22.04 を WSL で動かすなら WSL2 一択になりそう。
  • Ubuntu 22.04 on WSL1 は BSoDが頻発し needrestart の制御が効かないためあきらめた。
  • WSL2 は VPN 周りで困るので今後向き合っていくことになりそう。

Ubuntu 22.04 での大きな変更点

needrestart パッケージの導入

needrestart パッケージの導入は、Ubuntu 20.04 まではなかった変更で直面することになります。

gihyo.jp

特に私は Ansible で WSL の Ubuntu環境を構成しているので、apt パッケージの実行のたびにエラーになるのは困ります。 あと、WSL なので ホストOS の再起動もそれなりにすることもあり、結構いらない感じがあります。

ということで、適当に /etc/needrestart/conf.d/99_ansible.conf などをこしらえて、警告表示を無効にしています。

Ubuntu でアップグレードした場合の警告表示を無効化する | らくがきちょう v3 https://sig9.org/archives/4580

# use stdin to select restart service
$nrconf{ui} = 'NeedRestart::UI::stdio';
# control restart service
$nrconf{kernelhints} = '0';
$nrconf{ucodehints} = 0;
$nrconf{restart} = 'a';

これを Ansible での apt 処理の前段で設定することでok、となります。

- name: "needrestart - check OS is using needrestart"
  become: yes
  stat:
    path: /usr/sbin/needrestart
  register: needrestart_exists

- name: "needrestart - place custom conf.d"
  become: yes
  copy:
    dest: /etc/needrestart/conf.d/99_ansible.conf
    content: |
      # use stdin to select restart service
      $nrconf{ui} = 'NeedRestart::UI::stdio';
      # control restart service
      $nrconf{kernelhints} = '0';
      $nrconf{ucodehints} = 0;
      $nrconf{restart} = 'a';
  when: needrestart_exists.stat.exists

再インストールにはホストWindowsの再起動が必要

これまでの WSL での Ubuntu 再インストールは、アンインストール後に ストアから再インストールが可能でした。 Ubuntu 22.04 では、アンインストール後に Windows を再起動するまでストアから再インストールできなくなっているようです。 試行錯誤がちょっとめんどくさくなりました。

アンインストール

アンインストール後のストアからの再インストールがダウンロードで止まる
アンインストール後のストアからのインストールが失敗する

Ubuntu 22.04 をWSL2 に構成する

大きな変更以外は調整不要です。 Ubuntu 20.04 までの Ansible での構成は 22.04 でも問題なく機能するでしょう。

例えば私は次のようなコマンドで構成しています。

mkdir -p ~/github/guitarrapc && cd ~/github/guitarrapc
git clone https://github.com/guitarrapc/local-provisioner
cd ~/github/guitarrapc/local-provisioner/envs/ubuntu_wsl2

# ansible の導入
. ./prerequisites.sh
ansible-playbook -i hosts site.yml -K

リポジトリはこちらです。

github.com

docker WSL2 にインストールする場合

Docker for Windows ではなく、Ubuntu 22.04 に docker をインストールする場合、iptable を古いのを指定しないと動かないのは変わっていません。

$ sudo update-alternatives --set iptables /usr/sbin/iptables-legacy
$ sudo update-alternatives --set ip6tables /usr/sbin/ip6tables-legacy
$ sudo update-alternatives --config iptables

There are 2 choices for the alternative iptables (providing /usr/sbin/iptables).

  Selection    Path                       Priority   Status
------------------------------------------------------------
  0            /usr/sbin/iptables-nft      20        auto mode
* 1            /usr/sbin/iptables-legacy   10        manual mode
  2            /usr/sbin/iptables-nft      20        manual mode

設定してから docker を開始するとうまく動きます。

$ sudo service docker start

Ubuntu 22.04 をWSL1 に構成する (断念)

手元では、再現する問題が2点あり解消が難しそうなので断念しました。

  1. needrestart の制御が needrestart.conf でも効かずAnsibleでの構成でaptパッケージごとにエラーが生じる
  2. Ubuntu 22.04 でパッケージ構成時にBSoD が頻発する

needrestart を purge してもダメなので何かやり方が間違えている気がするものの、BSoD は解消できないのでちょっと難しそうです。 BSoD は、今のところ dotnet SDK の導入時に起こっています。

# ref: https://dotnet.microsoft.com/download/linux-package-manager/ubuntu18-04/sdk-current
# ref: https://github.com/ocha/ansible-role-dotnet-core
- name: "dotnet - make sure HTTPS is supported by apt"
  become: yes
  apt:
    name: apt-transport-https
    state: present
    update_cache: yes

- name: "dotnet - import Microsoft apt key"
  become: yes
  shell: wget https://packages.microsoft.com/config/ubuntu/{{ ansible_distribution_version }}/packages-microsoft-prod.deb -O /tmp/packages-microsoft-prod.deb
  changed_when: false

- name: "dotnet - add repo for Ubuntu"
  become: yes
  shell: dpkg -i /tmp/packages-microsoft-prod.deb
  changed_when: false

- name: "dotnet - install dotnet sdk ({{ args.version }})"
  become: yes
  package:
    name: "dotnet-sdk-{{ args.version }}"
    state: present
    update_cache: true

おまけ: WSL2 と VPN の問題

よく知られている問題なので、これをやっていきましょう。

qiita.com

Workaround for WSL2 network broken on VPN · GitHub

Pulumi で今のスタックを異なるプロジェクトのスタックに移動させる

Pulumi はステートを スタック (Stack) に保持しています。 スタックはリネームや別の Organization への移動はサポートされていますが、自分のOrganization の別プロジェクトへの移動はサポートされていません。

しかしスタックを後から整理したいときには、このスタックを別のプロジェクトに移動させたいというのはやりたくなるでしょう。 公式にはまとまっていないので、いずれサポートされるまでのワークアラウンドを見てみましょう。

tl;dr;

  • Pulumi CLI で pulumi importpulumi export を使ってステートを取り込みなおせばステートを別プロジェクトのスタックに移動できる。
  • スタックの移動前にスタック名を移動先で使いたい名前にリネームしましょう。
  • 今あるプロジェクトにスタックを追加するのが忘れがち

主にこの流れですが、別プロジェクト名なのでステートの編集が必要です。

github.com

何をしたいのか

スタック移動したい例を考えてみましょう。 下の図は、FooFoo-GuardDuty の2プロジェクトがあり、それぞれ master というスタックがある状態です。

Foo
  Master
Foo-GuardDuty
  Master

プロジェクト名を見てわかる通り、 Foo-GuardDuty は AWS GuardDuty 専用の処理なので Foo とは別に作りたかったようですが、プロジェクトで分けてしまいました。このやり方だと、同じプロジェクトなのにその関係が疑問付きになりますし、Pulumi UI 上でも縦に伸びて見にくなります。

そこで下の図のように、Foo-Dev プロジェクトの中に master と guardduty というスタックを持つ構成に変えましょう。

Foo
  Master
  Guardduty

これなら関連のあるプロジェクト Foo でまとめつつ、その構成の違いはスタックで示すことができます。

変更の流れ

次の流れでやっていきます。

  1. 移動先のスタックを既存プロジェクトに作成
  2. 移動元のスタック名を移動先のスタック名にリネーム
  3. 移動元のスタックをエキスポート
  4. エキスポートした json を編集
  5. 移動先のスタックに編集したjsonを インポート
  6. 移動先のスタックでシークレットやコンフィグをセットしなおし
  7. pulumi refresh && pulumi preview && pulumi up

変更前のフォルダ構成

ちなみに変更前は次のようなフォルダ構成です。

$ tree
.
├── Foo-GuardDuty.Master
└── Foo.Master

変更後のフォルダ構成

変更後は次のようになります。

.
├── Foo.GuardDuty
└── Foo.Master

移動先のスタックを既存プロジェクトに作成

すでにあるプロジェクト Foo にスタック guardduty を追加するには、pulumi init をプロジェクトとスタック名で初期化します。 今回スタックを置くフォルダ名を、Foo.GuardDuty としましょう。

mkdir Foo.GuardDuty
cat <<EOF > Foo.GuardDuty/Pulumi.yaml
name: Foo
runtime: dotnet
description: AWS Foo account
EOF
cat <<EOF > Foo.GuardDuty/Pulumi.GuardDuty.yaml
config:
  aws:region: ap-northeast-1
EOF

これで次のようなフォルダとファイルができたはずです。

$ tree
.
└── Foo.GuardDuty
    ├── Pulumi.GuardDuty.yaml
    └── Pulumi.yaml

既存プロジェクトにスタックを作る準備ができたので、 pulumi cli で Foo プロジェクトにguarddutyスタックを作ります。

pulumi stack init guardduty

これで既存の Fooプロジェクトに 空のスタック guardduty が追加されます。

移動元のスタック名を移動先のスタック名にリネーム

続いて移動元のプロジェクトのスタック、Foo-GuardDuty/Master のスタック名を移動先のスタック名 GuardDuty に変えましょう。 pulumi cli スタック名がリネームできます。

cd Foo-GuardDuty.Master
pulumi stack rename GuardDuty
cd ..

スタックのエキスポート前にやっておくと、エキスポートしたスタックのjsonを編集する手間が減るのでオススメです。

移動元のスタックをエキスポート

pulumi cli で Foo-GuardDuty/GuardDuty のステートをエキスポートして、Foo.GuardDuty にコピーしておきましょう。

cd Foo-GuardDuty.Master
pulumi stack export --file guardduty.stack.checkpoint.json
cp guardduty.stack.checkpoint.json ../Foo.GuardDuty/.
cd ../Foo.Guard

エキスポートした json を編集

json には、プロジェクト名とスタック名が書かれており、これが一致しないとインポートできません。 スタックのプロジェクト名が変わるので、json を sed や VS Code などで開いて一括置換しましょう。

置換は、::プロジェクト名:: で行うと間違えた場所を置換する心配がありません。

  • 検索文字列: ::Foo-GuardDuty::
  • 置換文字列: ::Foo::

移動先のスタックに編集したjsonを インポート

移動先のスタックで置換した json をインポートします。

cd Foo.GuardDuty
pulumi stack import --file guardduty.stack.checkpoint.json

正常に取り込めたはずです。

移動先のスタックでシークレットやコンフィグをセットしなおし

スタックをインポートで取り込んでも、pulumi config で設定していたコンフィグやシークレットは入りません。 適当にいい感じに設定しましょう。

pulumi config set foo bar

pulumi refresh && pulumi preview && pulumi up

Pulumiコード を元の環境から持ってきたら、新しいスタック環境でリソースとスタックの同期をとっていきましょう。 まずは実環境の状態をステートに取り込んでおきます。特に差分は出ないはず。

pulumi refresh

最後に実行して終わりです。

pulumi preview
pulumi up

プロジェクト名が変わったので、最上位urnであるプロジェクト名だけ入れ替えが出ますが、個別のリソースステートに差分は出ず影響ありません。

終わったらエキスポートした json を消したり、元のプロジェクトを消しましょう。 CI を組んであるなら、pulumi のプロジェクトパスを直したりすれば完璧ですね。

まとめ

pulumi はステートの扱いがかなり緩いので、比較的 json をいじる力業で何とかなります。 とはいえ、ミスをすると怖いので、やるときは実験プロジェクトなどで要領を把握してからやるといいでしょう。