EcAuthDocs

EcAuth シークレット管理ガイド

1Password を中心としたシークレット管理運用。ローカル開発・CI・Terraform・Azure を横断する設計。

概要

EcAuth プロジェクトでは、環境変数とシークレットをハイブリッド構成で管理します。

  • ローカル開発環境: 1Password CLI (op inject)
  • GitHub Actions: 1Password Service Account + GitHub Secrets
  • Azure 環境: Azure Key Vault(Terraform 経由で 1Password から同期)

アーキテクチャ

Source of Truth
1Password Vault: EcAuth
*-dev-* 開発環境用
*-staging-* ステージング用
*-prod-* 本番用
Local
ローカル開発
op inject でテンプレから .env を生成
CI
GitHub Actions
1password/load-secrets-action でジョブ環境に注入
CI · IaC
GitHub Actions + Terraform
Provider 経由で Azure リソースに反映
Azure
Azure 統合
  • Storage tfstate 永続化
  • Key Vault 本番シークレット参照
  • App Service ランタイム環境変数

Service Account 構成

3つの Service Account トークンを使い分けています。

GitHub Secret 名 用途 アクセス可能アイテム
OP_SERVICE_ACCOUNT_TOKEN インフラ用(Terraform) ecauth-*, mockidp-*, skirnir-workload-identity/*
OP_SERVICE_ACCOUNT_TOKEN_NONPROD NonProd アプリ用 ecauth-dev-*, ecauth-staging-*, mockidp-*
OP_SERVICE_ACCOUNT_TOKEN_PROD 本番アプリ用 ecauth-prod-*

GitHub Secrets 設定

# インフラ用トークン(Terraform ワークフロー)
gh secret set OP_SERVICE_ACCOUNT_TOKEN \
  --org EcAuth \
  --visibility all \
  --body "<インフラ用トークン>"

# NonProd 用トークン(staging, MockIdP staging)
gh secret set OP_SERVICE_ACCOUNT_TOKEN_NONPROD \
  --org EcAuth \
  --visibility all \
  --body "<NonProd 用トークン>"

# Prod 用トークン(MockIdP production)
gh secret set OP_SERVICE_ACCOUNT_TOKEN_PROD \
  --org EcAuth \
  --visibility all \
  --body "<Prod 用トークン>"

# その他
gh secret set ORG_PAT --org EcAuth --visibility all --body "<GitHub PAT>"
gh secret set MOCK_IDP_BASE_URL --org EcAuth --visibility all --body "<MockIdP URL>"

1Password 保管庫構成

保管庫

保管庫名 用途 アクセス権限
EcAuth EcAuth プロジェクト全体 開発チーム全員
skirnir-workload-identity Azure 共通認証情報(Terraform のみ) インフラ担当のみ

アイテム命名規則

{プロジェクト}-{環境}-{サービス種別}

例:
  ecauth-dev-sql      → EcAuth / 開発環境 / SQL Database
  ecauth-prod-app     → EcAuth / 本番環境 / アプリ設定
  mockidp-staging     → MockIdP / ステージング / 設定

アイテム構成

保管庫: EcAuth
│
├── 【データベース】
│   ├── ecauth-dev-sql                    ← MockIdP 用 (Azure SQL Database)
│   │   ├── hostname: mock-openid-provider-xxx.database.windows.net
│   │   ├── database: MockIdpDb
│   │   ├── username: ****
│   │   └── password: ****
│   │
│   ├── ecauth-staging-sql                ← EcAuth IdentityProvider 用 (staging)
│   │   ├── hostname: ecauth-staging-xxx.database.windows.net
│   │   ├── database: EcAuthDb
│   │   ├── username: ****
│   │   └── password: ****
│   │
│   └── ecauth-prod-sql                   ← EcAuth IdentityProvider 用 (production)
│       └── (同様)
│
├── 【アプリケーション】
│   ├── ecauth-dev-app
│   │   ├── state_password: ****
│   │   ├── default_client_id: client_id
│   │   └── default_client_secret: ****
│   │
│   ├── ecauth-staging-app                ← セクション内に追加フィールドあり
│   │   ├── state_password: ****
│   │   ├── client_id: ****
│   │   ├── client_secret: ****
│   │   ├── organization_code: ****
│   │   ├── organization_name: ****
│   │   ├── organization_tenant_name: ****
│   │   ├── redirect_uri: ****
│   │   ├── app_name: ****
│   │   ├── web_app_suffix: ****
│   │   ├── b2b_user_subject: ****
│   │   ├── b2b_user_external_id: ****
│   │   ├── b2b_redirect_uri: ****
│   │   └── b2b_allowed_rp_ids: ****
│   │
│   └── ecauth-prod-app
│       ├── state_password: ****
│       ├── client_id: ****
│       ├── client_secret: ****
│       ├── organization_code: ****
│       ├── organization_name: ****
│       ├── organization_tenant_name: ****
│       ├── redirect_uri: ****
│       ├── app_name: ****
│       ├── web_app_suffix: ****
│       ├── b2b_user_subject: ****
│       ├── b2b_user_external_id: ****
│       ├── b2b_redirect_uri: ****
│       └── b2b_allowed_rp_ids: ****
│
├── 【MockIdP】
│   ├── mockidp-dev                       ← 開発環境用
│   │   ├── base_url: https://mock-openid-provider.xxx.azurecontainerapps.io
│   │   ├── authorization_endpoint: https://mock-openid-provider.xxx.azurecontainerapps.io/authorization
│   │   ├── token_endpoint: https://mock-openid-provider.xxx.azurecontainerapps.io/token
│   │   ├── userinfo_endpoint: https://mock-openid-provider.xxx.azurecontainerapps.io/userinfo
│   │   ├── default_client_id: mockclientid
│   │   ├── default_client_secret: ****
│   │   ├── default_user_email: defaultuser@example.com
│   │   ├── default_user_password: ****
│   │   ├── federate_client_id: federateclientid
│   │   └── federate_client_secret: ****
│   │
│   ├── mockidp-staging                   ← ステージング用
│   │   ├── base_url: ****
│   │   ├── authorization_endpoint: ****
│   │   ├── token_endpoint: ****
│   │   ├── userinfo_endpoint: ****
│   │   ├── default_client_id: ****
│   │   ├── default_client_secret: ****
│   │   ├── default_user_email: ****
│   │   ├── default_user_password: ****
│   │   └── redirect_uri: ****
│   │
│   └── mockidp-production                ← 本番用
│       ├── base_url: ****
│       ├── authorization_endpoint: ****
│       ├── token_endpoint: ****
│       ├── userinfo_endpoint: ****
│       ├── default_client_id: ****
│       ├── default_client_secret: ****
│       ├── default_user_email: ****
│       └── default_user_password: ****
│
├── 【MockIdP 連携】
│   ├── ecauth-staging-mockidp            ← EcAuth → MockIdP 連携設定(ステージング)
│   │   ├── app_name: ****
│   │   ├── client_id: ****
│   │   └── client_secret: ****
│   │
│   └── ecauth-prod-mockidp              ← EcAuth → MockIdP 連携設定(本番)
│       ├── app_name: ****
│       ├── client_id: ****
│       └── client_secret: ****
│
├── 【Workload Identity】
│   └── ecauth-workload-identity          ← Azure Workload Identity 認証情報
│       ├── AZURE_CLIENT_ID: ****
│       ├── AZURE_TENANT_ID: ****
│       └── AZURE_SUBSCRIPTION_ID: ****
│
├── 【インフラストラクチャ】
│   └── ecauth-infrastructure-sp          ← Azure Service Principal
│       └── AZURE_CLIENT_ID: ****
│
└── 【外部 IdP】(オプション)
    ├── google-oauth-dev
    │   ├── client_id: ****
    │   └── client_secret: ****
    │
    └── amazon-oauth-dev
        ├── client_id: ****
        └── client_secret: ****

保管庫: skirnir-workload-identity
│
└── azure-common-credentials              ← Azure 共通認証情報
    ├── AZURE_TENANT_ID: ****
    └── AZURE_SUBSCRIPTION_ID: ****

※ EcAuth ワークフロー(staging.yml, production.yml)では ecauth-workload-identity を使用。
  skirnir-workload-identity は Terraform ワークフロー(ecauth-infrastructure)のみで使用。

ローカル開発環境

1. 1Password CLI のインストール

# macOS
brew install 1password-cli

# Linux (Debian/Ubuntu)
curl -sS https://downloads.1password.com/linux/keys/1password.asc | \
  sudo gpg --dearmor --output /usr/share/keyrings/1password-archive-keyring.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/1password-archive-keyring.gpg] https://downloads.1password.com/linux/debian/$(dpkg --print-architecture) stable main" | \
  sudo tee /etc/apt/sources.list.d/1password.list
sudo apt update && sudo apt install 1password-cli

# Windows
winget install AgileBits.1Password.CLI

2. サインイン

# 初回サインイン
eval $(op signin)

# 生体認証を有効化している場合は自動的に認証

3. .env ファイルの生成

cd EcAuth

# 開発環境用の .env を生成
op inject -i .env.dev.tpl -o .env

# Docker Compose と組み合わせて使用
op run --env-file=.env.dev.tpl -- docker compose up -d

4. .env.tpl テンプレートファイル

開発環境用 (EcAuth/.env.dev.tpl)

EcAuth DB は Docker 内、MockIdP は Azure dev 環境を使用。

# =============================================================================
# Database Configuration (Docker 内)
# =============================================================================
DB_HOST=db
MIGRATION_DB_HOST=localhost
DB_NAME=EcAuthDb
DB_USER=SA
DB_PASSWORD='<YourStrong@Passw0rd>'
STATE_PASSWORD='<strong_password_string_of_at_least_32_characters>'

# =============================================================================
# Default Organization/Client Settings
# =============================================================================
DEFAULT_ORGANIZATION_CODE=example
DEFAULT_ORGANIZATION_NAME=example
DEFAULT_ORGANIZATION_TENANT_NAME=example
DEFAULT_ORGANIZATION_REDIRECT_URI=https://localhost:8081/v1/auth/callback
DEFAULT_CLIENT_ID=client_id
DEFAULT_CLIENT_SECRET=client_secret
DEFAULT_APP_NAME=app_name

# =============================================================================
# Azure MockIdP (dev organization) Settings
# =============================================================================
MOCK_IDP_BASE_URL=op://EcAuth/mockidp-dev/base_url

# dev organization の MockIdP クライアント設定
MOCK_IDP_DEFAULT_CLIENT_ID=op://EcAuth/mockidp-dev/default_client_id
MOCK_IDP_DEFAULT_CLIENT_SECRET=op://EcAuth/mockidp-dev/default_client_secret
MOCK_IDP_DEFAULT_CLIENT_NAME=MockClient
MOCK_IDP_DEFAULT_USER_EMAIL=op://EcAuth/mockidp-dev/default_user_email
MOCK_IDP_DEFAULT_USER_PASSWORD=op://EcAuth/mockidp-dev/default_user_password

# Federate クライアント設定(EcAuth -> Azure MockIdP 連携用)
MOCK_IDP_FEDERATE_CLIENT_ID=op://EcAuth/mockidp-dev/federate_client_id
MOCK_IDP_FEDERATE_CLIENT_SECRET=op://EcAuth/mockidp-dev/federate_client_secret
MOCK_IDP_FEDERATE_CLIENT_NAME=FederateClient
MOCK_IDP_FEDERATE_USER_EMAIL=federate@example.jp

# =============================================================================
# Federate OAuth2 Settings (EcAuth -> Azure MockIdP)
# =============================================================================
FEDERATE_OAUTH2_APP_NAME=federate-oauth2
FEDERATE_OAUTH2_CLIENT_ID=op://EcAuth/mockidp-dev/federate_client_id
FEDERATE_OAUTH2_CLIENT_SECRET=op://EcAuth/mockidp-dev/federate_client_secret
FEDERATE_OAUTH2_AUTHORIZATION_ENDPOINT=op://EcAuth/mockidp-dev/authorization_endpoint
FEDERATE_OAUTH2_TOKEN_ENDPOINT=op://EcAuth/mockidp-dev/token_endpoint
FEDERATE_OAUTH2_USERINFO_ENDPOINT=op://EcAuth/mockidp-dev/userinfo_endpoint

# =============================================================================
# Google OAuth2 Settings (オプション)
# =============================================================================
GOOGLE_OAUTH2_APP_NAME=google-oauth2
GOOGLE_OAUTH2_CLIENT_ID=<Google OAuth2 client_id>
GOOGLE_OAUTH2_CLIENT_SECRET=<Google OAuth2 client_secret>
GOOGLE_OAUTH2_DISCOVERY_URL=https://accounts.google.com/.well-known/openid-configuration

ステージング環境用 (EcAuth/.env.staging.tpl)

EcAuth DB と MockIdP の両方が Azure 環境。

# =============================================================================
# Database Configuration (Azure SQL Database)
# =============================================================================
SQL_HOST=op://EcAuth/ecauth-staging-sql/hostname
SQL_DATABASE=op://EcAuth/ecauth-staging-sql/database
SQL_USERNAME=op://EcAuth/ecauth-staging-sql/username
SQL_PASSWORD=op://EcAuth/ecauth-staging-sql/password

# =============================================================================
# Staging Organization/Client Settings
# =============================================================================
STAGING_ORGANIZATION_CODE=op://EcAuth/ecauth-staging-app/organization_code
STAGING_ORGANIZATION_NAME=op://EcAuth/ecauth-staging-app/organization_name
STAGING_ORGANIZATION_TENANT_NAME=op://EcAuth/ecauth-staging-app/organization_tenant_name
STAGING_CLIENT_ID=op://EcAuth/ecauth-staging-app/client_id
STAGING_CLIENT_SECRET=op://EcAuth/ecauth-staging-app/client_secret
STAGING_APP_NAME=op://EcAuth/ecauth-staging-app/app_name
STAGING_REDIRECT_URI=op://EcAuth/ecauth-staging-app/redirect_uri

# =============================================================================
# Staging MockIdP Settings (Azure MockIdP との連携)
# =============================================================================
STAGING_MOCK_IDP_APP_NAME=op://EcAuth/ecauth-staging-mockidp/app_name
STAGING_MOCK_IDP_CLIENT_ID=op://EcAuth/ecauth-staging-mockidp/client_id
STAGING_MOCK_IDP_CLIENT_SECRET=op://EcAuth/ecauth-staging-mockidp/client_secret

# MockIdP エンドポイント(Azure Container Apps)
MOCK_IDP_BASE_URL=op://EcAuth/mockidp-staging/base_url
STAGING_MOCK_IDP_AUTHORIZATION_ENDPOINT=op://EcAuth/mockidp-staging/authorization_endpoint
STAGING_MOCK_IDP_TOKEN_ENDPOINT=op://EcAuth/mockidp-staging/token_endpoint
STAGING_MOCK_IDP_USERINFO_ENDPOINT=op://EcAuth/mockidp-staging/userinfo_endpoint

GitHub Actions

シークレット管理方針

シークレット 保存先 理由
ORG_PAT GitHub Secrets GitHub → GitHub 認証は GitHub 内で完結
OP_SERVICE_ACCOUNT_TOKEN* GitHub Secrets 1Password 連携に必須
MOCK_IDP_BASE_URL GitHub Secrets playwright.yml で使用(URL 情報)
その他すべて 1Password 一元管理のため

現在のワークフロー構成

ワークフロー リポジトリ 1Password トークン Azure 認証 備考
dotnet_tests.yml EcAuth なし なし ローカルテストのみ
playwright.yml EcAuth なし なし MOCK_IDP_BASE_URL は GitHub Secrets
staging.yml EcAuth _NONPROD 1Password 経由 migrate + build + deploy + verify
production.yml EcAuth _PROD 1Password 経由 migrate + build + deploy + verify(手動実行のみ)
terraform.yml ecauth-infrastructure OP_SERVICE_ACCOUNT_TOKEN 1Password 経由 Azure 認証情報も 1Password
deploy.yml EcAuth.MockIdP なし GitHub Secrets Workload Identity
migrate.yml EcAuth.MockIdP _NONPROD / _PROD なし 環境別トークン

ワークフロー例

基本的なワークフロー(1Password + Workload Identity)

# .github/workflows/staging.yml(deploy ジョブ抜粋)
name: Staging Deploy & Migrate

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    environment: staging

    permissions:
      id-token: write
      contents: read

    steps:
      - name: Load secrets from 1Password
        uses: 1password/load-secrets-action@v3
        with:
          export-env: true
        env:
          OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN_NONPROD }}
          AZURE_CLIENT_ID: op://EcAuth/ecauth-workload-identity/AZURE_CLIENT_ID
          AZURE_TENANT_ID: op://EcAuth/ecauth-workload-identity/AZURE_TENANT_ID
          AZURE_SUBSCRIPTION_ID: op://EcAuth/ecauth-workload-identity/AZURE_SUBSCRIPTION_ID
          WEB_APP_SUFFIX: op://EcAuth/ecauth-staging-app/web_app_suffix

      - name: Azure Login
        uses: azure/login@v2
        with:
          client-id: ${{ env.AZURE_CLIENT_ID }}
          tenant-id: ${{ env.AZURE_TENANT_ID }}
          subscription-id: ${{ env.AZURE_SUBSCRIPTION_ID }}

      - name: Deploy to Azure Web App
        uses: azure/webapps-deploy@v3
        with:
          app-name: ecauth-staging-${{ env.WEB_APP_SUFFIX }}
          package: ./publish

Terraform ワークフロー(1Password から Azure 認証情報を取得)

# ecauth-infrastructure/.github/workflows/terraform.yml
name: Terraform

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

permissions:
  id-token: write
  contents: read
  pull-requests: write

jobs:
  plan-staging:
    runs-on: ubuntu-latest
    environment: production

    steps:
      - uses: actions/checkout@v4

      - name: Load secrets from 1Password
        uses: 1password/load-secrets-action@v2
        with:
          export-env: true
        env:
          OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }}
          AZURE_TENANT_ID: op://skirnir-workload-identity/azure-common-credentials/AZURE_TENANT_ID
          AZURE_SUBSCRIPTION_ID: op://skirnir-workload-identity/azure-common-credentials/AZURE_SUBSCRIPTION_ID
          AZURE_CLIENT_ID: op://EcAuth/ecauth-infrastructure-sp/AZURE_CLIENT_ID

      - name: Azure Login
        uses: azure/login@v2
        with:
          client-id: ${{ env.AZURE_CLIENT_ID }}
          tenant-id: ${{ env.AZURE_TENANT_ID }}
          subscription-id: ${{ env.AZURE_SUBSCRIPTION_ID }}

      - name: Setup Terraform
        uses: hashicorp/setup-terraform@v3
        with:
          terraform_version: "~1.7"

      - name: Terraform Init
        working-directory: environments/staging
        run: terraform init
        env:
          ARM_USE_OIDC: true
          ARM_CLIENT_ID: ${{ env.AZURE_CLIENT_ID }}
          ARM_TENANT_ID: ${{ env.AZURE_TENANT_ID }}
          ARM_SUBSCRIPTION_ID: ${{ env.AZURE_SUBSCRIPTION_ID }}

      - name: Terraform Plan
        working-directory: environments/staging
        run: terraform plan -no-color
        env:
          ARM_USE_OIDC: true
          ARM_CLIENT_ID: ${{ env.AZURE_CLIENT_ID }}
          ARM_TENANT_ID: ${{ env.AZURE_TENANT_ID }}
          ARM_SUBSCRIPTION_ID: ${{ env.AZURE_SUBSCRIPTION_ID }}
          TF_VAR_op_service_account_token: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }}

Terraform 連携

Azure Storage Backend

Terraform の状態管理は Azure Storage Backend を使用します。

Backend 設定

environments/staging/terraform.tf:

terraform {
  required_version = ">= 1.0"

  required_providers {
    azurerm = {
      source  = "hashicorp/azurerm"
      version = "~> 4.0"
    }
    azuread = {
      source  = "hashicorp/azuread"
      version = "~> 3.0"
    }
    onepassword = {
      source  = "1Password/onepassword"
      version = "~> 3.0"
    }
  }

  # Azure Storage Backend
  backend "azurerm" {
    resource_group_name  = "ec-auth"
    storage_account_name = "stateecauthinfra"
    container_name       = "tfstate"
    key                  = "staging.tfstate"
    use_oidc             = true
    use_azuread_auth     = true
  }
}

provider "azurerm" {
  features {
    resource_group {
      prevent_deletion_if_contains_resources = false
    }
  }
  use_oidc = true
}

provider "azuread" {
  use_oidc = true
}

provider "onepassword" {
  service_account_token = var.op_service_account_token
}

1Password Terraform Provider

Terraform から 1Password のシークレットを直接参照できます。

Data Source でシークレット取得

environments/staging/main.tf:

# 1Password からシークレット取得
data "onepassword_item" "staging_sql" {
  # vault は UUID で指定する必要がある(v3.0 要件)
  # UUID は `op vault get "EcAuth" --format json | jq -r '.id'` で取得
  vault = "vdddxjhqbqi4rbapcr656fa5vu"
  title = "ecauth-staging-sql"
}

data "onepassword_item" "staging_app" {
  vault = "vdddxjhqbqi4rbapcr656fa5vu"
  title = "ecauth-staging-app"
}

locals {
  # v3.0 では DATABASE カテゴリの標準属性から直接取得可能
  sql_username = data.onepassword_item.staging_sql.username
  sql_password = data.onepassword_item.staging_sql.password
  sql_host     = data.onepassword_item.staging_sql.hostname
  sql_database = data.onepassword_item.staging_sql.database

  # セクション内のカスタムフィールドから取得
  web_app_suffix           = [for f in data.onepassword_item.staging_app.section[0].field : f.value if f.label == "web_app_suffix"][0]
  state_password           = [for f in data.onepassword_item.staging_app.section[0].field : f.value if f.label == "state_password"][0]
  organization_tenant_name = [for f in data.onepassword_item.staging_app.section[0].field : f.value if f.label == "organization_tenant_name"][0]
  redirect_uri             = [for f in data.onepassword_item.staging_app.section[0].field : f.value if f.label == "redirect_uri"][0]

  sql_connection_string = "Server=tcp:${local.sql_host},1433;Initial Catalog=${local.sql_database};Persist Security Info=False;User ID=${local.sql_username};Password=${local.sql_password};MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;"
}
注意

v3.0 では 1Password SDK を使用するため、GitHub Actions 環境でも op CLI のインストールは不要です。

Key Vault にシークレット保存

module "key_vault" {
  source = "../../modules/key-vault"

  # ... 既存の設定 ...

  # 1Password から取得した SQL 接続文字列と state-password を追加
  secret_names = concat(var.key_vault_secret_names, ["sql-connection-string", "state-password"])
  secret_values = merge(var.key_vault_secret_values, {
    "sql-connection-string" = local.sql_connection_string
    "state-password"        = local.state_password
  })
}

App Service から Key Vault 参照

module "web_app_identityprovider" {
  source = "../../modules/web-app"

  # Key Vault 参照(Azure Portal で平文表示されない)
  connection_string_name = "EcAuthDbContext"
  connection_string      = "@Microsoft.KeyVault(VaultName=${var.key_vault_name};SecretName=sql-connection-string)"

  app_settings = {
    "STATE_PASSWORD" = "@Microsoft.KeyVault(VaultName=${var.key_vault_name};SecretName=state-password)"
  }
}

データフロー

┌─────────────────────────────────────────────────────────────┐
│                 1Password (単一の情報源)                     │
│                 ecauth-staging-sql                          │
└─────────────────────────────────────────────────────────────┘
           │
           │ 1Password Terraform Provider (terraform apply 時)
           ▼
┌─────────────────────────────────────────────────────────────┐
│               GitHub Actions + Terraform                     │
│               (シークレットはログでマスク)                    │
└─────────────────────────────────────────────────────────────┘
           │
           ▼
┌─────────────────────────────────────────────────────────────┐
│                    Azure Key Vault                          │
│                    sql-connection-string                    │
└─────────────────────────────────────────────────────────────┘
           │
           │ @Microsoft.KeyVault(...) 参照
           ▼
┌─────────────────────────────────────────────────────────────┐
│                    Azure App Service                        │
│                    (起動時にキャッシュ、Portal で非表示)     │
└─────────────────────────────────────────────────────────────┘

Azure Key Vault(本番環境)

Key Vault 参照

App Service から Key Vault を参照する場合、起動時にキャッシュされるため、毎リクエストで Key Vault にアクセスしません。

タイミング Key Vault アクセス
App Service 起動/再起動 あり
設定変更時 あり
定期リフレッシュ 24時間ごと
各 HTTP リクエスト なし(キャッシュ使用)
// Program.cs
if (builder.Environment.IsProduction())
{
    var keyVaultName = builder.Configuration["KeyVaultName"];
    var keyVaultUri = new Uri($"https://{keyVaultName}.vault.azure.net/");

    builder.Configuration.AddAzureKeyVault(
        keyVaultUri,
        new DefaultAzureCredential());
}

セキュリティベストプラクティス

1. アクセス権限の最小化

  • 開発者には開発環境のシークレットのみアクセス権限を付与
  • 本番環境のシークレットは限定されたメンバーのみ

2. シークレットのローテーション

# 定期的なパスワード更新
# 1. 1Password でシークレットを更新
# 2. Terraform apply を実行して Key Vault に同期
# 3. Azure App Service を再起動

3. 監査ログ

  • 1Password: アクティビティログで誰がいつアクセスしたか確認可能
  • Azure Key Vault: 診断ログを有効化してアクセス監査

4. .env ファイルの取り扱い

# .gitignore
.env
.env.local
.env.*.local

# テンプレートファイルはコミット可
!.env.*.tpl

トラブルシューティング

1Password CLI の認証エラー

# セッションの再認証
eval $(op signin)

# アカウント情報の確認
op account list

シークレット参照エラー

# シークレットの存在確認
op item get "ecauth-dev-sql" --vault EcAuth

# フィールド一覧の確認
op item get "ecauth-dev-sql" --vault EcAuth --format json | jq '.fields[].label'

GitHub Actions での 1Password エラー

# デバッグ用: シークレットの読み込み確認
- name: Verify 1Password connection
  uses: 1password/load-secrets-action@v2
  with:
    export-env: false  # 環境変数にエクスポートしない
  env:
    OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }}
    TEST_SECRET: op://EcAuth/ecauth-dev-sql/database

App Service の Key Vault 参照エラー(SecretNotFound)

Azure App Service で Key Vault 参照を使用している場合、SecretNotFound エラーが発生することがあります。

症状:

コンテナー名: ecauth-staging-kv
シークレットの名前: sql-connection-string
SecretNotFound
Key Vault reference was not able to be resolved because Key Vault reference contains invalid Key Vault secret name that can't be found.

原因:

  • Terraform apply 時に App Service の設定更新が Key Vault シークレット作成より先に実行された
  • App Service が Key Vault 参照を解決しようとした時点でシークレットがまだ存在しなかった
  • Key Vault 参照のキャッシュが古い状態のまま残っている

確認方法:

# Key Vault シークレットの存在確認
az keyvault secret list --vault-name ecauth-staging-kv -o table

# Key Vault 参照のステータス確認
az rest --method get --uri "https://management.azure.com/subscriptions/<subscription-id>/resourceGroups/<resource-group>/providers/Microsoft.Web/sites/<app-name>/config/configreferences/connectionstrings?api-version=2022-03-01"

解決方法:

App Service の接続文字列を再設定して Key Vault 参照を強制的に再解決させます:

# 接続文字列を再設定(同じ値でも再解決される)
az webapp config connection-string set \
  --name <app-name> \
  --resource-group <resource-group> \
  --connection-string-type SQLAzure \
  --settings "EcAuthDbContext=@Microsoft.KeyVault(VaultName=<vault-name>;SecretName=sql-connection-string)"

# ステータス確認(Resolved になれば成功)
az rest --method get --uri "https://management.azure.com/subscriptions/<subscription-id>/resourceGroups/<resource-group>/providers/Microsoft.Web/sites/<app-name>/config/configreferences/connectionstrings?api-version=2022-03-01"

予防策:

Terraform で depends_on を設定して、Key Vault シークレット作成後に App Service を更新するようにします:

module "web_app" {
  # ...
  depends_on = [module.key_vault]
}

移行手順

既存の .env から 1Password への移行

  1. 1Password にアイテムを作成
  2. 既存の .env から値をコピー
  3. .env.tpl テンプレートを作成
  4. op inject で動作確認
  5. 既存の .env を削除(または .gitignore に追加済みか確認)
# 移行確認コマンド
op inject -i .env.dev.tpl -o .env.test
diff .env .env.test
rm .env.test

実装チェックリスト

Phase 1: 1Password Service Account 設定

    1. 1Password.com → Settings → Service Accounts
    2. New Service Account を作成
    3. Vault Access: 必要な保管庫に Read Items 権限
    4. Create Token でトークン生成
  • export OP_SERVICE_ACCOUNT_TOKEN="<トークン>"
    op read "op://EcAuth/ecauth-staging-sql/username"
    op read "op://EcAuth/ecauth-staging-sql/password"
    op read "op://EcAuth/ecauth-staging-sql/hostname"
    op read "op://EcAuth/ecauth-staging-sql/database"
  • gh secret set OP_SERVICE_ACCOUNT_TOKEN \
      --org EcAuth \
      --visibility all \
      --body "<トークン>"

Phase 2: Azure Workload Identity 設定

    1. Azure Portal → Azure Active Directory → App registrations
    2. New registration を作成
    3. Federated credentials を設定(GitHub Actions 用)
    • AZURE_CLIENT_ID, AZURE_TENANT_ID, AZURE_SUBSCRIPTION_ID を保存
    • ワークフローから op://EcAuth/ecauth-workload-identity/* で参照

Phase 3: Terraform 構成

    • terraform.tfonepassword provider 追加(version = "~> 3.0"
    • service_account_token = var.op_service_account_token を設定
    • variables.tfop_service_account_token 変数追加
    • main.tfdata "onepassword_item" 追加
    • vault は UUID で指定
  • terraform init -upgrade
    terraform plan
    terraform apply

Phase 4: 検証

  • curl https://ecauth-staging-xxx.azurewebsites.net/healthz
    • App Service → 構成 → 接続文字列 → @Microsoft.KeyVault(...) と表示

補足: EcAuth.MockIdP の 1Password 設定

MockIdP (EcAuth.MockIdP/migrate.yml) は環境別に異なるトークンを使用します:

# staging 環境: 1Password からシークレットを取得
- name: Load secrets from 1Password (staging)
  if: github.event.inputs.environment == 'staging'
  uses: 1password/load-secrets-action@v3
  with:
    export-env: true
  env:
    OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN_NONPROD }}
    SQL_HOST: op://EcAuth/ecauth-dev-sql/hostname
    SQL_DATABASE: op://EcAuth/ecauth-dev-sql/database
    SQL_USERNAME: op://EcAuth/ecauth-dev-sql/username
    SQL_PASSWORD: op://EcAuth/ecauth-dev-sql/password

# production 環境: Prod トークンを使用
- name: Load secrets from 1Password (production)
  if: github.event.inputs.environment == 'production'
  uses: 1password/load-secrets-action@v3
  with:
    export-env: true
  env:
    OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN_PROD }}
    SQL_HOST: op://EcAuth/ecauth-prod-sql/hostname
    SQL_DATABASE: op://EcAuth/ecauth-prod-sql/database
    SQL_USERNAME: op://EcAuth/ecauth-prod-sql/username
    SQL_PASSWORD: op://EcAuth/ecauth-prod-sql/password

参考リンク