Azure Functions で Azure Cloud Service を制御できなかった話

体調不良だったり年の瀬で忙しかったりで投稿が遅れてしましました。
申し訳ありません…。

# 発端

タイトルの通り、Azure Cloud Service を Azure Functions で制御できたら便利だな！と思って始めましたが、うまくいかなかった話です。
勘のいい人はこの時点で状況を悟ったかもしれませんが、他の方が私と同じ失敗をしないしないことを願ってこの記事を書いてます。

# 前提
Azure Cloud Service は管理証明書を使用することにより、サービス管理API経由でAzureポータルで操作するようなサービスのデプロイや開始・停止・削除といった、その名の通り管理系の操作を外部から行えます。
詳細は以下参照。
[Azure Cloud Services の証明書の概要](https://docs.microsoft.com/ja-jp/azure/cloud-services/cloud-services-certs-create)



# やってみた

コードの一部抜粋ですが、こんな感じになります。

```csharp:
var subscriptionCertData = Convert.FromBase64String(subscriptionCertDataFromBase64);
var credentials = new CertificateCloudCredentials(subscriptionId, new X509Certificate2(subscriptionCertData));
var managementClient = new ComputeManagementClient(credentials);
var parameters = new DeploymentCreateParameters
{
    Label = deployLabel,           // デプロイラベル
    Name = deployName,             // デプロイ名
    PackageUri = packageUri,       // デプロイパッケージのURL
    Configuration = configuration, // .cscfgのテキスト(BOMなしXML)
    StartDeployment = true,        // デプロイ直後にサービスを開始するか否か
};
var response = managementClient.Deployments.Create(serviceName, slot, parameters);
```

それで実行してみると、こんな例外が発生して動いてくれません。
>System.Security.Cryptography.CryptographicException: The system cannot find the file specified.
>
>   at System.Security.Cryptography.CryptographicException.ThrowCryptographicException(Int32 hr)
>   at System.Security.Cryptography.X509Certificates.X509Utils._LoadCertFromBlob(Byte[] rawData, IntPtr password, UInt32 dwFlags, Boolean persistKeySet, >SafeCertContextHandle& pCertCtx)
>   at System.Security.Cryptography.X509Certificates.X509Certificate.LoadCertificateFromBlob(Byte[] rawData, Object password, X509KeyStorageFlags keyStorageFlags)
>   at System.Security.Cryptography.X509Certificates.X509Certificate2..ctor(Byte[] rawData)

デプロイどころか証明書の処理でつまずいてる…！

# 悪あがき

この時点で無理そうなのは察しがついていましたが、なんとなく悔しいので試してみました。
そもそもWebJobだと上手くいっているはずなので、WebJobと同じようにしてみようと思い立ってやってみました。

以下を参照にAppServiceの設定に WEBSITE_LOAD_CERTIFICATES を追加して試してみました。
[Using Certificates in Azure Websites Applications](https://azure.microsoft.com/ja-jp/blog/using-certificates-in-azure-websites-applications/)

以下のボタンからAppServiceの設定に移動し…
![Function Appの設定画面](https://qiita-image-store.s3.amazonaws.com/0/150005/9e46917b-99ce-d1b2-a516-dc7fb648f270.png)

App Serviceのアプリケーション設定画面のアプリ設定に設定を追加。
![App Serviceのアプリケーション設定画面](https://qiita-image-store.s3.amazonaws.com/0/150005/41a5fc0d-f122-26af-c19a-d27fb396b954.png)

コードを以下のように書いてみて実行。

```csharp:
var certStore = new X509Store(StoreName.My, StoreLocation.CurrentUser);
certStore.Open(OpenFlags.ReadOnly);
X509Certificate2Collection certCollection = certStore.Certificates.Find(
                                            X509FindType.FindByThumbprint,
                                            "<your cert's thumbprint>",
                                            false);
if (certCollection.Count > 0)
{
    X509Certificate2 cert = certCollection[0];
}
```

**ダメでした。**
certCollectionの中身が0。取得できていません。

# ならば
この場合、証明書ストアから取得できないのはAppServiceの設定で無効になっているからです。
何故無効になっているかを考えれば設定を変更するだけ無駄なことは想像がつきますが、一応試してみました。
以下のようなURLでAzure Resource Manager経由でAppServiceの設定を参照します。（変な感じにマスクされているのは気にしないでください）
`https://resources.azure.com/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/AzureFunctions-JapanEast/providers/Microsoft.Web/sites/<Your function name>`

![Azure Resource Manager](https://qiita-image-store.s3.amazonaws.com/0/150005/b575d137-ef04-377e-37c2-7317018b7282.png)

Jsonで設定が記述されていますが、ここでまず一番上のモードを Read Only から Read/Write に変更。
Editモードにして "clientCertEnabled" という項目を false から trueに書き換えてPutします。

そして先ほどのFunctionを実行！

![Azure Functions Error](https://qiita-image-store.s3.amazonaws.com/0/150005/7c64bada-177a-6113-22ab-27190059ee59.png)

**Functions の動作そのものが不可能になった…。**

ちなみに上記の設定をtrueからfalseに戻せば上記のエラーは解消されFunctionは実行可能に戻ります。

# 結論
**無理でした。**
そもそもAzure Functions はAppServiceベースで共有リソースなので証明書ストアにアクセスさせない方針の模様です。
抜け道があるのかは私が調べた範囲内では見つけられませんでした。もし誰かできた人がいたら是非教えてください。

App ServiceのStandardプラン以上ならば占有リソースがあるはずなので、その環境下のWebJobなら同様のコードは動作するはずです。（Basicでもいける？）
ただ、私のケースではApp Serviceをほかに使用していなかったため紛れ込ませることもできなかったので、クラウドサービスのA0インスタンスにQueueトリガーで実行させることで対処しました。残念…。

# 終わりに
Azure Functionsはかなり便利なサービスですが、意外なところで制限があります。
制限を知ったうえで便利なFunctionライフを送りましょう。