#author("2020-08-07T06:54:46+00:00","","")
#mynavi(Azureメモ)
#setlinebreak(on);

* 概要 [#dadf4db4]
#html(<div class="pl10">)
#TODO
Azure  App Service で配備した Grafana でAD(Active Directory)認証を行う手順を記載する。
尚、認証以外の部分は、[[Azure App Service に Grafana をデプロイ]] をベースにしている為、詳細はそちらを参照。
#html(</div>)

* 目次 [#g9c9f576]
#contents
- 関連
-- [[Azureメモ]]
-- [[Azure App Service に Grafana をデプロイ]]
-- [[Azure VM上にGrafanaをデプロイ]]
- 参考
-- https://grafana.com/docs/grafana/latest/auth/ldap/
-- https://grafana.com/docs/grafana/latest/auth/ldap/#active-directory
-- https://grafana.com/docs/grafana/latest/auth/azuread/
-- https://techexpert.tips/ja/grafana-ja/active-directory%E3%81%A7%E3%81%AEgrafana-ldap%E8%AA%8D%E8%A8%BC/
-- [[Microsoft ID プラットフォーム を使用したアプリのサインイン フロー>https://docs.microsoft.com/ja-jp/azure/active-directory/develop/app-sign-in-flow]]
-- [[Microsoft ID プラットフォームを使用した認証と承認>https://docs.microsoft.com/ja-jp/azure/active-directory/develop/authentication-vs-authorization#authentication-and-authorization-using-microsoft-identity-platform]]
-- [[Microsoft ID プラットフォームと OAuth 2.0 認証コード フロー>https://docs.microsoft.com/ja-jp/azure/active-directory/develop/v2-oauth2-auth-code-flow]]
-- [[認証コード フローと暗黙的なフロー(Web Apps)>https://docs.microsoft.com/ja-jp/azure/active-directory/develop/v2-app-types#web-apps]]
-- [[Azure AD OAuth2 authentication>https://grafana.com/docs/grafana/latest/auth/azuread/]]
-- [[Microsoft ID プラットフォームと暗黙的な許可のフロー (暗黙的な許可フローの有効有効化)>https://docs.microsoft.com/ja-jp/azure/active-directory/develop/v2-oauth2-implicit-grant-flow#send-the-sign-in-request]]
-- [[Azure Active Directory を使ってアプリケーションに対するグループ要求を構成する>https://docs.microsoft.com/ja-jp/azure/active-directory/hybrid/how-to-connect-fed-group-claims#configure-the-azure-ad-application-registration-for-group-attributes]]
-- [[アプリケーションにアプリ ロールを追加してトークンで受け取る>https://docs.microsoft.com/ja-jp/azure/active-directory/develop/howto-add-app-roles-in-azure-ad-apps]]

* クライアント(アプリケーション)IDの登録 及び シークレットの作成 [#l24d8307]

* ADアプリ 及び シークレットの作成 [#m5cf0c30]
#html(<div class="pl10">)

シークレットの作成は App Service アプリを登録してから、そのアプリのシークレットとして登録しても良いが、
認証のベースとなるアプリ 及び シークレット は、以下の理由で別アプリとして先に作成しておく。

- 既に認証情報を持つアプリが存在するケースへの対応( 複数アプリ間の SSO まで考慮 )&br; ※ここで登録しているアプリは、先行でリリース済みの別アプリとして考える。
- App Service の作成時にOAuth系の環境変数の設定まで行いたい為。
- 開発段階ではシークレットが、特定のアプリに依存していない方が削除、作り直しが行いやすい。

#html(){{
<style>
.images div { vertical-align: top; margin-right: 20px; }
.images img { border: 1px solid #333; }
</style>
}}
#html(<div class="images">)
#html(<div class="ib">)

** ADアプリの登録 [#x0578f87]
#html(<div class="pl10">)

#html(<div class="images"><div class="ib">)
[Active Directory] を選択
&ref(create_secret1-1.png,nolink);
#html(</div><div class="ib">)
[アプリの登録] を選択
&ref(create_secret1-2.png,nolink);
#html(</div><div class="ib">)
[新規登録] を選択
&ref(create_secret1-3.png,nolink);
#html(</div><div class="ib">)
アプリケーション名を入力して登録
&ref(create_secret1-4.png,nolink);
#html(</div><div class="ib">)
クライアントID(アプリケーションID)を確認しておく
クライアントID 及び テナントID を確認しておく
&ref(create_secret1-5.png,nolink);
#html(</div><div class="ib">)
#html(</div></div>)

#html(</div>)

** クライアントシークレットの作成 [#ae9c173b]
#html(<div class="pl10">)

#html(<div class="images"><div class="ib">)
[証明書とシークレット] → [新しいクライアントシークレット] を選択
&ref(create_secret1-6.png,nolink);
#html(</div><div class="ib">)
説明を入力して [追加]
&ref(create_secret1-7.png,nolink);
#html(</div><div class="ib">)
登録したクライアントシークレットを確認しておく。(追加直後しか確認できないので注意)
&ref(create_secret1-8.png,nolink);
#html(</div></div>)

#html(</div>)
#html(</div>)

** 認証設定 [#nb493ae5]
#html(<div class="pl10">)

#html(</div>)
#html(<div class="images"><div class="ib">)
[認証] → [プラットフォームの追加]
&ref(create_secret1-9.png,nolink);
#html(</div><div class="">)
#html(<div class="ib">)
リダイレクトURI 等を登録する。
&ref(create_secret1-10b.png,nolink);
#html(</div><div class="ib">)
 
 
| 項目 | 設定値 |h
| リダイレクトURI | https&#58;//Grafanaアプリケーション名.azurewebsites.net/login/azuread |
| 〃(2つ目)|  https&#58;//Grafanaアプリケーション名.azurewebsites.net/.auth/login/aad/callback |
| 暗黙的な許可フロー |  シークレットを 他の Service App と共用する場合は2つともチェックを入れておく |

※ Grafanaアプリケーション名は 後述の 0_env.sh の $webappName と同じもの。

* リソースの作成 [#p10153b6]
#html(<div class="pl10">)
#html(</div></dv></div></div>)
#html(</div>)

** シェル作成 [#r711498e]
** アプリケーションロールの作成 [#qfb72dfa]
#html(<div class="pl10">)

#html(){{
<div id="tabs1">
  <ul>
    <li><a href="#tabs1-0">0_env.sh</a></li>
    <li><a href="#tabs1-1">1_resources.sh</a></li>
    <li><a href="#tabs1-2">2_vm_setup.tmpl</a></li>
  </ul>
}}
左側メニューの [マニフェスト] から、登録したアプリケーションのマニフェストを以下の通り更新する(アプリケーションロールの追加)
※ユニークIDは uuidgen 等を使用して3つ生成しておく。
※ https://grafana.com/docs/grafana/latest/auth/azuread/

// START tabs1-0
#html(<div id="tabs1-0">)

0_env.sh
#mycode2(){{
#!/bin/bash
        :
	"appRoles": [
		{
			"allowedMemberTypes": [
				"User"
			],
			"description": "Grafana Editor Users",
			"displayName": "Grafana Editor",
			"id": "ユニークなID",
			"isEnabled": true,
			"lang": null,
			"origin": "Application",
			"value": "Editor"
		},
		{
			"allowedMemberTypes": [
				"User"
			],
			"description": "Grafana read only Users",
			"displayName": "Grafana Viewer",
			"id": "ユニークなID",
			"isEnabled": true,
			"lang": null,
			"origin": "Application",
			"value": "Viewer"
		},
		{
			"allowedMemberTypes": [
				"User"
			],
			"description": "Grafana admin Users",
			"displayName": "Grafana Admin",
			"id": "ユニークなID",
			"isEnabled": true,
			"lang": null,
			"origin": "Application",
			"value": "Admin"
		}
	],
        :
        "groupMembershipClaims": "ApplicationGroup",
        :
}}

PREFIX="各リソース名に付ける接頭文字"
subscriptionId="サブスクリプションID"
region=japanwest
#html(</div>)

resourceGroup=${PREFIX}ResourceGroup

vnetName=${PREFIX}VNet
vnetPrefix=10.1.0.0/16
#html(</div>)
// ADアプリ 及び シークレットの作成

nsgName=${vnetName}SecGrp
nsgPubRuleName=${nsgName}PubRule
nsgPubInboundPort="3000"
* ユーザ作成 及び ロールの割当 [#f573268d]
#html(<div class="pl10">)

vmSubnetName=${PREFIX}VmSubnet
vmSubnetPrefix=10.1.1.0/24
** ユーザの作成 [#d6279ac1]
#html(<div class="pl10">)

vmName=${PREFIX}Vm
vmImage=UbuntuLTS
vmIpAddress=10.1.1.5
vmUser=sample
[Azure Active Directory] → [ユーザ] から下図の通りユーザを登録。
#html(<div class="ib border">)
&ref(create-users.png,nolink);
#html(</div>)

# 登録したアプリケーションのクライアントID、シークレット
clientId="確認したクライアントID"
clientSecret="確認したクライアントシークレット"
#html(</div>)

# テナントIDの取得
tenantId=`az account show -s $subscriptionId -o table | tail -1 | awk '{print $NF}'`
}}
** ロールの割り当て [#jdcb9361]
#html(<div class="pl10">)

[Azure Active Directory] → [エンタープライズ アプリケーション] から対象のアプリを選択後に [ユーザーとグループ] を選択して、ユーザにアプリケーションロールを割り当てる。
※デフォルトで Viewer権限 になるので、割り当てるのは Admin と Editor に該当するユーザだけでOK。
#html(<div class="ib border">)
&ref(attach_role.png,nolink);
#html(</div>)
// END tabs1-0

// START tabs1-1
#html(<div id="tabs1-1">)
#html(</div>)

#mycode2(){{
#!/bin/bash
#html(</div>)

# 設定の読み込み
source 0_env.sh

# リソース作成
if [ "$1" == "--create" ]; then
* リソースの作成 [#p10153b6]
#html(<div class="pl10">)

  # リソースグループの作成
  echo az group create
  az group create --name $resourceGroup --location $region
** ファイル作成 [#r711498e]
#html(<div class="pl10">)

  # NSG(ネットワークセキュリティグループの)作成
  echo az network nsg create
  az network nsg create --resource-group $resourceGroup --name $nsgName
シェルの内容は [[Azure App Service に Grafana をデプロイ]] の内容をベースにしている為、以下には変更点のみ記載する。

  # NSGルール(Public)
  echo az network nsg rule create
  az network nsg rule create \
    --resource-group $resourceGroup --nsg-name $nsgName --name ${nsgPubRuleName}1\
    --access Allow --protocol Tcp --direction Inbound --priority 100 \
    --source-address-prefix Internet --source-port-range "*" --destination-port-range $nsgPubInboundPort
#html(){{
<div id="tabs1">
  <ul>
    <li><a href="#tabs1-1">0_env.sh</a></li>
    <li><a href="#tabs1-2">3_app_resources.sh</a></li>
  </ul>
}}

  # NSGルール(Public) SSH
  echo "az network nsg rule create(ssh)"
  az network nsg rule create \
    --resource-group $resourceGroup --nsg-name $nsgName --name ${nsgPubRuleName}2 \
    --access Allow --protocol Tcp --direction Inbound --priority 101 \
    --source-address-prefix Internet --source-port-range "*" --destination-port-range "22"
// START tabs1-1
#html(<div id="tabs1-1">)

  # 仮想ネットワーク 及び サブネット作成
  echo az network vnet create
  az network vnet create \
      --name $vnetName --resource-group $resourceGroup \
      --address-prefixes $vnetPrefix --network-security-group $nsgName \
      --subnet-name $vmSubnetName --subnet-prefixes $vmSubnetPrefix

  # VM用の初期化シェルに含まれているシークレットID等を置換する
  cat 2_vm_setup.tmpl | sed "s/\$clientId/$clientId/g" \
      | sed "s/\$clientSecret/$clientSecret/g" \
      | sed "s/\$tenantId/$tenantId/g">2_vm_setup.sh

  # 仮想マシンの作成
  rm -rf  ~/.ssh/id_rsa
  rm -rf  ~/.ssh/id_rsa.pub
  echo az vm create
  az vm create \
    --resource-group $resourceGroup --name $vmName --image $vmImage --generate-ssh-keys \
    --vnet-name $vnetName --subnet $vmSubnetName \
    --private-ip-address $vmIpAddress --admin-username $vmUser \
    --public-ip-address-dns-name `echo $vmName | tr '[A-Z]' '[a-z]'` \
    --custom-data 2_vm_setup.sh

  # Grafana用のポート開放
  az vm open-port --resource-group $resourceGroup --name $vmName --port 3000

  # 生成されたSSH鍵を移動( --ssh-dest-key-path が効かない為 )
  mkdir -p pem
  if [ -e ~/.ssh/id_rsa ]; then
    mv ~/.ssh/id_rsa     ./pem/id_rsa_${vmName}
    mv ~/.ssh/id_rsa.pub ./pem/id_rsa_${vmName}.pub
  fi

fi

# リソース削除
if [ "$1" == "--delete" ]; then
  echo az group delete
  az group delete --name $resourceGroup
fi
以下を追加
#mycode2(){{
    :
    :
# OAuth用
adClientId="クライアントID"
adClientSecret="クライアントシークレット"
tenantId="テナントID"
}}

#html(</div>)
// END tabs1-1

// START tabs1-2
#html(<div id="tabs1-2">)

環境変数を追加(GF_AUTH_AZUREAD_XXXXX)
#mycode2(){{
#!/bin/bash
    :
    :
  # App Service 環境変数設定
  echo "az webapp config appsettings ( $webappName )"
  az webapp config appsettings set \
      -g $resourceGroup -n $webappName \
      --settings "GF_SERVER_ROOT_URL=https://${webappName}.azurewebsites.net" \
      "GF_SECURITY_ADMIN_PASSWORD=admin" "GF_DATABASE_TYPE=mysql" "GF_DATABASE_HOST=${vmIpAddress}:3306" \
      "GF_DATABASE_NAME=grafana" "GF_DATABASE_USER=grafana" "GF_DATABASE_PASSWORD=grafana" \
      "WEBSITES_PORT=3000" \
      "GF_AUTH_AZUREAD_NAME=Azure AD" \
      "GF_AUTH_AZUREAD_ENABLED=true" \
      "GF_AUTH_AZUREAD_CLIENT_ID=${adClientId}" \
      "GF_AUTH_AZUREAD_CLIENT_SECRET=${adClientSecret}" \
      "GF_AUTH_AZUREAD_AUTH_URL=https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/authorize" \
      "GF_AUTH_AZUREAD_TOKEN_URL=https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/token" \
      "GF_AUTH_DISABLE_LOGIN_FORM=true" "GF_AUTH_OAUTH_AUTO_LOGIN=true" \
      "GF_AUTH_SIGNOUT_REDIRECT_URL=https://${webappName}.azurewebsites.net/.auth/logout"
}}

apt-get update
apt-get install -y apt-transport-https ca-certificates curl gnupg-agent software-properties-common
#html(</div>)
// END tabs1-2

curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
#html(</div>)
// END tabs1

add-apt-repository \
   "deb [arch=amd64] https://download.docker.com/linux/ubuntu \
   $(lsb_release -cs) \
   stable"
#html(<script>$(function() { $("#tabs1").tabs(); });</script>)
#html(</div>)

apt-get update
apt-get install -y docker-ce docker-ce-cli containerd.io
** リソース作成 [#w558c42c]
#html(<div class="pl10">)

curl -L "https://github.com/docker/compose/releases/download/1.26.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
chmod +x /usr/local/bin/docker-compose
VMなど
#myterm2(){{
./1_resources.sh --create
}}

mkdir /tmp/grafana_docker && cd /tmp/grafana_docker
mkdir -p data/grafana && chmod 777 data/grafana
App Service(Grafana)
#myterm2(){{
./3_app_resources.sh --create
}}

cat <<_EOYML_>docker-compose.yml
version: "3"
#html(</div>)

services:
  grafana:
    image: grafana/grafana:7.1.1
    hostname: grafana_sample
    container_name: grafana_sample
    ports:
      - "3000:3000"
    volumes:
      - ./grafana.ini:/etc/grafana/grafana.ini
    environment:
      - GF_SERVER_ROOT_URL=http://localhost:3000
      - GF_SECURITY_ADMIN_PASSWORD=admin
_EOYML_
#html(</div>)

# get grafana.ini template
docker run -d --name grafana_tmp --rm grafana/grafana:7.1.1
while [ true ]; do
  docker cp grafana_tmp:/etc/grafana/grafana.ini grafana.ini.tmpl
  if [ "$?" == "0" ]; then
    docker stop grafana_tmp
    break
  fi  
  sleep 2
done
* App Service 側の認証/承認設定 [#k753fded]
#html(<div class="pl10">)

# replace [auth.azuread] config
cat grafana.ini.tmpl | awk -v client_id="$clientId" -v client_secret="$clientSecret" -v tenant_id="$tenantId" '
BEGIN{
  target=0
}
{
  if ($1=="[auth.azuread]") {
    target=1
    print $1
  }
  if (target>1){
    target=target+1
    if (substr($1, 1, 1) == "[") {
      target=0
    } else {
      print
    }   
  }
  if (target==1){
    print "name = Azure AD" 
    print "enabled = true"
    print "client_id = "client_id
    print "client_secret = "client_secret
    print "auth_url = https://login.microsoftonline.com/"tenant_id"/oauth2/v2.0/authorize"
    print "token_url = https://login.microsoftonline.com/"tenant_id"/oauth2/v2.0/token"
    print "role_attribute_path = contains(groups[*], 'admin') && 'Admin' || contains(groups[*], 'editor') && 'Editor' || 'Viewer'"
    print ""
    target=target+1
  }
  if (target==0) {
    print
  }
}' > grafana.ini
App Service 側もADアプリ側の [[認証設定>#nb493ae5]] と同じ設定をしておく。

# start grafana
docker-compose up -d
}}
#html(<div class="images"><div class="ib">)
[App Service] から対象の App Servce アプリを
選択後、「認証/承認」 を選択。
&ref(azure_app_servce_auth1.png,nolink);
#html(</div><div class="ib">)
・「オン」、「Active Directory でのログイン」 を選択。
・認証プロバイダーの Active Directory の選択。
&ref(azure_app_servce_auth2.png,nolink);
#html(</div><div class="ib">)
Active Directory の設定を下図の通り入力して [OK]
※クライアントID、シークレットは先に登録したADアプリのものを入力。
&ref(azure_app_servce_auth3.png,nolink);
#html(</div><div class="ib">)
 
詳細設定を下図の通り設定して、最後に [保存] を押下・
&ref(azure_app_servce_auth4.png,nolink);
#html(</div><div class="ib">)
 
 
許可される外部リダイレクト URLは以下の2つを入力しておく。(ADアプリ側に登録したものと同じもの)
| 項目 | 設定値 |h
| リダイレクトURI | https&#58;//Grafanaアプリケーション名.azurewebsites.net/login/azuread |
| 〃(2つ目)|  https&#58;//Grafanaアプリケーション名.azurewebsites.net/.auth/login/aad/callback |

#html(</div>)
// END tabs1-2
#html(</div></div>)

#html(</div>)
// END tabs1

#html(<script>$(function() { $("#tabs1").tabs(); });</script>)
#html(</div>)

** リソース作成 [#w558c42c]

* 動作確認 [#ka88153d]
#html(<div class="pl10">)
#myterm2(){{
./1_resources.sh --create
}}

#html(<div class="images">)

ブラウザから https&#58;//Grafanaアプリケーション名.azurewebsites.net にアクセスしてAdminロールを割り当てたユーザでログインしてみる。
※GF_AUTH_DISABLE_LOGIN_FORM 及び GF_AUTH_OAUTH_AUTO_LOGIN を true にしている為、Grafana のログインフォームは表示されず、最初からOAuth認証が始まる。

#html(<div class="ib">)
&ref(grafana-login2.png,nolink);
#html(</div><div class="ib">)
&ref(grafana-login3.png,nolink);
#html(</div><div class="ib">)
&ref(grafana-login4.png,nolink);
#html(</div><div class="ib">)
Admin 権限でログインされている事を確認。
&ref(grafana-login5.png,nolink);
#html(</div>)
#html(</div>)

#html(</div>)

* 補足 [#t61a4455]
#html(<div class="pl10">)

* XXXXXXXXXX [#v8d82db4]
** ログインURL等について [#fd63636d]
#html(<div class="pl10">)

grafana.ini (抜粋)
#mycode2(){{
[auth.azuread]
name = Azure AD
enabled = true
client_id = クライアントID
client_secret = クライアントシークレット
;scopes = openid email profile
auth_url = https://login.microsoftonline.com/テナントID/oauth2/v2.0/authorize
token_url = https://login.microsoftonline.com/テナントID/oauth2/v2.0/token
;allowed_domains =
;allowed_groups =
; 正常に動作しない (常に Viewer になってしまう )
;role_attribute_path = contains(info.groups[*], 'admin') && 'Admin' || contains(info.groups[*], 'editor') && 'Editor' || 'Viewer'
role_attribute_path = contains(groups[*], 'admin') && 'Admin' || contains(groups[*], 'editor') && 'Editor' || 'Viewer'
}}
Service App アプリの [認証/承認] 設定を行うと、以下のログイン/ログアウトURLが提供され、未ログイン時は自動的に OAuth認証フローが実行されるようになる。
また、このURLを利用してアプリ側でログイン/ログアウト用のリンクを設置したり、ある程度のフロー制御を行う事が出来るようになる。
※上記の GF_AUTH_SIGNOUT_REDIRECT_URL では .auth/logout を利用している。

| 種別 | URL |h
| ログイン | https://アプリケーション名.azurewebsites.net/.auth/login |
| ログアウト | https://アプリケーション名.azurewebsites.net/.auth/logout |

尚、Grafana では Aure OAuth認証用のURLとして 「https://サーバ名/login/azuread」 が用意されており、このURLにリダイレクトされる事で認証結果の受け取り等が行われる。
参考: https://grafana.com/docs/grafana/latest/auth/azuread/

#html(</div>)

* Azure AD認証を設定する [#n828409e]
** Grafana Auth Proxy によるロールマッピングについて [#uaa1d395]
#html(<div class="pl10">)
プロキシを噛ませてHTTPヘッダで認証できないかやってみたが、 Grafana の Auth Proxy は ロールマッピングには対応していない模様。
https://grafana.com/docs/grafana/latest/auth/overview/

Azure AD OAuth2 authentication
https://grafana.com/docs/grafana/latest/auth/azuread/
#html(<div class="ib" style="border:1px solid #333;">)
&ref(grafana_rolemapping_etc.png,nolink);
#html(</div>)

#html(</div>)

Microsoft ID プラットフォームと暗黙的な許可のフロー (暗黙的な許可フローの有効有効化)
https://docs.microsoft.com/ja-jp/azure/active-directory/develop/v2-oauth2-implicit-grant-flow#send-the-sign-in-request
** ADグループ単位のロール割り当てについて [#cc9e34ff]
#html(<div class="pl10">)
Active Directory プランによっては、ADグループ単位でのロール割り当ては出来ない。
※AZURE AD PREMIUM P2 などが必要。
#html(<div class="ib" style="border:1px solid #333;">)
&ref(attach_role_etc1.png,nolink);
#html(</div>)
#html(</div>)

#mycode3(){{
ID トークンおよびアクセス トークンを正しく要求するには、Azure portal の [アプリの登録] ページのアプリ登録で、
 [暗黙の付与] セクションの [ID トークン] および [アクセス トークン] を選択して、対応する暗黙的な許可フローを有効にする必要があります。
それが有効でない場合は、unsupported_response エラーが返されます
}}
#html(</div>)


トップ   一覧 単語検索 最終更新   ヘルプ   最終更新のRSS