はじめに
記事をご覧頂きありがとうございます!
前回に引き続き、今回も小ネタで、Azure FunctionsからAWSを操作する構成を検証したのでご紹介します。今回はSSO構成を採用せず、OIDC+AssumeRoleWithWebIdentityで長期鍵なしの接続を検証しました。
背景としては、AWSで構築したシステムのデータをAzureのBIツールで分析する必要があり、Azure FunctionsをHubとしてAWSのアカウントにアクセスする構成を実現しました。
AWSとAzureは、それぞれ個別には触っていましたが、連携自体は初めてだったため、そういった方も多いかもと思い、今回の検証結果をご紹介致します。
[忙しい人向け] この記事の記載内容紹介
- 検証内容のご紹介
Azure Functions(Python)から長期鍵なしでAWSのHubアカウントにアクセスし、Switch RoleでS3のオブジェクト一覧を取得。- Azure
- Azure App RegistrationでOIDC IdPとしての設定を行う
- Azure Functionsがバッチ処理の実行元となる
- AWS
- IAM Identity ProviderにAzureのOIDC情報を登録
- IAM Roleを作成し、OIDC IdPからのAssumeRoleを許可
- S3アクセス用のIAM RoleにSwitch Role(権限委譲)
- Azure
- はまったポイントの紹介
- Issuerの指定
構成概要
AzureとAWSの連携というと、Microsoft Entra ID(旧Azure AD)とAWS Identity Centerを使ったSSO構成が一般的です。特に社内ユーザーがAWSコンソールにログインするようなユースケースでは、SSO連携が主流です。
しかし、今回は、そこまでの重厚な構成は採用せず、Azure FunctionsからAWSのS3などのリソースに対してバッチ処理を安全に実行するという目的に絞り、OIDC(OpenID Connect)を使ったIdP連携によるロール引受(AssumeRoleWithWebIdentity)の構成を採用しました。
OIDCとは?
OIDC(OpenID Connect)は、OAuth 2.0をベースにした認証プロトコルです。
OAuth 2.0は「権限付与」が目的ですが、OIDCはそこに「認証」を追加します。
認証後にIDトークン(JWT形式)が発行され、ユーザーやサービスのID情報を安全にやり取りできます。
今回の構成では、Entra IDがOIDCトークンを発行し、AWSがそのトークンを検証してIAMロールを引き受けます。
STSのAssumeRoleWithWebIdentityとは?
AWS STS(Security Token Service)の AssumeRoleWithWebIdentity API は、外部IdP(今回はAzure)から渡されたJWT(IDトークン)を検証し、一時的なアクセスキー+セッショントークンを返します。
この仕組みにより、Azure Functionsなどの外部サービスがAWSリソースに安全かつ動的にアクセスできるようになります。
構成のセキュリティと運用性のメリット
- 長期的なアクセスキーを配布しないため、漏洩リスクが大幅に低減
- IAMロールの権限を必要最小限に絞ることで、最小権限の原則を守りやすい
- Azure側の認証基盤をそのまま活用できるため、ID管理の一元化にも貢献
手順
1. Azure側(アプリ登録&Functions準備)
1.1. Azure Functionsの作成
Azure PortalでAzure Functionsを作成します。
1.2. Managed IDの発行
Azure PortalのAzure Functionsページ左側メニュー「ID」をクリックし、以下の通りに設定して発行を行います。
- 状態:オン
1.3. コードの開発
コードの流れは以下の通りで、Azure FunctionsのManaged Identityを使って、AWSロールに安全にスイッチする構成です。
(1) Azure MIでOIDCトークン取得(aud = APP_ID_URI/.default)
(2) AWSのFuncRoleに AssumeRoleWithWebIdentity*¹
(3) そのクレデンシャルでTargetRoleにAssumeRole
(4) TargetRoleでS3のオブジェクト一覧を取得
import os, json
import azure.functions as func
from azure.identity import ManagedIdentityCredential
import boto3
app = func.FunctionApp()
@app.function_name(name="bridge")
@app.route(route="bridge", methods=["GET"])
def bridge(req: func.HttpRequest) -> func.HttpResponse:
app_id_uri = os.environ["APP_ID_URI"]
func_role_arn = os.environ["FUNC_ROLE_ARN"]
target_role_arn = os.environ["TARGET_ROLE_ARN"]
aws_region = os.environ.get("AWS_REGION", "ap-northeast-1")
s3_bucket = os.environ["S3_BUCKET"]
# 1) Azure MI → aud = APP_ID_URI/.default のトークン取得
web_id_token = ManagedIdentityCredential().get_token(f"{app_id_uri}/.default").token
# 2) STS: FuncRole へ AssumeRoleWithWebIdentity
sts_func = boto3.client("sts", region_name=aws_region)
creds_func = sts_func.assume_role_with_web_identity(
RoleArn=func_role_arn,
RoleSessionName="funcrole-bridge",
WebIdentityToken=web_id_token,
DurationSeconds=3600
)["Credentials"]
# 3) STS: RuncRoleの短期クレデンシャルでTargetRoleへAssumeRole
sts_target = boto3.client(
"sts",
region_name=aws_region,
aws_access_key_id=creds_func["AccessKeyId"],
aws_secret_access_key=creds_func["SecretAccessKey"],
aws_session_token=creds_func["SessionToken"]
)
creds_target = sts_target.assume_role(
RoleArn=target_role_arn,
RoleSessionName="targetrole-session",
DurationSeconds=3600
)["Credentials"]
# 4) TargetRoleの短期クレデンシャルでS3 list(長期鍵は使っていない)
s3 = boto3.client(
"s3",
region_name=aws_region,
aws_access_key_id=creds_target["AccessKeyId"],
aws_secret_access_key=creds_target["SecretAccessKey"],
aws_session_token=creds_target["SessionToken"]
)
resp = s3.list_objects_v2(Bucket=s3_bucket, MaxKeys=10)
keys = [it["Key"] for it in resp.get("Contents", [])]
return func.HttpResponse(
json.dumps({"ok": True, "assumed_to": "targetrole", "keys": keys}, ensure_ascii=False),
mimetype="application/json", status_code=200
)
- 必要な環境変数
- APP_ID_URI :アプリケーションID URI(例:api://[client-id])
- FUNC_ROLE_ARN :Functionsが引き受けるIAMロールARN
- TARGET_ROLE_ARN:Switch先IAMロールARN
- AWS_REGION :ap-northeast-1 等
- S3_BUCKET :対象バケット名
1.4. Azure Functionsへのコードのデプロイ
私の場合はローカルで開発を行っていたので、コードが配置されたフォルダ配下に移動し、以下のazure cliを実行します。
func azure functionapp publish [Functions名]
ローカルでビルドを行って動作確認を行いたい場合は以下のコマンドです。
func build
1.5. アプリの登録
Azure Portalで以下の値を入力し、アプリの登録を作成します。
- 名前:任意の名前
- サポートされているアカウントの種類:この組織ディレクトリのみに含まれるアカウント (クラウド利用技術ディレクトリ のみ - シングル テナント)
1.6. アプリケーションIDのURIの発行
Azure Portalで以下の値を入力して、アプリケーションIDのURI(例:api://[client-id])を設定します。
- アプリケーション ID の URI:api://[client-id]
2. AWS側(OIDC IdPとIAMロール)
2.1. ID プロバイダの作成
AWS Consoleで以下の値を入力し、ID プロバイダを作成します。
- プロバイダのタイプ:OpenID Connect
- プロバイダのURL:https://sts.windows.net/[tenant-id]/
- 対象者:手順1.3で発行したアプリケーションIDのURI(例:api://[client-id])
2.2. Switch先IAMロールの作成
AWS Consoleで以下のように設定を行い、Switch先IAMロールを作成します。
2.2.1. 信頼ポリシー
作成自体はこれからですが、PrincipalでFunctions用IAMロールを指定します。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::[AWSアカウントID]:role/[Functions用IAMロール名]"
},
"Action": "sts:AssumeRole"
}
]
}
2.2.2. 操作権限ポリシー
今回はS3内のバケット一覧を取得するだけなので、AWSマネージドのAmazonS3ReadOnlyAccessポリシーをアタッチします。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:Get*",
"s3:List*",
"s3:Describe*",
"s3-object-lambda:Get*",
"s3-object-lambda:List*"
],
"Resource": "*"
}
]
}
2.3. Functions用IAMロールの作成
AWS Consoleで以下のように設定を行い、Functions用IAMロールを作成します。
2.3.1. 信頼ポリシー
ConditionでIssuer(iss)とAudience(aud)を指定します。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::[AWSアカウントID]:oidc-provider/sts.windows.net/[tenant-id]/"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"sts.windows.net/[tenant-id]/:aud": "api://[client-id]"
}
}
}
]
}
OIDC IdP作成時のIssuer URLと、この信頼ポリシーのFederated/StringEqualsキーは必ず一致させて下さい。ここを間違えて、InvalidIdentityTokenになることが多いです。
2.3.2. 操作権限ポリシー
sts:AssumeRoleでSwitch先ロール名を指定します。
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "VisualEditor0",
"Effect": "Allow",
"Action": "sts:AssumeRole",
"Resource": [
"arn:aws:iam::[AWSアカウントID]:role/[Switch先IAMロール名]"
],
"Condition": {
"Bool": {
"aws:MultiFactorAuthPresent": "true"
}
}
}
]
}
3. 動作確認
3.1. S3の準備
動作確認用のS3を用意して、オブジェクトをアップロードします。
3.2. 環境変数の準備
Functions実行用の環境変数を設定します。
az functionapp config appsettings set `
--name [Function名] `
--resource-group [ResoueceGroup名] `
--settings `
APP_ID_URI='[アプリケーションID URI]' `
FUNC_ROLE_ARN='[Function用IAMロールのARN]' `
TARGET_ROLE_ARN="[Switch先IAMロールのARN]" `
S3_BUCKET='[動作確認用S3バケット名]' `
AWS_REGION='ap-northeast-1'
3.3. Functionsの実行
以下のコマンドでFunctionsを実行して動作確認を行います。
$app = "[Functions名]"
$rg = "[ResourceGroup名]"
$func = "[関数名]"
$invoke = az functionapp function show `
--name $app `
--resource-group $rg `
--function-name $func `
--query "invokeUrlTemplate" -o tsv
$key = az functionapp function keys list `
--name $app `
--resource-group $rg `
--function-name $func `
--query "default" -o tsv
Invoke-RestMethod -Method GET -Uri $invoke -Headers @{ "x-functions-key" = $key }
ok assumed_to keys
-- ---------- ----
True C {qiita.pptx}
Functionsの実行結果で、無事S3のオブジェクト一覧を取得することが出来ました。
はまったポイント
Issuer(iss)
OIDC IdPのIssuer URLを指定するところで、以下のEntra IDのOIDCエンドポイントv2を入力したところ、エラーとなりました。
login.microsoftonline.com/{tenant-id}/v2.0
一方で、今回1.3の手順でも記載しましたが、以下のEntra IDのOIDCエンドポイントv1を入力した結果、成功しました。
sts.windows.net/{tenant-id}/
この挙動は、Entra IDの「アプリの登録」で設定する内容によって変わるようです。特に、サポートされているアカウントの種類やAPI権限の設定によって、v2エンドポイントに対してトークンを要求しても、実際には v1形式のissクレーム(sts.windows.net)が返されることがあります。
実際には、次のいずれかの条件を満たす場合にv1が返るケースが多いようです。
- アプリが「この組織ディレクトリのみ」
- 従来のAzure ADリソース(v1 API)を使用
- accessTokenAcceptedVersionが明示的に2に設定されていない
今回のケースでは、カスタムWeb API(Azure Functions)を使用しており、Microsoft Graphなどのv1 APIを直接利用しているわけではありません。そのため、2の「従来のAzure ADリソース」には該当しないと考えられます。ただし、Microsoft GraphやAzure AD Graphなど、v1 APIしか提供されていないサービスを利用する場合は、v1形式のトークン(sts.windows.net)が返されることが一般的です。
そのため、1と3が影響したと考えられます。1の場合、シングルテナントアプリとなりますが、マルチテナントアプリが外部ユーザーや個人アカウント対応のためv2と互換性を重視しているのとは反対に、シングルテナントアプリは古いv1エンドポイントとの互換性を重視していたため、v1が返ることが多いようです。また、3についても、明示的に設定しない=nullの場合、v1が返ることが多いようです。
これらのことから、今回の検証では未検証となりますが、確実にv2にしたい場合はアプリマニフェストでaccessTokenAcceptedVersionを2に設定し、さらにv2エンドポイント(プロバイダのURL)を使用し、スコープ(対象者)を指定するで、v2が返るようになるとのことです。
まとめ
今回は、Azure FunctionsからAWSを操作する構成を検証しました。採用した方法は OIDC + AssumeRoleWithWebIdentity です。この方法が手軽で使いやすい理由は以下の通りです。
- 長期アクセスキーが不要
- 配布・保管・ローテーションの手間ゼロ
- STSの短期資格モデルでリスクを限定
- 完全起動化
- Azure Functionsのコード内でトークンを取得し、ブラウザ認証なしでAWS操作が可能
- バッチ処理に最適
- 構成がシンプル
- Azure側はアプリ登録+ManagedID、AWS側はOIDC IdP+ロールで実現可能
- SSO連携より導入が容易
AzureとAWSを個別に使うことは多くても、こうしたマルチクラウド連携は意外と機会が少ないと思います。今回の検証が、同様の構成を検討している方の参考になれば幸いです。
今回も最後までお読みいただきありがとうございました!
We Are Hiring!






