Azure の REST API を curl で操作する

本来、Go の SDK を使って、Terraform にコントリビュートしようとしているのですが、Azure の認証の部分がふわっとしか理解できていないことに気づきました。この辺りは、Active Directory の知識とか、OAuth 2.0 の知識が必要になってくるので、無理せず、一歩一歩いきます。

今日は、REST API を素の curl で叩いてイメージを理解したいと思います。

# Active Directory を用いた認証方式

Active Directory を用いた認証方式には２つあります。１つが、エンドユーザー認証（対話型）です。これは Azure CLI でも使われている認証方式で、ユーザが、Webページ経由で認証する方法です。もう一つが、サービス間認証（非対話型）と呼ばれるものです。サービスプリンシパルを使って、そういった手でのオペレーションなしに認証する方式です。それぞれを試して見ましょう。

# サービス間認証（非対話型）

こちらの方ですが、先にサービスプリンシパルを用意します。私は、Azure CLI を使ったこの方法が一番お手軽で好きです。

* [Azure CLI 2.0 で Azure サービス プリンシパルを作成する](https://docs.microsoft.com/ja-jp/cli/azure/create-an-azure-service-principal-azure-cli?toc=%2fazure%2fazure-resource-manager%2ftoc.json)

サービスプリンシパルは、各種のアプリケーションや、ユーザから、Azure のリソースにアクセスするためのセキュリティID です。これがあれば、Azure を色々操作できちゃいます。これを作ると

```
Client ID
Client Secret
Subscription ID
Tenant ID
```
の４つが取得できます。該当のクライアントID にアクセスする権限が割り振られるという感じです。認証のステップは簡単で、サーバーに対して、クライアントから、アクセストークンを取得したのち、アクセストークン付きで REST API を呼ぶというシンプルなものです。単純に POST しているだけですね。

```
curl -X POST https://login.microsoftonline.com/<TENANT-ID>/oauth2/token  \
  -F grant_type=client_credentials \
  -F resource=https://management.core.windows.net/ \
  -F client_id=<CLIENT-ID> \
  -F client_secret=<AUTH-KEY>
```

のフォーマットでメッセージを送ると、トークンが帰ってきます。ポイントは、URL の最後が、 `token` になっていることです。実際にリクエストして見ます。`MY_...` の箇所は適宜差し替えてください。

```
$ curl -X POST https://login.microsoftonline.com/MY_TENANT_ID/oauth2/token  \
>   -F grant_type=client_credentials \
>   -F resource=https://management.core.windows.net/ \
>   -F client_id=MY_CLIENT_ID \
>   -F client_secret=MY_CLIENT_SECRET
{"token_type":"Bearer","expires_in":"3599","ext_expires_in":"0","expires_on":"1503234463","not_before":"1503230563","resource":"https://management.core.windows.net/","access_token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6IlZXVkljMVdEMVRrc2JiMzAxc2FzTTVrT3E1USIsImtpZCI6IlZXVkljMVdEMVRrc2JiMA8HEUNVZTQhvNW1BydJa5JlRbSn_jeLeo_3l2LlCtzDMyqIPf8M64KU4XLb
　　　：　省略
wvhcWtW4hFQmHDZjBNBONUt4KwxVeHVrXkXwAYKADQvOFbgrOf4VjlEHU9uf8xUME-dgDJ2Nang"}
```

がっつり `access_token` が帰ってきています。こいつをヘッダーにつけて、送ってあげればオッケー

```
curl -v -X GET -H "Content-Type: application/json" -H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6IlZXVkljMVdEMVRrc2JiMzAxc2FzTTVrT3E1USIsImtpZCI6IlZXVkljMVdEMVRrc2JiMzAxc2
　　　：　中略
wvhcWtW4hFQmHDZjBNBONUt4KwxVeHVrXkXwAYKADQvOFbgrOf4VjlEHU9uf8xUME-dgDJ2Nang" -H "Host: management.azure.com"  https://management.azure.com/subscriptions/MY_SUBSCRIPTION_ID/resourceGroups?api-version=2017-05-10&top=10
```

もうちょっと署名とかの展開を想像していましたが、今はかなり簡単みたいですね。もちろん、ライブラリだと、タイムアウトすると、このトークンのリフレッシュが必要になってきます。

獲得した、トークンを、`Authorization: Bearer` ヘッダに渡してあげると、タイムアウトまで、トークンが有効になり、リソースにアクセス可能になります。拍子抜けするぐらい簡単ですね。 ちなみに、これは、リソースグループの一覧を取得するAPIを叩いて見た例です。

```
< HTTP/1.1 200 OK
< Cache-Control: no-cache
< Pragma: no-cache
< Content-Type: application/json; charset=utf-8
< Expires: -1
< x-ms-ratelimit-remaining-subscription-reads: 14998
< x-ms-request-id: 9f7e0527-5269-4d53-83d6-578a90d6db4c
< x-ms-correlation-request-id: 9f7e0527-5269-4d53-83d6-578a90d6db4c
< x-ms-routing-request-id: JAPANEAST:20170820T121120Z:9f7e0527-5269-4d53-83d6-578a90d6db4c
< Strict-Transport-Security: max-age=31536000; includeSubDomains
< Date: Sun, 20 Aug 2017 12:11:20 GMT
< Content-Length: 16462
< 
{"value":[{"id":"/subscriptions/MY_SUBSCRIPTION_ID/resourceGroups/6packabs-dev","name":"6packabs-dev","location":"japaneast","properties":{"provisioningState":"Succeeded"}},{"id":"/subscriptions/MY_SUBSCRIPTION_ID/resourceGroups/6packabs-prod","name":"6packabs-prod","location":"japaneast","properties":{"provisioningState":"Succeeded"}},
:
```

# エンドユーザー認証（対話式）

次はこちらです。実は途中までしかうまくいっていませんが、途中までシェアしたいと思います。

インタラクティブに実施する方法としては、[Data Lake Store での Azure Active Directory を使用したエンドユーザーの認証](https://docs.microsoft.com/ja-jp/azure/data-lake-store/data-lake-store-end-user-authenticate-using-active-directory) がわかりやすいです。これを参考に、Azure AD のアプリケーションを作成しておき、リダイレクトURIをセットしておきます。

## 認証ページへのアクセス

まず、認証ページに、Web ブラウザからアクセスします。

```
https://login.microsoftonline.com/<TENANT-ID>/oauth2/authorize?client_id=<APPLICATION-ID>&response_type=code&redirect_uri=<REDIRECT-URI>
```

こんな感じで、先ほど作成した Azure AD のアプリケーションから、TENANT_ID, APPLICATION_ID を渡しておきます。これを貼り付けてブラウザからアクセスすると、お馴染みの認証画面になります。先ほどと違って、URLの最後が `authorize` になっていますね。

　ポイントとしては、このリダイレクトされた画面に、認証コードが渡されます。こんな感じ。

```
http://someserver.com/?code=<AUTHORIZATION-CODE>&session_state=<GUID>
```
つまり、リダイレクトを設定しておいて、そのサーバを作っておいて、パラメータの、code をゲットすればそれが認証コードです。さて、どうするか。サーバーを作るのが面倒なので、 Azure Functions の HttpTrigger でサクッと作成して、その、URLを、リダイレクトURI にしてしまえばいいです。

```csharp
using System.Net;

public static async Task<HttpResponseMessage> Run(HttpRequestMessage req, TraceWriter log)
{
    log.Info("C# HTTP trigger function processed a request.");


    // parse query parameter
    string code = req.GetQueryNameValuePairs()
        .FirstOrDefault(q => string.Compare(q.Key, "code", true) == 0)
        .Value;

        log.Info($"code: {code}");

    return code == null
        ? req.CreateResponse(HttpStatusCode.BadRequest, "Please pass a code on the query string or in the request body")
        : req.CreateResponse(HttpStatusCode.OK, "Hello ");
}

```
先ほどの、URLをブラウザにはると認証されて、この Azure Functions が呼ばれます。そして、ログとして次のように出てきます。

```
2017-08-20T05:20:52.229 C# HTTP trigger function processed a request.
2017-08-20T05:20:52.229 code: AQABAAIAAAA9kTklhVy7SJTGAzR-p1BcGwLK-HhcTGjeeGslEMAzK-cTTzr5QH0KxdJriHAxX0G9RWDd64pmEftnJJoqmmO3OTH-
 : 中略
RZyN9C6NVl0kT667J_5XWl1mFNzE9cQkDjT5dIP1aoSjLscKzRYMgtKkGATvU9V3pijMkXG76xxulytan4wKutZYdx-WY3vPm32qwz9BymwEjJZmN8gAA
2017-08-20T05:20:52.229 Function completed (Success, Id=e847af2c-ce74-459a-b698-c9a943e63918, Duration=0ms)

```

このコードを使って、トークンを取得します。

```
 curl -X POST https://login.microsoftonline.com/<TENANT-ID>/oauth2/token \
 -F redirect_uri=<REDIRECT-URI> \
 -F grant_type=authorization_code \
 -F resource=https://management.core.windows.net/ \
 -F client_id=<APPLICATION-ID> \
 -F code=<AUTHORIZATION-CODE>
```

あとは、こいつをリクエストしてあげれば、先ほどと同じような、アクセストークンが取得されます。残念ながら、この作業のためには、先ほど作成したアプリケーションに、AD の管理者の権限で権限を付与する必要があり、手元にない私の方では、環境を用意できませんでした。

ちなみに、トークンが有効期限切れの時は、次の要求を行います。

```
 curl -X POST https://login.microsoftonline.com/<TENANT-ID>/oauth2/token  \
      -F grant_type=refresh_token \
      -F resource=https://management.core.windows.net/ \
      -F client_id=<APPLICATION-ID> \
      -F refresh_token=<REFRESH-TOKEN>
```

# まとめ

今日は最後まで試しきれませんでしたが、少なくとも、どんなオペレーションが内部で行われているのかわかりました。継続して調査したいと思います。

## リソース

* [REST API で Azure Data Lake Store の使用を開始する](https://docs.microsoft.com/ja-jp/azure/data-lake-store/data-lake-store-get-started-rest-api)
* [Azure Active Directory for developers](https://docs.microsoft.com/en-us/azure/active-directory/develop/active-directory-developers-guide)
* [The OAuth 2.0 Authorization Framework](https://tools.ietf.org/html/rfc6749)
* [Azure REST API Reference](https://docs.microsoft.com/en-us/rest/api/)
* [The OAuth 2.0 Authorization Framework: Bearer Token Usage](https://tools.ietf.org/html/rfc6750)
* [トークンを利用した認証・認可 API を実装するとき Authorization: Bearer ヘッダを使っていいのか調べた](http://qiita.com/uasi/items/cfb60588daa18c2ec6f5)