From 2865e65cfff6abadb936094a1912effdd4a715fa Mon Sep 17 00:00:00 2001 From: reatang Date: Tue, 31 Mar 2026 01:25:55 +0800 Subject: [PATCH 1/3] feat. add vault transit engine --- app/(dashboard)/sse/page.tsx | 104 ++++++++++++++++++++++------------- i18n/locales/en-US.json | 3 + i18n/locales/zh-CN.json | 3 + types/kms.ts | 32 +++++++++-- 4 files changed, 99 insertions(+), 43 deletions(-) diff --git a/app/(dashboard)/sse/page.tsx b/app/(dashboard)/sse/page.tsx index a99ca9d..a0e7ead 100644 --- a/app/(dashboard)/sse/page.tsx +++ b/app/(dashboard)/sse/page.tsx @@ -55,7 +55,7 @@ const KEY_LIST_LIMIT = 20 const DEFAULT_PENDING_DELETE_DAYS = 7 type ConfigFormState = { - backendType: "local" | "vault" + backendType: "local" | "vault" | "vault-transit" keyDir: string filePermissions: string defaultKeyId: string @@ -142,23 +142,35 @@ function isAbsolutePath(value: string) { return /^(?:[A-Za-z]:[\\/]|\\\\|\/)/.test(value.trim()) } +function normalizeBackendType(value?: string | null): ConfigFormState["backendType"] { + switch (value) { + case "Vault": + return "vault" + case "VaultTransit": + return "vault-transit" + default: + return "local" + } +} + function buildFormStateFromStatus(status: KmsServiceStatusResponse | null): ConfigFormState { if (!status) return INITIAL_FORM_STATE const summary = status.config_summary const backendSummary = summary?.backend_summary const cacheSummary = summary?.cache_summary + const backendType = normalizeBackendType(status.backend_type ?? summary?.backend_type) return { - backendType: status.backend_type === "Vault" ? "vault" : "local", + backendType, keyDir: backendSummary?.key_dir ?? "", filePermissions: String(backendSummary?.file_permissions ?? 384), defaultKeyId: summary?.default_key_id ?? "", - timeoutSeconds: String(backendSummary?.timeout_seconds ?? 30), - retryAttempts: String(backendSummary?.retry_attempts ?? 3), + timeoutSeconds: String(summary?.timeout_seconds ?? 30), + retryAttempts: String(summary?.retry_attempts ?? 3), enableCache: summary?.enable_cache ?? cacheSummary?.enabled ?? true, - maxCachedKeys: String(summary?.max_cached_keys ?? cacheSummary?.max_cached_keys ?? 1000), - cacheTtlSeconds: String(summary?.cache_ttl_seconds ?? cacheSummary?.cache_ttl_seconds ?? cacheSummary?.ttl_seconds ?? 3600), + maxCachedKeys: String(summary?.max_cached_keys ?? cacheSummary?.max_keys ?? 1000), + cacheTtlSeconds: String(summary?.cache_ttl_seconds ?? cacheSummary?.ttl_seconds ?? 3600), address: backendSummary?.address ?? "", vaultToken: "", namespace: backendSummary?.namespace ?? "", @@ -218,6 +230,7 @@ export default function SSEPage() { const statusKind = React.useMemo(() => getStatusKind(status), [status]) const isRunning = statusKind === "Running" const hasConfiguration = statusKind !== "NotConfigured" + const hasStoredVaultCredentials = status?.config_summary?.backend_summary?.has_stored_credentials === true const statusBadgeValue = statusKind === "Error" ? "Error" : typeof status?.status === "string" ? status.status : statusKind @@ -230,7 +243,7 @@ export default function SSEPage() { if (syncForm) { setFormState((current) => { const next = buildFormStateFromStatus(res) - if (current.vaultToken && next.backendType === "vault") { + if (current.vaultToken && next.backendType !== "local") { next.vaultToken = current.vaultToken } return next @@ -345,7 +358,7 @@ export default function SSEPage() { return { payload: { - backend_type: "local", + backend_type: "Local", key_dir: formState.keyDir.trim(), file_permissions: parseOptionalInteger(formState.filePermissions) ?? 384, default_key_id: defaultKeyId || undefined, @@ -361,7 +374,7 @@ export default function SSEPage() { if (!formState.address.trim()) { return { error: t("Please enter Vault server address") } } - if (!formState.vaultToken.trim()) { + if (!formState.vaultToken.trim() && !hasStoredVaultCredentials) { return { error: t("Please enter Vault token") } } if (!formState.mountPath.trim()) { @@ -370,7 +383,7 @@ export default function SSEPage() { return { payload: { - backend_type: "vault", + backend_type: formState.backendType === "vault" ? "Vault" : "VaultTransit", address: formState.address.trim(), auth_method: { Token: { @@ -379,8 +392,12 @@ export default function SSEPage() { }, namespace: formState.namespace.trim() || null, mount_path: formState.mountPath.trim(), - kv_mount: formState.kvMount.trim() || null, - key_path_prefix: formState.keyPathPrefix.trim() || null, + ...(formState.backendType === "vault" + ? { + kv_mount: formState.kvMount.trim() || null, + key_path_prefix: formState.keyPathPrefix.trim() || null, + } + : {}), skip_tls_verify: formState.skipTlsVerify, default_key_id: defaultKeyId || undefined, timeout_seconds: timeoutSeconds ?? 30, @@ -391,7 +408,7 @@ export default function SSEPage() { }, } }, - [formState, t], + [formState, hasStoredVaultCredentials, t], ) const submitConfiguration = React.useCallback( @@ -737,7 +754,8 @@ export default function SSEPage() { {t("Local filesystem")} - {t("HashiCorp Vault Transit Engine")} + {t("HashiCorp Vault KV2")} + {t("HashiCorp Vault Transit Engine")} @@ -811,11 +829,19 @@ export default function SSEPage() { type="password" value={formState.vaultToken} onChange={(event) => updateFormState("vaultToken", event.target.value)} - placeholder={t("Enter your Vault authentication token")} + placeholder={ + hasStoredVaultCredentials + ? t("Stored token is hidden. Enter a new token only to replace it.") + : t("Enter your Vault authentication token") + } autoComplete="off" /> - {t("Required: Vault authentication token")} + + {hasStoredVaultCredentials + ? t("Leave blank to keep the stored Vault token.") + : t("Required: Vault authentication token")} + @@ -842,27 +868,31 @@ export default function SSEPage() { - - {t("KV Mount")} - - updateFormState("kvMount", event.target.value)} - placeholder="secret" - /> - - - - - {t("Key Path Prefix")} - - updateFormState("keyPathPrefix", event.target.value)} - placeholder="rustfs/kms/keys" - /> - - + {formState.backendType === "vault" && ( + <> + + {t("KV Mount")} + + updateFormState("kvMount", event.target.value)} + placeholder="secret" + /> + + + + + {t("Key Path Prefix")} + + updateFormState("keyPathPrefix", event.target.value)} + placeholder="rustfs/kms/keys" + /> + + + + )}
diff --git a/i18n/locales/en-US.json b/i18n/locales/en-US.json index dbb4815..2f514ea 100644 --- a/i18n/locales/en-US.json +++ b/i18n/locales/en-US.json @@ -240,6 +240,7 @@ "Enter AppRole Role ID": "Enter AppRole Role ID", "Enter AppRole Secret ID": "Enter AppRole Secret ID", "Enter your Vault authentication token": "Enter your Vault authentication token", + "Leave blank to keep the stored Vault token.": "Leave blank to keep the stored Vault token.", "Enterprise": "Enterprise", "Enterprise License": "Enterprise License", "Enterprise Service Level": "Enterprise Service Level", @@ -305,6 +306,7 @@ "Get Help": "Get Help", "Groups": "Groups", "HashiCorp Encryption": "HashiCorp Encryption", + "HashiCorp Vault KV2": "HashiCorp Vault KV2", "HashiCorp Vault Transit Engine": "HashiCorp Vault Transit Engine", "Health Check Interval (seconds)": "Health Check Interval (seconds)", "High Memory Usage Warning": "High Memory Usage Warning", @@ -608,6 +610,7 @@ "Replication": "Replication", "Request timeout in seconds, default: 30": "Request timeout in seconds, default: 30", "Required: Vault authentication token": "Required: Vault authentication token", + "Stored token is hidden. Enter a new token only to replace it.": "Stored token is hidden. Enter a new token only to replace it.", "Reset": "Reset", "Reset to Default": "Reset to Default", "Reset to default successfully": "Reset to default successfully", diff --git a/i18n/locales/zh-CN.json b/i18n/locales/zh-CN.json index 8b025b0..aabc6f7 100644 --- a/i18n/locales/zh-CN.json +++ b/i18n/locales/zh-CN.json @@ -243,6 +243,7 @@ "Enter AppRole Role ID": "输入 AppRole 角色 ID", "Enter AppRole Secret ID": "输入AppRole Secret ID", "Enter your Vault authentication token": "输入您的Vault认证令牌", + "Leave blank to keep the stored Vault token.": "留空则继续使用已存储的 Vault 令牌。", "Enterprise": "企业版", "Enterprise License": "企业版许可证", "Enterprise Service Level": "企业服务级别", @@ -312,6 +313,7 @@ "Get Notification Config Failed": "获取通知配置失败", "Groups": "分组", "HashiCorp Encryption": "HashiCorp 加密", + "HashiCorp Vault KV2": "HashiCorp Vault KV2", "HashiCorp Vault Transit Engine": "HashiCorp Vault Transit 引擎", "Health Check Interval (seconds)": "健康检查间隔(秒)", "High Memory Usage Warning": "高内存使用警告", @@ -626,6 +628,7 @@ "Replicate Delete": "复制删除", "Request timeout in seconds, default: 30": "请求超时时间(秒),默认:30", "Required: Vault authentication token": "必需:Vault 认证令牌", + "Stored token is hidden. Enter a new token only to replace it.": "已存储的令牌不会显示;仅在需要替换时输入新令牌。", "Reset": "重置", "Reset to Default": "重置为默认", "Reset to default successfully": "重置为默认成功", diff --git a/types/kms.ts b/types/kms.ts index c6a8e2e..b95658d 100644 --- a/types/kms.ts +++ b/types/kms.ts @@ -1,9 +1,10 @@ export type KmsServiceStatus = "NotConfigured" | "Configured" | "Running" | { Error: string } -export type KmsBackendType = "Local" | "Vault" +export type KmsBackendType = "Local" | "Vault" | "VaultTransit" export interface KmsCacheSummary { enabled?: boolean + max_keys?: number | null max_cached_keys?: number | null cache_ttl_seconds?: number | null ttl_seconds?: number | null @@ -12,18 +13,22 @@ export interface KmsCacheSummary { export interface KmsBackendSummary { key_dir?: string | null file_permissions?: number | null + has_master_key?: boolean | null address?: string | null + auth_method_type?: string | null + has_stored_credentials?: boolean | null namespace?: string | null mount_path?: string | null kv_mount?: string | null key_path_prefix?: string | null skip_tls_verify?: boolean | null - timeout_seconds?: number | null - retry_attempts?: number | null } export interface KmsConfigSummary { + backend_type?: KmsBackendType | null default_key_id?: string | null + timeout_seconds?: number | null + retry_attempts?: number | null enable_cache?: boolean | null max_cached_keys?: number | null cache_ttl_seconds?: number | null @@ -49,7 +54,7 @@ export interface KmsStartRequest { } export interface KmsLocalConfigPayload { - backend_type: "local" + backend_type: "Local" key_dir: string file_permissions?: number default_key_id?: string @@ -67,7 +72,7 @@ export interface KmsVaultTokenAuthMethod { } export interface KmsVaultConfigPayload { - backend_type: "vault" + backend_type: "Vault" address: string auth_method: KmsVaultTokenAuthMethod namespace?: string | null @@ -83,7 +88,22 @@ export interface KmsVaultConfigPayload { cache_ttl_seconds?: number } -export type KmsConfigPayload = KmsLocalConfigPayload | KmsVaultConfigPayload +export interface KmsVaultTransitConfigPayload { + backend_type: "VaultTransit" + address: string + auth_method: KmsVaultTokenAuthMethod + namespace?: string | null + mount_path: string + skip_tls_verify?: boolean + default_key_id?: string + timeout_seconds?: number + retry_attempts?: number + enable_cache?: boolean + max_cached_keys?: number + cache_ttl_seconds?: number +} + +export type KmsConfigPayload = KmsLocalConfigPayload | KmsVaultConfigPayload | KmsVaultTransitConfigPayload export interface KmsKeyInfo { key_id: string From 393ac129b57085440a8663e5ca9bac7119c51cf6 Mon Sep 17 00:00:00 2001 From: reatang Date: Thu, 2 Apr 2026 00:05:37 +0800 Subject: [PATCH 2/3] fix: Make the name a bit longer. --- components/buckets/info.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/buckets/info.tsx b/components/buckets/info.tsx index 8a9c85c..d45554f 100644 --- a/components/buckets/info.tsx +++ b/components/buckets/info.tsx @@ -257,7 +257,7 @@ export function BucketInfo({ bucketName }: BucketInfoProps) { (keys as { keys?: Array<{ key_id?: string; tags?: { name?: string }; description?: string }> })?.keys ?? [] setKmsKeyOptions( list.map((k) => ({ - label: k.tags?.name ?? k.description ?? `Key-${(k.key_id ?? "").slice(0, 8)}`, + label: k.tags?.name ?? k.description ?? `Key-${(k.key_id ?? "").slice(0, 24)}`, value: k.key_id ?? "", })), ) From 44add8c578489309cba9d677a7cafa8b5ded530b Mon Sep 17 00:00:00 2001 From: reatang Date: Fri, 3 Apr 2026 00:34:14 +0800 Subject: [PATCH 3/3] fix name --- app/(dashboard)/sse/page.tsx | 13 +++++++------ types/kms.ts | 25 +++++++++++++++++++++++-- 2 files changed, 30 insertions(+), 8 deletions(-) diff --git a/app/(dashboard)/sse/page.tsx b/app/(dashboard)/sse/page.tsx index a0e7ead..be6259d 100644 --- a/app/(dashboard)/sse/page.tsx +++ b/app/(dashboard)/sse/page.tsx @@ -55,7 +55,7 @@ const KEY_LIST_LIMIT = 20 const DEFAULT_PENDING_DELETE_DAYS = 7 type ConfigFormState = { - backendType: "local" | "vault" | "vault-transit" + backendType: "local" | "vault-kv2" | "vault-transit" keyDir: string filePermissions: string defaultKeyId: string @@ -145,7 +145,8 @@ function isAbsolutePath(value: string) { function normalizeBackendType(value?: string | null): ConfigFormState["backendType"] { switch (value) { case "Vault": - return "vault" + case "VaultKV2": + return "vault-kv2" case "VaultTransit": return "vault-transit" default: @@ -383,7 +384,7 @@ export default function SSEPage() { return { payload: { - backend_type: formState.backendType === "vault" ? "Vault" : "VaultTransit", + backend_type: formState.backendType === "vault-kv2" ? "VaultKV2" : "VaultTransit", address: formState.address.trim(), auth_method: { Token: { @@ -392,7 +393,7 @@ export default function SSEPage() { }, namespace: formState.namespace.trim() || null, mount_path: formState.mountPath.trim(), - ...(formState.backendType === "vault" + ...(formState.backendType === "vault-kv2" ? { kv_mount: formState.kvMount.trim() || null, key_path_prefix: formState.keyPathPrefix.trim() || null, @@ -754,7 +755,7 @@ export default function SSEPage() { {t("Local filesystem")} - {t("HashiCorp Vault KV2")} + {t("HashiCorp Vault KV2")} {t("HashiCorp Vault Transit Engine")} @@ -868,7 +869,7 @@ export default function SSEPage() { - {formState.backendType === "vault" && ( + {formState.backendType === "vault-kv2" && ( <> {t("KV Mount")} diff --git a/types/kms.ts b/types/kms.ts index b95658d..31b0519 100644 --- a/types/kms.ts +++ b/types/kms.ts @@ -1,6 +1,6 @@ export type KmsServiceStatus = "NotConfigured" | "Configured" | "Running" | { Error: string } -export type KmsBackendType = "Local" | "Vault" | "VaultTransit" +export type KmsBackendType = "Local" | "Vault" | "VaultKV2" | "VaultTransit" export interface KmsCacheSummary { enabled?: boolean @@ -88,6 +88,23 @@ export interface KmsVaultConfigPayload { cache_ttl_seconds?: number } +export interface KmsVaultKV2ConfigPayload { + backend_type: "VaultKV2" + address: string + auth_method: KmsVaultTokenAuthMethod + namespace?: string | null + mount_path: string + kv_mount?: string | null + key_path_prefix?: string | null + skip_tls_verify?: boolean + default_key_id?: string + timeout_seconds?: number + retry_attempts?: number + enable_cache?: boolean + max_cached_keys?: number + cache_ttl_seconds?: number +} + export interface KmsVaultTransitConfigPayload { backend_type: "VaultTransit" address: string @@ -103,7 +120,11 @@ export interface KmsVaultTransitConfigPayload { cache_ttl_seconds?: number } -export type KmsConfigPayload = KmsLocalConfigPayload | KmsVaultConfigPayload | KmsVaultTransitConfigPayload +export type KmsConfigPayload = + | KmsLocalConfigPayload + | KmsVaultConfigPayload + | KmsVaultKV2ConfigPayload + | KmsVaultTransitConfigPayload export interface KmsKeyInfo { key_id: string