このガイドを使う場面
- HashiCorp Vaultが組織の標準である場合
- KubernetesでVault Agent Injectorを使用できる場合
- クラスターにVault Secrets Operator(VSO)が導入されている場合
- Spring Cloud Vaultで直接連携する場合
application.ymlにVaultトークンがハードコードされている場合
例
Pattern A: Spring Cloud Vault(直接連携)
アプリケーションがVault APIを呼び出してシークレットを読み込みます。Spring Cloud Vaultは、application.ymlのspring.config.import: vault://を読み取り、Vaultから設定値を供給します。
このパターンを選ぶ場面
- VMやECSなど、Kubernetes以外でもVaultを使用する場合
- Spring BootアプリケーションがVaultから直接取得する必要がある場合
変更前
# application.yml: 토큰 하드코딩
spring:
config:
import: vault://
cloud:
vault:
host: vault.internal
port: 8200
scheme: https
authentication: TOKEN
token: hvs.xxxxxxxxxxxxxxxxxxxxxxxxxxxx # 절대 커밋하면 안되는 부분
kv:
enabled: true
backend: secret
default-context: my-service
変更後
# application.yml: Kubernetes Auth + role 기반 인증
spring:
config:
import: vault://
cloud:
vault:
host: vault.internal
port: 8200
scheme: https
authentication: KUBERNETES
kubernetes:
role: my-service-role
service-account-token-file: /var/run/secrets/kubernetes.io/serviceaccount/token
kv:
enabled: true
backend: secret
default-context: my-service
Kubernetesでは、KUBERNETES認証でサービスアカウントトークンを使います。VMやECSでは対応するプラットフォーム認証やAPPROLEを検討し、AppRoleのrole-idとsecret-idは保護された経路で渡してください。Projected Service Account Tokenを別のボリュームにマウントする場合は、service-account-token-fileに実際のパス(例:/var/run/secrets/tokens/vault-token)を指定してください。
依存関係の例
// build.gradle
dependencies {
implementation 'org.springframework.cloud:spring-cloud-starter-vault-config'
// Dynamic Secrets (Database Secret Engine) 사용 시 추가
implementation 'org.springframework.cloud:spring-cloud-vault-config-databases'
}
<!-- pom.xml -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-vault-config</artifactId>
</dependency>
<!-- Dynamic Secrets (Database Secret Engine) 사용 시 추가 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-vault-config-databases</artifactId>
</dependency>
Springのコード例
Vaultのキーがそのままプロパティ名になるため、db.passwordやjwt.secretなど、アプリケーションの参照名と一致させてください。次の@ConfigurationPropertiesクラスには、検証用の依存関係と、@ConfigurationPropertiesScanまたは@EnableConfigurationPropertiesによる登録が必要です。
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class DatabaseConfig {
// Vault 경로 secret/my-service의 db.password 키에 저장된 값
@Value("${db.password}")
private String dbPassword;
@Value("${jwt.secret}")
private String jwtSecret;
}
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.validation.annotation.Validated;
import jakarta.validation.constraints.NotBlank;
@Validated
@ConfigurationProperties(prefix = "db")
public class DbProperties {
@NotBlank
private String password;
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
Pattern B: Vault Agent Injector(Kubernetesサイドカー)
Vault Agentをサイドカーとして注入し、シークレットをファイルで供給します。InjectorとVault認証の構成後、アノテーションでファイル注入を要求し、Springのspring.config.importで読み込みます。
このパターンを選ぶ場面
- KubernetesにVault Agent Injectorが導入されている場合
- アプリケーションコードを変更せず、Vaultのシークレットをファイルで渡す場合
変更前
# Vault 토큰 하드코딩 및 DB 비밀번호와의 혼동
env:
- name: VAULT_TOKEN
value: "hvs.xxxxxxxxxxxxxxxxxxxxxxxxxxxx"
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: my-secret # DB 비밀번호 대신 Vault 토큰을 참조하는 잘못된 예시
key: vault-token
変更後
Podアノテーションの例
Deploymentの抜粋であり、selectorやlabelなどは省略しています。PodのサービスアカウントとVaultロールを関連付け、必要なパスの読み取り権限を設定してください。
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-service
spec:
template:
metadata:
annotations:
vault.hashicorp.com/agent-inject: "true"
vault.hashicorp.com/role: "my-service-role"
vault.hashicorp.com/agent-inject-secret-application.properties: "secret/data/my-service"
vault.hashicorp.com/agent-inject-template-application.properties: |
{{- with secret "secret/data/my-service" -}}
db.password={{ .Data.data.db_password }}
jwt.secret={{ .Data.data.jwt_secret }}
{{- end }}
spec:
containers:
- name: my-service
image: my-service:latest
Injectorは/vault/secrets/application.propertiesを生成します。テンプレートはKV v2のdb_passwordとjwt_secretをSpringのプロパティに対応付けます。実値に改行や特殊文字がある場合は、properties形式に合わせてエスケープしてください。
Springでの読み込み
# application.yml: 주입된 파일을 Spring 설정으로 로딩
spring:
config:
import: file:/vault/secrets/application.properties
必須のシークレットファイルなので、optional:は付けません。
Pattern C: Vault Secrets Operator
VSOはVaultStaticSecretやVaultDynamicSecretを監視し、Vaultの値をKubernetes Secretに同期します。アプリケーションはVaultに直接連携せず、通常のKubernetes Secretを読み取ります。値はVaultの暗号化境界の外にあるetcdにも保存されるため、保存時の暗号化とRBACを先に構成してください。
このパターンを選ぶ場面
- KubernetesにVSOが導入されている場合
- Vault Agentサイドカーを使わず、Kubernetes Secretで管理する場合
- 同じシークレットを複数の名前空間に供給する場合
変更前
# Kubernetes Secret에 값을 직접 하드코딩하지 않는다
apiVersion: v1
kind: Secret
metadata:
name: my-service-secret
type: Opaque
stringData:
db-password: "s3cr3t-password" # 절대 커밋하면 안되는 부분
jwt-secret: "my-jwt-secret-key"
変更後
# VaultStaticSecret CRD: VSO가 Vault에서 값을 읽어 Kubernetes Secret으로 동기화
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultStaticSecret
metadata:
name: my-service-secret
namespace: my-namespace
spec:
vaultAuthRef: my-vault-auth
mount: secret
type: kv-v2
path: my-service
refreshAfter: 60s
destination:
name: my-service-secret # 생성될 Kubernetes Secret 이름
create: true
Springでの読み込み
このリソースは、同じ名前空間に構成したmy-vault-authを参照します。Vaultのキーがdb_passwordとjwt_secretの場合は、環境変数とアプリケーションのプロパティに明示的に対応付けてください。
# application.yml
db:
password: ${DB_PASSWORD}
jwt:
secret: ${JWT_SECRET}
# 컨테이너 환경 변수 설정의 일부
env:
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: my-service-secret
key: db_password
- name: JWT_SECRET
valueFrom:
secretKeyRef:
name: my-service-secret
key: jwt_secret
実行中のプロセスの環境変数は自動更新されません。同期後の再起動、または対応する再読み込み手順を構成してください。
動的シークレット
Vaultは、DBアカウントやAWSキーなどを有効なリース期間付きで発行できます。更新と失効を監視し、アプリケーションが新しい認証情報を使うように構成してください。
Pattern Aでの使用(Spring Cloud Vault)
# application.yml: DB Dynamic Secrets
spring:
config:
import: vault://
cloud:
vault:
host: vault.internal
port: 8200
scheme: https
authentication: KUBERNETES
kubernetes:
role: my-service-role
service-account-token-file: /var/run/secrets/kubernetes.io/serviceaccount/token
database:
enabled: true
role: my-service-db-role # Vault Database Secret Engine에 등록된 role
backend: database
Spring Cloud Vaultは起動時にDB認証情報を取得し、DataSourceに供給します。更新可能なリースは管理しますが、max_ttl到達時に新しい認証情報を取得してDataSourceを再構成することはありません。期限前にアプリケーションを再起動するか、認証情報と接続プールを別途更新する手順を用意してください。
Pattern Cでの使用(VSO)
# VaultDynamicSecret CRD
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultDynamicSecret
metadata:
name: my-service-db-secret
namespace: my-namespace
spec:
vaultAuthRef: my-vault-auth
mount: database
path: creds/my-service-db-role
destination:
name: my-service-db-secret
create: true
開発者の作業
application.ymlからVaultトークンを削除してください。- Pattern A:環境に合った対応認証方式を選び、認証情報の受け渡しを保護してください。
- Pattern B:Podアノテーションを追加し、注入されたファイルを
spring.config.importで参照してください。 - Pattern C:
VaultStaticSecretまたはVaultDynamicSecretを定義してください。 @ConfigurationPropertiesの必須値を@NotBlankなどで検証し、欠けている場合は起動を中止してください。- ログ、例外、Actuatorエンドポイントにシークレットが出ないことを確認してください。
インフラ・プラットフォーム担当者の作業
- サービスごとにVaultのポリシーとロールを作成し、最小権限を適用してください。
- Kubernetes認証を有効にし、各サービスアカウントに適切なロールを関連付けてください。
- Pattern B:
vault-agent-injectorの導入を確認してください。 - Pattern C:VSOの導入を確認してください。
- Database Secrets Engineに接続とロールを構成してください。
- ローテーション時の再起動や再読み込みを運用手順に記載してください。
- Helm values、ConfigMap、DockerイメージにVaultトークンを含めないでください。
検証方法
application.yml、Helm values、ConfigMapにVaultトークンが埋め込まれていないことを確認してください。- Pattern A:値をログに出さずに、起動時の取得が成功することを確認してください。
- Pattern B:
kubectl execで/vault/secrets/にファイルがあることを確認してください。 - Pattern C:
kubectl get secretで同期先Secretの作成を確認してください。 /actuator/env、デバッグログ、例外応答にシークレットが出ないことを確認してください。- 必須のシークレットがなければ起動に失敗することを確認してください。
よくある誤り
application.ymlにVaultトークンをハードコードする- 長期トークンをアクセス制御やローテーションなしで使う
- Vaultのパスを設定ではなくアプリケーションコードに埋め込む
- ローテーション後の再起動手順を用意しない
- リース更新、最大TTL、アプリケーションが新しい認証情報を使う時点を調整しない
担当者への案内例
- 「
application.ymlからVaultトークンを削除し、KubernetesではKUBERNETES認証を使用してください。」 - 「Vault認証とInjectorを構成し、アノテーションで注入を要求して、
spring.config.importで必須ファイルを読み込んでください。」 - 「環境に合った認証と最小権限を適用し、ローテーション後に新しい値をアプリケーションへ反映する手順を記載してください。」
- 「VSOが導入済みなら、
VaultStaticSecretやVaultDynamicSecretを定義し、通常のKubernetes Secretとして値を供給してください。」 - 「動的シークレットではリース更新と最大TTLを確認し、期限前にアプリケーションが新しい認証情報を使うようにしてください。」