06 Apr 2016, 17:00

Azureの監査ログアラートからWebhookの流れで楽をする

監査ログからアラートを上げられるようになります

Azureの監査ログからアラートを上げる機能のプレビューがはじまりました。これ、地味ですが便利な機能です。日々の運用に効きます。

どんな風に使えるか

ルールに合致した監査ログが生成された場合、メール通知とWebhookによる自動アクションができます。可能性無限大です。

たとえば、「特定のリソースグループにVMが生成された場合、そのVMに対し強制的にログ収集エージェントをインストールし、ログを集める」なんてことができます。

これは「生産性を上げるため、アプリ開発チームにVMの生成は委任したい。でもセキュリティなどの観点から、ログは集めておきたい」なんてインフラ担当/Opsの課題に効きます。開発チームに「VM生成時には必ず入れてね」とお願いするのも手ですが、やはり人間は忘れる生き物ですので、自動で適用できる仕組みがあるとうれしい。

これまでは監視用のVMを立てて、「新しいVMがあるかどうか定期的にチェックして、あったらエージェントを叩き込む」なんてことをしていたわけですが、もうそのVMは不要です。定期的なチェックも要りません。アラートからアクションを実現する仕組みを、Azureがマネージドサービスとして提供します。

実装例

例としてこんな仕組みを作ってみましょう。

  • 西日本リージョンのリソースグループ”dev”にVMが作成されたら、自動的にメール通知とWebhookを実行
  • WebhookでAzure AutomationのRunbook Jobを呼び出し、OMS(Operations Management Suite)エージェントを該当のVMにインストール、接続先OMSを設定する
  • OMSでログ分析

準備

以下の準備ができているか確認します。

  • Azure Automation向けADアプリ、サービスプリンシパル作成
  • サービスプリンシパルへのロール割り当て
  • Azure Automationのアカウント作成
  • Azure Automation Runbook実行時ログインに必要な証明書や資格情報などの資産登録
  • Azure Automation Runbookで使う変数資産登録 (Runbook内でGet-AutomationVariableで取得できます。暗号化もできますし、コードに含めるべきでない情報は、登録しましょう。後述のサンプルではログイン関連情報、OMS関連情報を登録しています)
  • OMSワークスペースの作成

もしAutomationまわりの作業がはじめてであれば、下記記事を参考にしてください。とてもわかりやすい。

勤務時間中だけ仮想マシンを動かす(スケジュールによる自動起動・停止)

Azure Automation側の仕掛け

先にAutomationのRunbookを作ります。アラート設定をする際、RunbookのWebhook URLが必要になるので。

ちなみにわたしは証明書を使ってログインしています。資格情報を使う場合はログインまわりのコードを読み替えてください。

param ( 
    [object]$WebhookData          
)

if ($WebhookData -ne $null) {  
    $WebhookName    =   $WebhookData.WebhookName
    $WebhookBody    =   $WebhookData.RequestBody  
    $WebhookBody = (ConvertFrom-Json -InputObject $WebhookBody)

    $AlertContext = [object]$WebhookBody.context

    $SPAppID = Get-AutomationVariable -Name 'SPAppID'
    $Tenant = Get-AutomationVariable -Name 'TenantID'
    $OMSWorkspaceId = Get-AutomationVariable -Name 'OMSWorkspaceId'
    $OMSWorkspaceKey = Get-AutomationVariable -Name 'OMSWorkspaceKey'
    $CertificationName = Get-AutomationVariable -Name 'CertificationName'
    $Certificate = Get-AutomationCertificate -Name $CertificationName
    $CertThumbprint = ($Certificate.Thumbprint).ToString()    

    $null = Login-AzureRmAccount -ServicePrincipal -TenantId $Tenant -CertificateThumbprint $CertThumbprint -ApplicationId $SPAppID   

    $resourceObj = Get-AzureRmResource -ResourceId $AlertContext.resourceId
    $VM = Get-AzureRmVM -Name $resourceObj.Name -ResourceGroupName $resourceObj.ResourceGroupName

    $Settings = @{"workspaceId" = "$OMSWorkspaceId"}
    $ProtectedSettings = @{"workspaceKey" = "$OMSWorkspaceKey"}

    if ($VM.StorageProfile.OsDisk.OsType -eq "Linux") {  
        Set-AzureRmVMExtension -ResourceGroupName $AlertContext.resourceGroupName -Location $VM.Location -VMName $VM.Name -Name "OmsAgentForLinux" -Publisher "Microsoft.EnterpriseCloud.Monitoring" -ExtensionType "OmsAgentForLinux" -TypeHandlerVersion "1.0" -Settings $Settings -ProtectedSettings $ProtectedSettings;
    }
    elseif ($VM.StorageProfile.OsDisk.OsType -eq "Windows")
    {
        Set-AzureRmVMExtension -ResourceGroupName $AlertContext.resourceGroupName -Location $VM.Location -VMName $VM.Name -Name "MicrosoftMonitoringAgent" -Publisher "Microsoft.EnterpriseCloud.Monitoring" -ExtensionType "MicrosoftMonitoringAgent" -TypeHandlerVersion "1.0" -Settings $Settings -ProtectedSettings $ProtectedSettings;
    }
    else
    {
        Write-Error "Unknown OS Type."
    }
}
else 
{
    Write-Error "This runbook is meant to only be started from a webhook." 
}

Runbookができたら、Webhookを作ります。詳しくはこちら

WebhookのURLを控えておいてください。

Azure 監査ログアラート側の仕掛け

Powershellでアラートルールを作ります。実行アカウントの権限に気をつけてください。

PS C:\work> $actionEmail = New-AzureRmAlertRuleEmail -CustomEmail yourname@example.com

PS C:\work> $actionWebhook = New-AzureRmAlertRuleWebhook -ServiceUri https://abcdefgh.azure-automation.net/webhooks?token=your_token

PS C:\work> Add-AzureRmLogAlertRule -Name createdVM -Location "Japan West" -ResourceGroup dev -OperationName Microsoft.Compute/virtualMachines/write -Status Succeeded  -SubStatus Created -TargetResourceGroup dev -Actions $actionEmail,$actionWebhook

以上。これで”dev”リソースグループにVMが作られた場合、自動でOMSエージェントがインストールされ、ログ収集がはじまります。

なお、メールも飛んできますので、うっとおしくなったらメール通知はアクションから外すか、ルールでさばいてくださいね。

25 Mar 2016, 22:50

Azure & Terraform Tips (ARM対応 2016春版)

俺の屍を越えていけ

今週リリースされたTerraform v0.6.14で、Azure Resource Manager ProviderのリソースにVMとテンプレートデプロイが追加されました。この週末お楽しみ、という人も多いかもしれません。

小生、v0.6.14以前から触っていたこともあり、土地勘があります。そこで現時点でのTipsをいくつかご紹介します。

この3つは触る前から意識しよう

  1. ARMテンプレートリソースは分離して使う
  2. リソース競合したら依存関係を定義する
  3. 公開鍵認証SSH指定でエラーが出ても驚かない

1. ARMテンプレートリソースは分離して使う

v0.6.14で、リソース“azurerm_template_deployment”が追加されました。なんとARMテンプレートを、Terraformの定義ファイル内にインラインで書けます。

でも、現時点の実装では、おすすめしません。

ARMテンプレートのデプロイ機能とTerraformで作ったリソースが不整合を起こす

避けるべきなのは”Complete(完全)“モードでのARMテンプレートデプロイです。なぜなら完全モードでは、ARM リソースマネージャーは次の動きをするからです。

リソース グループに存在するが、テンプレートに指定されていないリソースを削除します

つまり、ARMテンプレートで作ったリソース以外、Terraform担当部分を消しにいきます。恐怖! デプロイ vs デプロイ!!。リソースグループを分ければ回避できますが、リスク高めです。

タイムアウトしがち

それでもTerraformの外でARMテンプレートデプロイは継続します。成功すれば結果オーライですが…Terraform上はエラーが残ります。「ああそれ無視していいよ」ではあるのですが、割れ窓理論的によろしくないです。

せっかくのリソースグラフを活用できない

Terraformはグラフ構造で賢くリソース間の依存関係を管理し、整合性を維持しています。サクサク apply & destroyできるのもそれのおかげです。ARMテンプレートでデプロイしたリソースはそれに入れられないので、もったいないです。

読みづらい

Terraform DSLにJSONが混ざって読みにくいです。Terraform DSLを使わない手もありますが、それでいいのかという話です。

それでも”terraformコマンドに操作を統一したい”など、どうしても使いたい人は、ARMテンプレート実行部は管理も実行も分離した方がいいと思います。

2. リソース競合したら依存関係を定義する

Terraformはリソース間の依存関係を明示する必要がありません。ですが、行き届かないこともあります。その場合は“depends_on”で明示してあげましょう。

例えば、以前のエントリで紹介した下記の問題。

Error applying plan:

1 error(s) occurred:
azurerm_virtual_network.vnet1: autorest:DoErrorUnlessStatusCode 429 PUT https://management.azure.com/subscriptions/my_subscription_id/resourceGroups/mygroup/providers/Microsoft.Network/virtualnetworks/vnet1?api-version=2015-06-15 failed with 429


Cannot proceed with operation since resource /subscriptions/GUID/resourceGroups/xxxx/providers/Microsoft.Network/networkSecurityGroups/yyy allocated to resource /subscriptions/GUID/resourceGroups/***/providers/Microsoft.Network/virtualNetworks/yyy is not in Succeeded state. Resource is in Updating state and the last operation that updated/is updating the resource is PutSecurityRuleOperation. 

HTTPステータスコード429(Too many requests)が返ってきているのでわかりにくいですが、実態はセキュリティーグループリソースの取り合いです。

  • サブネットリソース作成側: サブネットを新規作成し、セキュリティーグループを紐付けたい
  • セキュリティーグループルール作成側: ルールをセキュリティーグループに登録したい(更新処理)

この2つが並行してセキュリティーグループを取り合うので、高確率でエラーになります。セキュリティーグループルールはリソースの新規作成でなく、セキュリティーグループの更新処理であるため「リソースを作成したら/存在したら次にすすむ」というTerraformのグラフでうまく表現できないようです。

そのような場合、明示的に依存関係を”depends_on”で定義します。

# Create a frontend subnet
# "depends_on" arg is a workaround to avoid conflict with updating NSG rules 
resource "azurerm_subnet" "frontend" {
    name = "frontend"
    resource_group_name = "${var.resource_group_name}"
    virtual_network_name = "${azurerm_virtual_network.vnet1.name}"
    address_prefix = "${var.vnet1_frontend_address_prefix}"
    network_security_group_id = "${azurerm_network_security_group.frontend.id}"
    depends_on = [
        "azurerm_network_security_rule.fe_web80",
        "azurerm_network_security_rule.fe_web443",
        "azurerm_network_security_rule.fe_ssh"
    ]
}

これでサブネット作成処理は、セキュリティーグループルール登録完了まで、作成処理開始を待ちます。美しくないですが、当面の回避策です。

3. 公開鍵認証SSH指定でエラーが出ても驚かない

TerraformはLinux VMの定義で、公開鍵認証SSHを指定できます。こんな感じで。

os_profile_linux_config {
    disable_password_authentication = true
    ssh_keys {
        path = "/home/${var.adminuser}/.ssh/authorized_keys"
        key_data = "${file("/Users/you/.ssh/yourkey.pem")}"
    }
}

が、エラーが返ってきます。

[DEBUG] Error setting Virtual Machine Storage OS Profile Linux Configuration: &errors.errorString{s:"Invalid address to set: []string{\"os_profile_linux_config\", \"12345678\", \"ssh_keys\"}"}

残念ながら、Terraformが使っているAzure SDK(Golang)のバグです。

妥当性チェックのエラーで、実際にはキーの登録はできているようです。私は何度か試行してすべて公開鍵SSHログインに成功しています。

Issueとして認識されていますので、修正を待ちましょう。

23 Mar 2016, 13:00

Azure & Terraform エラーコード429の対処法

Terraformer増加に備えて

2016/3/21にリリースされたTerraform v0.6.14で、Azure Resource Manager ProviderのリソースにVMとテンプレートデプロイが追加されました。待っていた人も多いのではないでしょうか。

追ってHashicorp認定パートナーのクリエーションラインさんから導入・サポートサービスがアナウンスされましたし、今後AzureをTerraformでコントロールしようという需要は増えそうです。

エラーコード429

さて、TerraformでAzureをいじっていると、下記のようなエラーに出くわすことがあります。

Error applying plan:

1 error(s) occurred:
azurerm_virtual_network.vnet1: autorest:DoErrorUnlessStatusCode 429 PUT https://management.azure.com/subscriptions/my_subscription_id/resourceGroups/mygroup/providers/Microsoft.Network/virtualnetworks/vnet1?api-version=2015-06-15 failed with 429

autorestがステータスコード429をキャッチしました。RFC上で429は“Too many requests”です。何かが多すぎたようです。

対処法

もう一度applyしてください

冪等性最高。冪等性なんていらない、という人もいますが、こういうときはありがたい。Terraformが作成に失敗したリソースのみ再作成します。

背景

エラーになった背景ですが、2つの可能性があります。

  1. APIリクエスト数上限に達した
  2. リソースの作成や更新に時間がかかっており、Azure側で処理を中断した

1. APIリクエスト数上限に達した

Azure Resource Manager APIには時間当たりのリクエスト数制限があります。読み取り 15,000/時、書き込み1,200/時です。

Azure サブスクリプションとサービスの制限、クォータ、制約

Terraformは扱うリソースごとにAPIをコールするので、数が多い環境で作って壊してをやると、この上限にひっかかる可能性があります。

長期的な対処として、Terraformにリトライ/Exponential Backoffロジックなどを実装してもらうのがいいのか、このままユーザ側でシンプルにリトライすべきか、悩ましいところです。

ひとまずプロダクトの方針は確認したいので、Issueに質問をあげておきました

2. リソースの作成や更新に時間がかかっており、Azure側で処理を中断した

Terraform側ではエラーコードで判断するしかありませんが、Azureの監査ログで詳細が確認できます。

わたしが経験したエラーの中に、こんなものがありました。

Cannot proceed with operation since resource /subscriptions/GUID/resourceGroups/xxxx/providers/Microsoft.Network/networkSecurityGroups/yyy allocated to resource /subscriptions/GUID/resourceGroups/***/providers/Microsoft.Network/virtualNetworks/yyy is not in Succeeded state. Resource is in Updating state and the last operation that updated/is updating the resource is PutSecurityRuleOperation. 

Too many requestsというよりは、リソースのアップデートが終わってないので先に進めない、という内容です。

Too many requestsをどう解釈するかにもよりますが、ちょっと混乱しますね。この問題はFeedbackとしてあがっています

でも安心してください。もう一度applyしてください

(2016/3/25 追記) 回避策を別エントリに書きました

17 Mar 2016, 23:00

PackerとAnsibleでAzureのGolden Imageを作る(ARM対応)

いつの間に

ナイスな感じにイメージを作ってくれるPackerですが、いつの間にかAzure ARM対応のBuilderが出ておりました。0.10からかな。早く言ってください。

ansible_localと組み合わせたサンプル

さっそく試してそつなく動くことを確認しました。サンプルをGithubにあげておきます

手の込んだ設定もできるように、Provisonerにansible_localを使うサンプルで。

前準備

  • リソースグループとストレージアカウントを作っておいてください。そこにイメージが格納されます。
  • 認証情報の類は外だしします。builder/variables.sample.jsonを参考にしてください。
  • Packerの構成ファイルはOSに合わせて書きます。サンプルのbuilder/ubuntu.jsonはubuntuの例です。
    • Azure ARM BuilderはまだWindowsに対応していません。開発中とのこと。
  • ansibleはapache2をインストール、サービスEnableするサンプルにしました。

サンプル

ubuntu.jsonはこんな感じです。

{
  "variables": {
    "client_id": "",
    "client_secret": "",
    "resource_group": "",
    "storage_account": "",
    "subscription_id": "",
    "tenant_id": ""
  },
  "builders": [{
    "type": "azure-arm",

    "client_id": "{{user `client_id`}}",
    "client_secret": "{{user `client_secret`}}",
    "resource_group_name": "{{user `resource_group`}}",
    "storage_account": "{{user `storage_account`}}",
    "subscription_id": "{{user `subscription_id`}}",
    "tenant_id": "{{user `tenant_id`}}",

    "capture_container_name": "images",
    "capture_name_prefix": "packer",

    "image_publisher": "Canonical",
    "image_offer": "UbuntuServer",
    "image_sku": "14.04.3-LTS",

    "location": "Japan West",
    "vm_size": "Standard_D1"
  }],
  "provisioners": [{
    "type": "shell",
      "scripts": [
        "../script/ubuntu/provision.sh"
    ]
  },
  {
    "type": "ansible-local",
    "playbook_file": "../ansible/baseimage.yml",
    "inventory_file": "../ansible/hosts",
    "role_paths": [
      "../ansible/roles/baseimage"
    ]
  },
  {
    "type": "shell",
      "scripts": [
        "../script/ubuntu/deprovision.sh"
    ]
  }]
}

waagentによるde-provisionはansibleでもできるのですが、他OS対応も考えて、最後に追いshellしてます。他ファイルはGithubでご確認を。

これで手順書&目視&指差し確認でイメージ作るのを、やめられそうですね。

09 Mar 2016, 16:30

Terraform & Azure デプロイ設計4原則

注: 2018/1/8にサンプルを更新しました。更新エントリはこちら

情報がありそうでない

以前のエントリで書いたとおり、TerraformでAzureへデプロイする方式をClassicからResource Managerへ移行しているところです。

今後も継続して試行錯誤するとは思うのですが、ふらふらしないように原則を作りました。この手の情報はありそうでないので、参考になればと思いこのエントリを書いています。

なお、考え方は他のクラウドやデプロイツールでも応用できるかと。

4原則

  1. セキュリティファースト
  2. 手順書をなくそう
  3. 分割境界にこだわりすぎない
  4. 早すぎる最適化は悪

なお、サンプルのTerraformファイル群を、Githubに置いておきました。

今後ガラガラポンする可能性は大いにありますが、現時点ではこんな構造です。

.
├── .gitignore
├── main.tf
├── availability_set
│   ├── avset_web.tf
│   ├── avset_db.tf
│   └── variables.tf
├── network
│   ├── sg_backend.tf
│   ├── sg_frontend.tf
│   ├── variables.tf
│   └── vnets.tf
├── storage
│   ├── storage_backend.tf
│   ├── storage_frontend.tf
│   └── variables.tf
└── terraform.tfvars

Availability Setに対するVMのデプロイはTerraformの外でやっています。まだTerraformのAzure RM Providerにない、ということもありますが、VMの増減はアドホックだったり、別ツールを使いたいケースが多いので。

1. セキュリティファースト

セキュリティはデザイン時に考慮すべき時代です。機密情報が漏れないように、また、身内がうっかりリソースを壊して泣かないようにしましょう。

  • 認証情報は変数指定し、設定ファイルから読み込む

    • サブスクリプションIDやOAuth Client ID/Secretなどを、リソースを作るtfファイルに書かない
    • terraform.tfvarsなどにまとめて書く
  • 認証情報や現物情報が入ったファイルはバージョン管理ツールから除外する

    • Gitなら.gitignoreに指定する
    • .tfstateなど現物情報(Azure上のIDなど)が入る結果ファイルも除外
      • チームで使う場合はファイルではなく、Consulなどのリモートバックエンドを使うと思いますが、念のため
  • RBACで必要最小限の権限を付与する

    • Terraformの外の話ですが、サービスプリンシパルを作る時には意識しましょう
    • 身内がリソースをうっかり壊したら、それは管理者の責任です
  • ネットワークセキュリティグループはサブネットに指定しておく

    • 個々のVMの管理者に任せず、サブネットで絞っておきましょう
      • VMはアドホックに作られるケースが多く、ルーズになりがちです
    • サンプルではフロントエンドとバックエンドサブネットそれぞれにセキュリティグループを指定しています
      • フロントの受信はPort 80、443、22を許可 (できれば22はソースIP指定)
      • バックの受信はフロントサブネットからのみ許可 (Internetからの通信を deny all)

2. 手順書をなくそう

どうせなら手順書を無くす心意気でやりましょう。Infrastructure as Codeのメリットのひとつです。コードで手順を語りましょう。わかりやすさ重視です。

ドキュメントを否定する訳ではなく、コード化に至った背景、ポリシーや使い方、前提条件はドキュメント化し、あとはコードで語る。という世界観です。

3. 分割境界にこだわりすぎない

TerraformのModuleをはじめ、最近のデプロイツールはリソースや処理単位をグルーピングできます。ここがアーキテクトの腕の見せ所です。安易に「ベストプラクティス教えろや」という人は残念ながら残念です。大事なことなので2回言いました。

グルーピング、分割する目的は、

  • main.tfの肥大化を防止し、コードの見通しを良くする
  • 再利用しやすくする
  • 責任範囲を明確化し、オーナー意識を醸成する
  • 権限とコードを一致させる

などが挙げられます。規模が小さく関わる人が少ないうちは無理して分割する必要はないですが、大きくなってくるとメリットがあります。

以下が分割単位、境界ポリシーの例です。

  • リソースタイプで分割する

    • サンプルはその例
      • ネットワーク、ストレージ、VM Availability Setで分割
    • 直観的
    • デプロイに関わる人数が少ない間はこれがおすすめ
  • 組織単位で分割する

    • コンウェイの法則
    • リソースタイプ = 組織 という場合もある
      • ネットワーク管理者が別グループ、など
  • 地理的に分割する

    • リージョンやロケーションで分割
    • リソースタイプと組み合わせる手もある
      • “Network_JapanEast”など
  • 静的なリソースと動的なリソースを分ける

    • 変化の頻度で分ける
      • ネットワークが頻繁に変わることはまれ
      • VMは増減が激しい
    • 動的なリソースは対象から外す、別手段とする手も

スカッとしませんが、ひとつのポリシーにこだわらず、複数組み合わせてもいいと思います。そんな世界に僕らは生きています。

4. 早すぎる最適化は悪

最適化できる人 = その道のエキスパート です。使いはじめたばかりの段階では、最適化とか無理。また、システムの外部環境や制約がはじめから決まっていることは、まれです。

なので、はじめから「最強の構成」を目指さないほうがいいでしょう。特に分割方針。きっとすぐに変えたくなります。

ひとつのmain.tfで動かしながら、まずTerraformやAzureの仕様や挙動を理解しましょう。そして、慣れてきて、システムの外部環境や制約が見えてきた時点で分割方針を決めてもいいのではないか、と思います。

そして、

  • リファクタリングできるなら、する
  • リファクタリングできなくても、理解の上で維持し機会を待つ、または、次の機会に活かす
  • はじめに作った人へマサカリを投げない

完璧を求めずにいきましょう。

でも、しつこいですが、セキュリティだけは、はじめから意識してくださいね。Security by design。