[アップデート]AMG(Managed Grafana)がGrafana APIトークンを作成するAPIをサポートしてIaCがやりやすくなりました

2022.06.07

今回はAMG(Amazon Managed Grafana)がGrafana APIトークンを作成するAPIをサポートしました、というアップデートをご紹介します。

何が嬉しいの?

一言でいうと、Grafanaワークスペースコンソール内のGrafanaリソース(データソース、ダッシュボード、フォルダーなど)のIaC化がやりやすくなりました。

例えばTerraformで上記のIaC化を行なうとします。Grafana Providerが必要です。

プロビジョニング先のGrafana Server(AMGだとworkspaceと呼ぶ)を指定するためにurlattributeを使い、また認証のためにauthattributeにAPIトークンを設定します。

provider "grafana" {
  url  = "http://grafana.example.com/"
  auth = var.grafana_auth
}

アップデート以前

上記urlについては、AMGの場合、aws_grafana_workspaceを使ってworkspaceを作成し、そのreference attributeのendpointを参照すればOKです。

一方、authに設定すべきAPIトークンは、一度Grafanaワークスペースコンソールにログインして、その中で作成する必要があります。 なのでコードだけで完結できないんですよね。ここが問題です。

※ 以下は、AMGのワークスペース作成時に認証アクセスでAWS SSOを選んだ場合のAPIトークン作成のフローです。SAMLを選んでいた場合については私が把握できてないので今回は割愛させていただきますが、大体同じなのではと思います。

  1. ワークスペースの詳細ページに行き、AMG アプリケーションにログインできるユーザー(グループ)を設定します。 configure-user-and-group
  2. 設定したユーザー(グループ)のタイプを「管理者(Admin)」に変更します。 grafana-admin
  3. ログインできるようにしたユーザーでSSOコンソールに行くと、AMGアプリケーションのパネルが表示されるので、クリックしてログインします。
  4. 以下画像のようにConfiguration→API Keysに遷移します。 grafana-api-key
  5. API Keyを作成します。RoleはAdminに設定します。 new-grafana-api-key-02 grafana-api-key-03
  6. API Keyが作成されます。これをコピペしてauthattributeに設定します。 grafana-api-key-04 VCSにKey値が残るのが嫌なので、Parameter Store等を経由するのがいいでしょう。
resource "aws_grafana_workspace" "sample" {
  account_access_type      = "CURRENT_ACCOUNT"
  authentication_providers = ["AWS_SSO"]
  permission_type          = "SERVICE_MANAGED"

  name         = "sample"
  description  = "sample"
  role_arn     = aws_iam_role.workspace.arn
  data_sources = ["PROMETHEUS"]
}

data "aws_ssm_parameter" "grafana_api_key" {
  name = "sample-amg-api-key"
}

provider "grafana" {
  url  = "https://${aws_grafana_workspace.sample.endpoint}"
  auth = data.aws_ssm_parameter.grafana_api_key.value
}

これでGrafanaリソースのプロビジョニングができるようになります。以下はAMP(Amazon Managed Service for Prometheus)をデータソースとして設定している例です。

resource "aws_prometheus_workspace" "sample" {
  alias = "sample"
}

data "aws_region" "current" {}

resource "grafana_data_source" "prometheus" {
  type       = "prometheus"
  name       = "amp"
  is_default = true
  url        = aws_prometheus_workspace.sample.prometheus_endpoint
  json_data {
    http_method     = "POST"
    sigv4_auth      = true
    sigv4_auth_type = "workspace-iam-role"
    sigv4_region    = data.aws_region.current.name
  }
}

先程問題点として、「Grafana providerの設定に必要なAPIトークンを取得するためには、一度Grafanaワークスペースコンソールにログインして、その中で作成する必要があること」を挙げました。「まあ、最初一回ブラウザポチポチ作業が必要なだけでしょ?そんなに問題じゃなくないか?」と思われるかもしれません。そうじゃないんです。このAPIトークン、最大でも30日で期限切れするんです。 期限切れした場合、terraform planやapplyで以下のようなエラーになります。

Error: status: 401, body: {"message":"Expired API key"}

というわけで、30日ごとにコンソールにログインして、API Keyを作成し直す という作業が発生します。これが地味に辛い。

アップデート後

今回のアップデートで、このトークンをコンソール内で作成する以外に、AWSのAPIでも作成することができるようになりました。なのでもうコンソールログインが不要になります🙌

現在のところ、Terraform AWS providerではこのアップデートのサポートは無いようです。(issueはあったのでそのうちされそう)

AWS CLIは対応済なので、これをうまくTerraformの世界に取り込みましょう。

やってみた

AWS CLI アップデート

まずは、追加された前述のコマンドaws grafana create-workspace-api-keyが使えるように、AWS CLIをアップデートします。

% aws --version
aws-cli/2.4.22 Python/3.8.8 Darwin/20.6.0 exe/x86_64 prompt/off
% curl "https://awscli.amazonaws.com/AWSCLIV2.pkg" -o "AWSCLIV2.pkg"
sudo installer -pkg AWSCLIV2.pkg -target /
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 27.6M  100 27.6M    0     0  30.3M      0 --:--:-- --:--:-- --:--:-- 30.2M
Password:
installer: Package name is AWS Command Line Interface
installer: Upgrading at base path /
installer: The upgrade was successful.
% aws --version                                                     
aws-cli/2.7.6 Python/3.9.11 Darwin/20.6.0 exe/x86_64 prompt/off

providerをインストール

External Data Sourceを使って、aws grafana create-workspace-api-keyの結果をTerraformの世界に取り込もうと思います。ですので、まずはexternal providerを追加します。

 terraform {
   required_version = "~> 1.2.0"
  
   required_providers {
     aws = {
       source = "hashicorp/aws"
         version = "~> 4.9"
     }
     grafana = {
       source = "grafana/grafana"
       version = "1.23.0"
     }    
+     external = {
+       source = "hashicorp/external"
+       version = "2.2.2"
+     }
 }

この後忘れず terraform initしましょう。

失敗

以下のようにExternal Data Sourceを使いました。が、エラーになりました。残念。

data "external" "create_amg_workspace_api_key" {
  program = [
    "aws",
    "grafana",
    "create-workspace-api-key",
    "--key-name", "for-terraform-${formatdate("YYMMDDhhmmss",timestamp())}",
    "--key-role", "ADMIN",
    "--seconds-to-live", "600",
    "--workspace-id", split(".",aws_grafana_workspace.sample.endpoint)[0],
  ]
}

provider "grafana" {
  url  = "https://${aws_grafana_workspace.sample.endpoint}"
  auth = data.external.create_amg_workspace_api_key.result.key
}

apply(plan)の結果

╷
│ Error: status: 401, body: {"message":"Unauthorized"}
│ 
│ 
│   with grafana_data_source.prometheus,
│   on grafana.tf line 25, in resource "grafana_data_source" "prometheus":
│   25: resource "grafana_data_source" "prometheus" {
│ 
╵

ポイントはkey-nameの部分をtimestamp関数を使って動的にしている点です。このaws grafana create-workspace-api-keyコマンドを使うとその都度新しいAPI Keyが作成されます。すでに存在しているkey名を指定してしまうと以下のエラーになります。

│ An error occurred (ConflictException) when calling the CreateWorkspaceApiKey operation: Api key with
│ name hoge already exists.

というわけでtimestamp関数を使って、key-nameが既存のものと重複しないようにしています。

なのですが、どうやらこのtimestamp関数をExternal Data Source内で使うと先程の401エラーになってしまうようです… 解決方法がわからないのでこの方法は諦めました。

修正→成功

方針変更して、

  1. Terraform外部でkey-nameを定義
  2. 変数として1をTerraformに取り込む

というやり方をやってみました。

variable "api_key_suffix" {
    type        = string
    description = "suffix of amg workspace api key name"
}

data "external" "create_amg_workspace_api_key" {
  program = [
    "aws",
    "grafana",
    "create-workspace-api-key",
    "--key-name", "for-terraform-${var.api_key_suffix}",
    "--key-role", "ADMIN",
    "--seconds-to-live", "600",
    "--workspace-id", split(".",aws_grafana_workspace.sample.endpoint)[0],
  ]
}

provider "grafana" {
  url  = "https://${aws_grafana_workspace.sample.endpoint}"
  auth = data.external.create_amg_workspace_api_key.result.key
}

そしてplanやapplyの際に変数を指定します。

% terraform apply -var "api_key_suffix=$(date '+%y%m%d%k%M%S')"

毎回このコマンドを叩くのは面倒なのでスクリプト化するのがよいでしょう。

改良

前項の方法で動作するようにはなったのですが、ちょっとイマイチです。

  • 変数の指定が面倒(スクリプト化してしまえばOKですが、できればしたくない)
  • planやapplyの度にAPI keyが増えていく many-keys

なんとかならないものかなーと考えていたところ、現在お世話になっているお客様👨‍💼に教えていただきました。

実装済みのコードも共有いただきました。要はdelete-workspace-api-keyで既存のkeyを削除してから、同じ名前でkeyを作り直すやり方です。

./bash/create-workspace-api-key.sh

set -eu

eval "$(jq -r '@sh "WORKSPACE_ID=\(.workspace_id)"')"

aws grafana delete-workspace-api-key --key-name terraform_apikey --workspace-id ${WORKSPACE_ID} > /dev/null || true

API_KEY=$(aws grafana create-workspace-api-key --key-name terraform_apikey --key-role ADMIN --seconds-to-live 86400 --workspace-id ${WORKSPACE_ID} --query key --output text)

echo "{\"apikey\": \"${API_KEY}\"}" 

exit 0
data "external" "grafana_apikey" {
  program = ["bash", "${path.module}/bash/create-workspace-api-key.sh"]

  query = {
    workspace_id = split(".",aws_grafana_workspace.sample.endpoint)[0]
  }
}

provider "grafana" {
  url  = "https://${aws_grafana_workspace.sample.endpoint}"
  auth = data.external.grafana_apikey.result.apikey
}

この方法なら先程挙げたイマイチな点2つ両方とも解消できていますね!

まとめ

AMGが、Grafana APIトークンを作成するAPIをサポートしましたというアップデートのご紹介でした。AMGとTerraform両方併せて使われている方にはかなり嬉しいアップデートではないでしょうか。ぜひ導入をご検討ください。