Secret management integration. Use when working with HashiCorp Vault, credential management, or secure configuration. Covers secret storage, key management, NestJS integration, online/offline modes, and automatic token renewal. Keywords: secret, secrets, key, key management, secret management, vault, credential, HashiCorp, token, environment variables, configuration
@rytass/secret 系列套件提供統一的密鑰管理介面,目前支援 HashiCorp Vault 作為後端儲存。
| 套件 | 說明 | 用途 |
|------|------|------|
| @rytass/secret | 基礎介面 | 定義 SecretManager 抽象類別 |
| @rytass/secret-adapter-vault | Vault 適配器 | HashiCorp Vault 完整實現 |
| @rytass/secret-adapter-vault-nestjs | NestJS 模組 | Vault 的 NestJS 依賴注入整合 |
import { SecretManager } from '@rytass/secret';
// 所有 Secret Adapter 都繼承此抽象類別
abstract class SecretManager {
constructor(project: string);
get project(): string; // 取得初始化時的 path
abstract get<T>(key: string): Promise<T> | T;
abstract set<T>(key: string, value: T): Promise<void> | void;
abstract delete(key: string): Promise<void> | void;
}
# 直接使用 Vault
npm install @rytass/secret-adapter-vault
# NestJS 專案
npm install @rytass/secret-adapter-vault-nestjs
import { VaultSecret } from '@rytass/secret-adapter-vault';
const vault = new VaultSecret('apps/myapp/config', {
host: 'https://vault.company.com',
online: false, // 離線模式(預設)
auth: {
account: 'myapp-service',
password: 'secure-password',
},
onReady: () => {
// 同步操作,無需 await
const dbPassword = vault.get<string>('DATABASE_PASSWORD');
// 修改本地快取(不立即同步到 Vault)
vault.set('API_KEY', 'new-value');
vault.delete('OLD_CONFIG');
// 或者立即同步到 Vault(syncToOnline = true)
vault.set('ANOTHER_KEY', 'value', true); // 立即同步
vault.delete('TEMP_KEY', true); // 立即同步
// 批次同步變更到 Vault
vault.sync(); // 如果版本不符會拋錯
vault.sync(true); // force = true,強制覆蓋
},
});
// app.module.ts
import { VaultModule } from '@rytass/secret-adapter-vault-nestjs';
@Module({
imports: [
VaultModule.forRoot({
path: '/secret/data/myapp',
fallbackFile: '.env.local', // Vault 不可用時的備用
}),
],
})
export class AppModule {}
// config.service.ts
@Injectable()
export class ConfigService {
constructor(private readonly vault: VaultService) {}
// get() 預設泛型為 string,可省略
async getDatabaseUrl(): Promise<string> {
return this.vault.get('DATABASE_URL'); // 等同 get<string>()
}
// set() 和 delete() 也支援 syncToOnline 參數
async updateConfig(key: string, value: string): Promise<void> {
await this.vault.set(key, value); // syncToOnline = false(預設)
await this.vault.set(key, value, true); // syncToOnline = true,立即同步
}
async removeConfig(key: string): Promise<void> {
await this.vault.delete(key); // syncToOnline = false(預設)
await this.vault.delete(key, true); // syncToOnline = true,立即同步
}
}
| 特性 | 線上模式 | 離線模式(預設) |
|------|---------|-----------------|
| 操作方式 | 即時連接 Vault | 使用本地快取 |
| get() 返回 | Promise<T> | T(同步) |
| set()/delete() 返回 | Promise<void> | Promise<void>(syncToOnline 時)或同步操作本地快取 |
| 效能 | 有網路延遲 | 極快 |
| 適用場景 | 需即時更新 | 頻繁讀取 |
| Token 管理 | 自動續期(預設 TTL 約 32 天)| 初始化時取得 |
enum VaultSecretState {
INIT = 'INIT', // 初始化中
READY = 'READY', // 可用
TERMINATED = 'TERMINATED', // 已終止
}
// 檢查狀態
if (vault.state === VaultSecretState.READY) {
// 可以安全操作
}
enum VaultEvents {
INITED = 'INITED', // 初始化開始
READY = 'READY', // Vault 準備就緒
TOKEN_RENEWED = 'TOKEN_RENEWED', // Token 已續期
TERMINATED = 'TERMINATED', // 連線已終止
ERROR = 'ERROR', // 發生錯誤
}
以下所有型別皆從 @rytass/secret-adapter-vault 導出:
import {
VaultSecret,
VaultSecretState,
VaultEvents,
VaultAuthMethods,
VaultAuthMethodAccountPassword,
VaultSecretOptions,
VaultSecretOnlineOptions,
VaultSecretOfflineOptions,
VaultGetType,
VaultSetType,
VaultDeleteType,
VaultTokenRetrieveSuccessResponse,
VaultAPIFailedResponse,
VaultTokenRetrieveResponse,
VaultGetSecretSuccessResponse,
VaultGetSecretResponse,
} from '@rytass/secret-adapter-vault';
認證型別:
// 帳號密碼認證
interface VaultAuthMethodAccountPassword {
account: string;
password: string;
}
// 支援的認證方式(目前僅支援帳號密碼)
type VaultAuthMethods = VaultAuthMethodAccountPassword;
選項型別:
// 通用選項
interface VaultSecretOptions {
host: string; // Vault 伺服器位址
auth: VaultAuthMethods; // 認證方式
online?: boolean; // 是否使用線上模式(預設 false)
tokenTTL?: number; // Token TTL(毫秒),預設 2764724(約 32 天)
onError?: (error: string) => void; // 錯誤回調
onReady?: () => void; // 準備就緒回調
}
// 線上模式選項(online: true)
// 注意:獨立定義,非繼承自 VaultSecretOptions
interface VaultSecretOnlineOptions {
host: string;
auth: VaultAuthMethods;
online: true;
tokenTTL?: number;
onError?: (error: string) => void;
onReady?: () => void;
}
// 離線模式選項(online: false 或省略)
// 注意:獨立定義,非繼承自 VaultSecretOptions
interface VaultSecretOfflineOptions {
host: string;
auth: VaultAuthMethods;
online?: false;
tokenTTL?: number;
onError?: (error: string) => void;
onReady?: () => void;
}
條件型別(根據模式決定返回值):
// get() 返回型別:線上模式為 Promise<T>,離線模式為 T
type VaultGetType<O extends VaultSecretOptions, T> = O extends VaultSecretOnlineOptions ? Promise<T> : T;
// set() 返回型別
type VaultSetType<O extends VaultSecretOptions> = O extends VaultSecretOnlineOptions ? Promise<void> : void;
// delete() 返回型別
type VaultDeleteType<O extends VaultSecretOptions> = O extends VaultSecretOnlineOptions ? Promise<void> : void;
API 回應型別:
// API 失敗回應
interface VaultAPIFailedResponse {
errors: string[];
}
// Token 取得成功回應
// 注意:token_type 使用內部 enum VaultTokenType(未導出)
type VaultTokenRetrieveSuccessResponse = {
auth: {
client_token: string;
accessor: string;
policies: string[];
token_policies: string[];
metadata: Record<string, string> | null;
lease_duration: number;
renewable: boolean;
entity_id: string;
token_type: 'service' | 'batch'; // VaultTokenType enum(未導出)
orphan: boolean;
mfa_requirement: null;
num_uses: number;
};
} & VaultAPIBaseInfo<null>;
// Token 取得回應(成功或失敗)
type VaultTokenRetrieveResponse = VaultTokenRetrieveSuccessResponse | VaultAPIFailedResponse;
// Secret 取得成功回應
type VaultGetSecretSuccessResponse = VaultAPIBaseInfo<{
data: Record<string, unknown>;
metadata: {
created_time: string;
custom_metadata: null;
deletion_time: string;
destroyed: boolean;
version: number;
};
}>;
// Secret 取得回應(成功或失敗)
type VaultGetSecretResponse = VaultGetSecretSuccessResponse | VaultAPIFailedResponse;
const vault = new VaultSecret('apps/myapp/config', {
host: 'https://vault.company.com',
online: true,
auth: {
account: 'service-account',
password: 'password',
},
});
// 需要 await
const secret = await vault.get<string>('SECRET_KEY');
await vault.set('NEW_KEY', 'value');
await vault.delete('OLD_KEY');
@Module({
imports: [
VaultModule.forRoot({ path: '/secret/data/database' }),
TypeOrmModule.forRootAsync({
imports: [VaultModule],
inject: [VaultService],
useFactory: async (vault: VaultService) => ({
type: 'postgres',
host: await vault.get<string>('DB_HOST'),
port: await vault.get<number>('DB_PORT'),
username: await vault.get<string>('DB_USERNAME'),
password: await vault.get<string>('DB_PASSWORD'),
database: await vault.get<string>('DB_NAME'),
}),
}),
],
})
export class DatabaseModule {}
@Module({
imports: [
VaultModule.forRoot({ path: '/secret/data/auth' }),
JwtModule.registerAsync({
imports: [VaultModule],
inject: [VaultService],
useFactory: async (vault: VaultService) => ({
secret: await vault.get<string>('JWT_SECRET'),
signOptions: {
expiresIn: await vault.get<string>('JWT_EXPIRY') || '1h',
},
}),
}),
],
})
export class AuthModule {}
const vault = new VaultSecret('apps/myapp/config', {
host: 'https://vault.company.com',
online: false,
auth: { account: 'user', password: 'pass' },
onError: (error) => {
console.error('Vault error:', error);
// 可在此切換到備用配置
},
onReady: () => {
console.log('Vault ready');
},
});
// 離線模式下定期同步變更
setInterval(async () => {
try {
// sync() 會檢查版本,如果 Vault 上的版本與快取不符會拋出錯誤
await vault.sync();
console.log('Synced to Vault');
} catch (error) {
if (error.message.includes('version is not match')) {
// 版本不符時,可選擇強制覆蓋
await vault.sync(true); // force = true
console.log('Force synced to Vault');
} else {
console.warn('Sync failed:', error);
}
}
}, 300000); // 每 5 分鐘
// 離線模式下,set() 和 delete() 預設只修改本地快取
vault.set('KEY', 'value'); // syncToOnline = false(預設)
vault.delete('KEY'); // syncToOnline = false(預設)
// 如果需要立即同步到 Vault,傳入 syncToOnline = true
vault.set('KEY', 'value', true); // 立即同步到 Vault
vault.delete('KEY', true); // 立即同步到 Vault
process.on('SIGTERM', () => {
vault.terminate();
process.exit(0);
});
// NestJS 模組配置選項
interface VaultModuleOptions {
path: string; // Vault secret path(預設 '/')
fallbackFile?: string; // Vault 不可用時的備用 .env 檔案
}
class VaultService {
// get() 預設泛型為 string,可省略
async get<T = string>(key: string): Promise<T>;
// set() 支援 syncToOnline 參數
async set<T = string>(key: string, value: T, syncToOnline = false): Promise<void>;
// delete() 支援 syncToOnline 參數
async delete(key: string, syncToOnline = false): Promise<void>;
}
# 必需(若未設置 VAULT_HOST,會自動切換至備用模式)
VAULT_HOST=https://vault.example.com:8200
VAULT_ACCOUNT=your-username
VAULT_PASSWORD=your-password
注意:
path不是透過環境變數設定,而是在VaultModule.forRoot({ path: '...' })中指定。
詳細 API 文件請參閱 reference.md。
VAULT_HOST 使用 HTTPS線上模式會自動續期。如果仍然過期:
tokenTTL 設定當 VAULT_HOST 未設置時或連線失敗時,VaultService 會切換至備用模式:
get() 從 ConfigService(環境變數)讀取set() 和 delete() 會拋出錯誤:"Cannot set/delete value when fallback to env file is enabled."// 備用模式下的行為
try {
await vaultService.set('KEY', 'value');
} catch (error) {
// Error: Cannot set value when fallback to env file is enabled.
console.log('Vault 備用模式不支援寫入操作');
}
Search for places (restaurants, cafes, etc.) via Google Places API proxy on localhost.
Interact with GitHub using the `gh` CLI. Use `gh issue`, `gh pr`, `gh run`, and `gh api` for issues, PRs, CI runs, and advanced queries.
Create or update AgentSkills. Use when designing, structuring, or packaging skills with scripts, references, and assets.
Start voice calls via the OpenClaw voice-call plugin.
Notion API for creating and managing pages, databases, and blocks.
Gemini CLI for one-shot Q&A, summaries, and generation.
Category:developer