EcAuth シークレット管理ガイド
1Password を中心としたシークレット管理運用。ローカル開発・CI・Terraform・Azure を横断する設計。
概要
EcAuth プロジェクトでは、環境変数とシークレットをハイブリッド構成で管理します。
- ローカル開発環境: 1Password CLI
(
op run) - GitHub Actions: 1Password Service Account + GitHub Secrets
- Azure 環境: Azure Key Vault(Terraform 経由で 1Password から同期)
アーキテクチャ
EcAuth
*-dev-*
開発環境用
*-staging-*
ステージング用
*-prod-*
本番用
op run でテンプレからサブプロセス env に注入(平文 .env なし)
1password/load-secrets-action でジョブ環境に注入
- Storage tfstate 永続化
- Key Vault 本番シークレット参照
- App Service ランタイム環境変数
Service Account 構成
Service Account トークンは読み取り用の 1 つだけです。
| GitHub Secret 名 | Service Account | 権限 | 用途 |
|---|---|---|---|
OP_SERVICE_ACCOUNT_TOKEN |
ecauth-ci |
EcAuth / skirnir-workload-identity /
EcAuth-terraform(現在は空・未使用)の 読み取り |
全ワークフロー共通(アプリ・Terraform とも) |
1Password の Service Account は作成後に保管庫の追加も権限変更もできません。
参照する保管庫を増やすときは Service Account ごと作り直し、GitHub Secrets を配り直します。
書き込み権限が必要になった場合も EcAuth 保管庫全体に及ぶ構成は採らず、
用途を絞った独立の Service Account を用意します。
GitHub Secrets 設定
# 読み取り用トークン(public リポジトリには organization secret で届く)
gh secret set OP_SERVICE_ACCOUNT_TOKEN \
--org EcAuth \
--visibility selected \
--repos EcAuth,ec-cube2-ecauth,ec-cube4-ecauth,ecauth-auth-js,ecauth-website \
--body "<ecauth-ci のトークン>"
# GitHub Free の org では organization secret が private リポジトリに渡らないため、
# private リポジトリには同名の secret を個別に設定する(重複ではなく必然)
gh secret set OP_SERVICE_ACCOUNT_TOKEN --repo EcAuth/EcAuthDocs \
--body "<ecauth-ci のトークン>"
gh secret set OP_SERVICE_ACCOUNT_TOKEN --repo EcAuth/ecauth-infrastructure --env production \
--body "<ecauth-ci のトークン>"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.CLI2. サインイン
# 初回サインイン
eval $(op signin)
# 生体認証を有効化している場合は自動的に認証3. op run でのシークレット注入
平文 .env を生成せず、op run
でシークレットをサブプロセスの環境変数としてのみ注入する。
cd EcAuth
# 開発環境: Docker Compose をシークレット注入付きで起動
op run --env-file=.env.dev.tpl -- docker compose -p ec-auth up -d --build
# 補間結果の確認(コンテナを起動せず compose の最終設定をレンダリング)
op run --env-file=.env.dev.tpl -- docker compose -p ec-auth config4. .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 エンドポイント(Cloudflare Workers)
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_endpointGitHub Actions
シークレット管理方針
| シークレット | 保存先 | 理由 |
|---|---|---|
OP_SERVICE_ACCOUNT_TOKEN |
GitHub Secrets | 1Password 連携の起点。これだけは 1Password に置けない |
PACKAGES_READ_TOKEN |
GitHub Dependabot Secrets | dependabot.yml は静的な ${{secrets.X}} しか解決できず、
トークンを発行する step を挟めない。加えて GitHub Packages は fine-grained PAT
非対応のため classic PAT(read:packages のみ)を使う |
| GitHub App の秘密鍵 | 1Password | 実行時に load-secrets-action で読み、create-github-app-token
に渡す。export-env は false にしてステップ出力で渡す |
| その他すべて | 1Password | 一元管理のため |
現在のワークフロー構成
| ワークフロー | リポジトリ | 1Password トークン | Azure 認証 | 備考 |
|---|---|---|---|---|
main.yml |
EcAuth | — | — | オーケストレーター。push / pull_request をここだけで受け、各スイートを reusable workflow として呼ぶ |
dotnet_tests.yml |
EcAuth | なし | なし | ユニットテスト + 冪等マイグレーション検証。NuGet 認証は GITHUB_TOKEN |
playwright.yml |
EcAuth | OP_SERVICE_ACCOUNT_TOKEN |
なし | MOCK_IDP_BASE_URL を 1Password から取得。Dependabot 起動の run では skip |
selenium.yml |
EcAuth | OP_SERVICE_ACCOUNT_TOKEN |
なし | 同上 |
eccube_plugin_e2e.yml |
EcAuth | OP_SERVICE_ACCOUNT_TOKEN |
なし | 1Password の step は install_source == 'package-api' のときだけ動く |
staging.yml |
EcAuth | OP_SERVICE_ACCOUNT_TOKEN |
1Password 経由 | migrate + build + deploy + verify |
production.yml |
EcAuth | OP_SERVICE_ACCOUNT_TOKEN |
1Password 経由 | 同上(workflow_dispatch のみ) |
claude.yml / claude-code-review.yml |
EcAuth | OP_SERVICE_ACCOUNT_TOKEN |
なし | GitHub App EcAuth Docs Reader の秘密鍵を 1Password から読み、installation token で EcAuthDocs を checkout |
terraform.yml |
ecauth-infrastructure | OP_SERVICE_ACCOUNT_TOKEN |
1Password 経由 | apply 後の verify は GitHub App EcAuth Workflow Dispatcher で EcAuth のデプロイワークフローを起動する |
deploy.yml |
EcAuth.MockIdP | なし | なし | Cloudflare Workers へデプロイ。CLOUDFLARE_API_TOKEN / CLOUDFLARE_ACCOUNT_ID を使う |
ワークフロー例
基本的なワークフロー(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 }}
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: ./publishTerraform ワークフロー(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/databaseApp 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 への移行
- 1Password にアイテムを作成
- 既存の .env から値をコピー
- .env.tpl テンプレートを作成
op runで動作確認- 既存の .env を削除(または .gitignore に追加済みか確認)
# 移行確認コマンド(テンプレートが正しく解決されるか、平文を残さず検証)
op run --env-file=.env.dev.tpl -- docker compose -p ec-auth config実装チェックリスト
Phase 1: 1Password Service Account 設定
-
- 1Password.com → Settings → Service Accounts
- New Service Account を作成
- Vault Access: 必要な保管庫に Read Items 権限
- 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 設定
-
- Azure Portal → Azure Active Directory → App registrations
- New registration を作成
- Federated credentials を設定(GitHub Actions 用)
-
AZURE_CLIENT_ID,AZURE_TENANT_ID,AZURE_SUBSCRIPTION_IDを保存- ワークフローから
op://EcAuth/ecauth-workload-identity/*で参照
Phase 3: Terraform 構成
-
terraform.tfにonepasswordprovider 追加(version = "~> 3.0")service_account_token = var.op_service_account_tokenを設定variables.tfにop_service_account_token変数追加
-
main.tfにdata "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(...)と表示
- App Service → 構成 → 接続文字列 →
補足: EcAuth.MockIdP の構成
MockIdP は Cloudflare Workers + TypeScript で動いており、SQL Database を持たず
1Password も参照しません。デプロイは deploy.yml が Cloudflare の
API トークンだけで行います。
| 項目 | 構成 |
|---|---|
| ホスティング | Cloudflare Workers(dev / staging / production の 3 環境) |
| ワークフロー | ci.yml / deploy.yml / e2e-tests.yml /
claude-code-review.yml |
| シークレット | CLOUDFLARE_API_TOKEN / CLOUDFLARE_ACCOUNT_ID
(リポジトリの GitHub Secrets) |
| データベース | なし |
EcAuth 側から MockIdP へ接続するための情報(エンドポイント、クライアント資格情報)は
1Password の mockidp-dev / mockidp-staging /
mockidp-production に置いてあります。参照するのは EcAuth 側のワークフローで、
MockIdP 自身ではありません。