APC 技術ブログ

株式会社エーピーコミュニケーションズの技術ブログです。

株式会社 エーピーコミュニケーションズの技術ブログです。

Azure AppService / Container AppsでManaged Identityを利用する

こんにちは。ACS事業部亀崎です。

最近取り組んでいたBackstage RBAC Policy対応のため、Open Policy Agentopa-serverをAzure Container Appsで利用するための設定をする機会がありました。

Backstage RBAC Policyの対応についてはぜひ以下の投稿をご覧ください。

techblog.ap-com.co.jp

opa-serverはPolicyという設定情報の置き場としてAzure Blob Storageを利用することができるのですが、アクセスの際の認証方法としてmanaged identityが利用できるように記載されていました。そのドキュメントに従って指定するのですが、どうしてもアクセスができませんでした。

そこで、Azureプロフェッショナル集団でもある私達のチームメンバーにも確認したところ、Azure VMとAzure Container Appsではmanaged identityの利用方法が違う、ということを教えてもらいました(ちょっと質問したらすぐに「それはこうだよ」と応えてくれるチームメンバー、本当に頼もしいです

ではどうやればできるのか。その調査結果とその後の対応について今回はお伝えしたいと思います。

Azure Managed Identityとは

Blob Storageなど、Azureリソースにアクセスする際にはアクセストークンが必要になります。外部からアクセスする際にはあらかじめ発行した資格情報(セキュリティトークン)を利用します。しかし、できればそうした資格情報はアプリケーション側に持たせたくないものです。そうしたときに利用するのがAzure Managed Identityです。 Managed Identityを利用することでアプリケーション側には資格情報をもたせる必要なく自動的に認証をサポートするAzure内の仕組みです。

learn.microsoft.com

Azure内で動かすサービスならば積極的に活用したいものの1つではないでしょうか。

アプリケーションでの利用方法

Azure VM上のアプリケーションで利用する場合

Azure VM上でmanaged identityを利用してAzure Access Tokenを取得するには、Azure Instance Metadata Service(IMDS)というサービスを利用します。

learn.microsoft.com

実際には http://169.254.169.254/metadata/identity/oauth2/token エンドポイントに対し、resource(scope)やapi-versionを指定し、さらにHTTP Headerに Metadata: true という値を設定してリクエストします。

(request)

$ curl 'http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://management.azure.com/' -H Metadata:true

すると以下のような応答が返ってきます。

response

{
  "access_token":"eyJ0eXAiOi...",
  "refresh_token":"",
  "expires_in":"3599",
  "expires_on":"1504130527",
  "not_before":"1504126627",
  "resource":"https://management.azure.com",
  "token_type":"Bearer"
}

ここで返されるaccess_tokenをAzure Resourceにアクセスする際のTokenとして利用します。

learn.microsoft.com

Azure AppService / Azure Container Appsの場合

Azure AppService(Azure Functions)やContainer Appsの場合、上記のIMDSエンドポイントにアクセスすることができません。 実はこれらの環境では別の方法が提供されています。

learn.microsoft.com

Azure Container AppsやApp Serviceでmanaged identityを有効にすると以下の2つの環境変数が設定されます。

IDENTITY_ENDPOINT="http://localhost:42356/msi/token"
IDENTITY_HEADER="853b9a84-5bfa-4b22-a3f3-0b9a43d9ad8a"

これらの値をエンドポイント、およびHTTP Headerで利用してアクセストークンを取得します。

request

curl `${IDENTITY_ENDPOINT}?resource=https://vault.azure.net&api-version=2019-08-01 -H x-identity-header:${IDENTITY_HEADER}

response

{
    "access_token": "eyJ0eXAi…",
    "expires_on": "1586984735",
    "resource": "https://vault.azure.net",
    "token_type": "Bearer",
    "client_id": "5E29463D-71DA-4FE0-8E69-999B57DB23B0"
}

SDKではどう扱っている?

すでにアプリケーションを開発されている方で、SDKをご利用の方は「こんなエンドポイントとか意識したことないよ」と思われるかもしれません。

実はその通りです。SDKは、環境変数など様々な情報を利用して環境を特定し、適切な方式を自動的に選択しています。

例えば、Go言語の場合は次のところで判定しています。

   c := managedIdentityClient{id: options.ID, endpoint: imdsEndpoint, msiType: msiTypeIMDS}
    env := "IMDS"
    if endpoint, ok := os.LookupEnv(identityEndpoint); ok {
        if _, ok := os.LookupEnv(identityHeader); ok {
            if _, ok := os.LookupEnv(identityServerThumbprint); ok {
                if options.ID != nil {
                    return nil, errors.New("the Service Fabric API doesn't support specifying a user-assigned managed identity at runtime")
                }
                env = "Service Fabric"
                c.endpoint = endpoint
                c.msiType = msiTypeServiceFabric
            } else {
                env = "App Service"
                c.endpoint = endpoint
                c.msiType = msiTypeAppService
            }
        } else if _, ok := os.LookupEnv(arcIMDSEndpoint); ok {
            if options.ID != nil {
                return nil, errors.New("the Azure Arc API doesn't support specifying a user-assigned managed identity at runtime")
            }
            env = "Azure Arc"
            c.endpoint = endpoint
            c.msiType = msiTypeAzureArc
        }
    } else if endpoint, ok := os.LookupEnv(msiEndpoint); ok {
        c.endpoint = endpoint
        if _, ok := os.LookupEnv(msiSecret); ok {
            if options.ID != nil && options.ID.idKind() != miClientID {
                return nil, errors.New("the Azure ML API supports specifying a user-assigned managed identity by client ID only")
            }
            env = "Azure ML"
            c.msiType = msiTypeAzureML
        } else {
            if options.ID != nil {
                return nil, errors.New("the Cloud Shell API doesn't support user-assigned managed identities")
            }
            env = "Cloud Shell"
            c.msiType = msiTypeCloudShell
        }
    } else {
        c.probeIMDS = options.dac
        setIMDSRetryOptionDefaults(&cp.Retry)
    }

    client, err := azcore.NewClient(module, version, azruntime.PipelineOptions{
        Tracing: azruntime.TracingOptions{
            Namespace: traceNamespace,
        },
    }, &cp)

github.com

JavaScriptの場合はいくつかのファイルに分かれて判定ロジックが記述されていますが、IDENTITY_ENDPOINTについては次のコードでチェックしています。

github.com

これらのロジックのお陰で意識することなく最適な方式で実現されています。

独自実装の例(opa-serverのその後)

では、どういった場合に今回の情報が必要になるのか。

それが私が遭遇した opa-server の例です。 opa-server のBlob StorageアクセスのロジックはSDKは利用せずに、独自でHTTP Requestを記述しています。私が調べ始めた段階では確かに managed identityはサポートしていましたが、Azure VM用のIMDSのみのサポートとなっていました。このため、Container Apps上で利用しようとしてもmanaged identityを使ったアクセストークン取得ができませんでした。

github.com

ということで・・・・

せっかくここまで調べたのですし、自分たちにとっても必要となる機能ですのでApp Service/Container Apps対応の修正を行い、Pull Requestを提出させていただきました。

github.com

2024年10月初旬にマージされているので、10月末から11月にかけてのリリースにて公開されるものと思います。

まとめ

この内容を知っておけば、Azure VM、Azure AppService、Azure Functions、Azure Container Appsどの環境を相手にしてもmanaged identityを利用することができるようになると思います。

あとはAKSです。実はAKSもまた少し違った方法が用意されています。そちらについては機会をあらためてご紹介したいと思います。

最後に、私達APC ACS事業部ではAzureによるSystem Modernize、Platform Engineering、Backstage、AIなどに関するお手伝いをさせていただいております。 話を聞かせて欲しいなどありましたらぜひご連絡ください。

株式会社エーピーコミュニケーションズ お問い合わせフォーム

それではまた次の機会でお会いしましょう。