AzureCloudShell(PowerShellCore)でAzureDNS管理のドメインのワイルドカード証明書を取得する(Let'sEncrypt)

# はじめに
## Azure Cloud Shell(Powershell),Azure PowerShell Core
IgniteでAzure Cloud ShellのPowerShellが[GAしました](https://azure.microsoft.com/en-us/updates/powershell-in-azure-cloud-shell-now-generally-available/)

7月あたりにはPowerShellもBashと同様Linuxコンテナになり、PowerShellもCore(6系)に変わっています。
＃Windowsコンテナはやっぱり無理があったんですかね

Azure PowerShellも[クロスプラットフォーム化](https://azure.microsoft.com/en-us/blog/azure-powershell-cross-platform-az-module-replacing-azurerm/)し始めたりしてますが、、
この流れあんまり好きじゃないです。

クロスプラットフォームはAzure CLI、
WindowsはAzure PowerShellってすみ分け出来てたと思うんです

## 先日の似たタイトルのエントリ
[AzureCloudShell(PowerShell)でAzureDNS管理のドメイン証明書をWEBサーバーなしで取得する(Let'sEncrypt)](https://qiita.com/HiroyukiSakoh/items/144cc1bc955ac3697b84)
↑では、[ACMESharp](https://github.com/ebekker/ACMESharp)を使用していましたが、ACMEv1かつCore非対応で、CloudShellでは動かなくなってしまいました。
[ACMESharp Core](https://github.com/PKISharp/ACMESharpCore)はまだこれからといった感じでしょうか。

# きょうのほんだい
Let'Encrypt公式の[ACME Client Implementations](https://letsencrypt.org/docs/client-options/)を見ていたところ、
ACMEv2(ワイルドカードとか！)に対応した、PowerShellで使える[Posh-ACME](https://github.com/rmbolger/Posh-ACME)を見つけたので、
今回はこちらで先日のエントリを焼き直します。

# できあがったもの
```powershell:呼び出し
cd $home/clouddrive
.\PoshACME_CloudShell.ps1 -CertificateDomainNames @("*.test.example.com","test2.example.com") -CertificatePassword "Password" -AccountMailAddress letsencrypt@example.com
```

```powershell:PoshACME_CloudShell.ps1
param(
 [Parameter(Mandatory=$True)]
 [string[]]
 $CertificateDomainNames,
 [string]
 [Parameter(Mandatory=$True)]
 $CertificatePassword,
 [string]
 [Parameter(Mandatory=$True)]
 $AccountMailAddress
)
$ErrorActionPreference = "Stop"

#アクセストークン取得
$subscriptionId = (Get-AzContext).Subscription.Id
$response = Invoke-WebRequest -Uri http://localhost:50342/oauth2/token -Body @{resource="https://management.azure.com/"} -Header @{Metadata="true"}
$accessToken = ($response.Content | ConvertFrom-JSON).access_token

#テストはLE_STAGEでやること！！
#Set-PAServer LE_PROD
Set-PAServer LE_STAGE

#Let's Encrypt
$cert = New-PACertificate `
  -Domain $CertificateDomainNames `
  -Contact $AccountMailAddress `
  -AcceptTOS `
  -DnsPlugin Azure `
  -PluginArgs @{`
      AZSubscriptionId=$subscriptionId;`
      AZAccessToken=$accessToken;`
  } `
  -DNSSleep 5 `
  -PfxPass $CertificatePassword `
  -Verbose -force

#保存
$savePath = "{0}-{1}" -f $CertificateDomainNames[0].Replace('*','!'),(Get-Date).ToString("yyyyMMddHHmmss")
New-Item $savePath -ItemType Directory
Copy-Item $cert.CertFile (".\{0}\{1}" -f $savePath,"cert.cer")
Copy-Item $cert.PfxFile (".\{0}\{1}" -f $savePath,"cert.pfx")
Copy-Item $cert.KeyFile (".\{0}\{1}" -f $savePath,"cert.key")
Copy-Item $cert.ChainFile (".\{0}\{1}" -f $savePath,"chain.cer")
Copy-Item $cert.FullChainFile (".\{0}\{1}" -f $savePath,"fullchain.cer")
Copy-Item $cert.PfxFullChain (".\{0}\{1}" -f $savePath,"fullchain.pfx")
```

とってもシンプルにかけました。
ACMESharpでは、AzureDNSのレコード更新まわりを自前で処理していましたが、
Posh-ACMEはAzureDNSに対応しています。

DNSチャレンジを処理するプロバイダがプラグイン形式で作られていて、
現時点では24ものプロバイダに対応しています。[List of Supported DNS Providers](https://github.com/rmbolger/Posh-ACME/wiki/List-of-Supported-DNS-Providers)

## ハマったポイント
ローカル(Azure Powershell)ではGet-AzureRmContextのtokencacheを使ってたんですが、
CloudShell上だとtokencacheが空っぽでした。
前はCloudShell上でも取れた気がするんですが、PowerShellCoreになってから…？
[Azure Cloud Shell の MSI の使用](https://docs.microsoft.com/ja-jp/azure/cloud-shell/msi-authorization)の通り、呼び出してみたら取れましたが、、

# おわりに
AutomationのPowerShellランタイムがもしCoreになったら、
すべてのモジュールをCoreで動くように書き換えないとダメとかなりそうで、
3rdPartyモジュールを使ったスクリプト組んでる人はかわいそうだなとか思ったり。

アクセストークン取得と保存処理をちょこっと変えればAutomationでも簡単にできます
こんな感じで取ると良いかと

```powershell
$servicePrincipalConnection=Get-AutomationConnection -Name "AzureRunAsConnection"
Add-AzureRmAccount `
    -ServicePrincipal `
    -TenantId $servicePrincipalConnection.TenantId `
    -ApplicationId $servicePrincipalConnection.ApplicationId `
    -CertificateThumbprint $servicePrincipalConnection.CertificateThumbprint 
    
$subscriptionId = (Get-AzureRmContext).Subscription.Id
$accessToken = ((Get-AzureRmContext).tokencache.ReadItems() | Where-Object { $_.ClientId -eq $servicePrincipalConnection.ApplicationId } | Select-Object -First 1).AccessToken
```