Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 68 additions & 39 deletions app/(dashboard)/sse/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ const KEY_LIST_LIMIT = 20
const DEFAULT_PENDING_DELETE_DAYS = 7

type ConfigFormState = {
backendType: "local" | "vault"
backendType: "local" | "vault-kv2" | "vault-transit"
keyDir: string
filePermissions: string
defaultKeyId: string
Expand Down Expand Up @@ -134,25 +134,36 @@ function isAbsolutePath(value: string) {
return /^(?:[A-Za-z]:[\\/]|\\\\|\/)/.test(value.trim())
}

function normalizeBackendType(value?: string | null): ConfigFormState["backendType"] {
switch (value) {
case "Vault":
case "VaultKV2":
return "vault-kv2"
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 ?? "",
Expand Down Expand Up @@ -212,6 +223,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

Expand All @@ -224,7 +236,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
Expand Down Expand Up @@ -339,7 +351,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,
Expand All @@ -355,7 +367,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()) {
Expand All @@ -364,7 +376,7 @@ export default function SSEPage() {

return {
payload: {
backend_type: "vault",
backend_type: formState.backendType === "vault-kv2" ? "VaultKV2" : "VaultTransit",
address: formState.address.trim(),
auth_method: {
Token: {
Expand All @@ -373,8 +385,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-kv2"
? {
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,
Expand All @@ -385,7 +401,7 @@ export default function SSEPage() {
},
}
},
[formState, t],
[formState, hasStoredVaultCredentials, t],
)

const submitConfiguration = React.useCallback(
Expand Down Expand Up @@ -736,7 +752,8 @@ export default function SSEPage() {
</SelectTrigger>
<SelectContent>
<SelectItem value="local">{t("Local filesystem")}</SelectItem>
<SelectItem value="vault">{t("HashiCorp Vault Transit Engine")}</SelectItem>
<SelectItem value="vault-kv2">{t("HashiCorp Vault KV2")}</SelectItem>
<SelectItem value="vault-transit">{t("HashiCorp Vault Transit Engine")}</SelectItem>
</SelectContent>
</Select>
</FieldContent>
Expand Down Expand Up @@ -814,11 +831,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"
/>
</FieldContent>
<FieldDescription>{t("Required: Vault authentication token")}</FieldDescription>
<FieldDescription>
{hasStoredVaultCredentials
? t("Leave blank to keep the stored Vault token.")
: t("Required: Vault authentication token")}
</FieldDescription>
</Field>
</FieldGroup>

Expand All @@ -845,27 +870,31 @@ export default function SSEPage() {
</FieldContent>
</Field>

<Field>
<FieldLabel>{t("KV Mount")}</FieldLabel>
<FieldContent>
<Input
value={formState.kvMount}
onChange={(event) => updateFormState("kvMount", event.target.value)}
placeholder="secret"
/>
</FieldContent>
</Field>

<Field>
<FieldLabel>{t("Key Path Prefix")}</FieldLabel>
<FieldContent>
<Input
value={formState.keyPathPrefix}
onChange={(event) => updateFormState("keyPathPrefix", event.target.value)}
placeholder="rustfs/kms/keys"
/>
</FieldContent>
</Field>
{formState.backendType === "vault-kv2" && (
<>
<Field>
<FieldLabel>{t("KV Mount")}</FieldLabel>
<FieldContent>
<Input
value={formState.kvMount}
onChange={(event) => updateFormState("kvMount", event.target.value)}
placeholder="secret"
/>
</FieldContent>
</Field>

<Field>
<FieldLabel>{t("Key Path Prefix")}</FieldLabel>
<FieldContent>
<Input
value={formState.keyPathPrefix}
onChange={(event) => updateFormState("keyPathPrefix", event.target.value)}
placeholder="rustfs/kms/keys"
/>
</FieldContent>
</Field>
</>
)}
</FieldGroup>

<div className="flex items-center gap-3 rounded-md border p-3">
Expand Down
2 changes: 1 addition & 1 deletion components/buckets/info.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -275,7 +275,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 ?? "",
})),
)
Expand Down
3 changes: 3 additions & 0 deletions i18n/locales/en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,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",
Expand Down Expand Up @@ -314,6 +315,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",
Expand Down Expand Up @@ -619,6 +621,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",
Expand Down
3 changes: 3 additions & 0 deletions i18n/locales/zh-CN.json
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,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": "企业服务级别",
Expand Down Expand Up @@ -321,6 +322,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": "高内存使用警告",
Expand Down Expand Up @@ -637,6 +639,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": "重置为默认成功",
Expand Down
53 changes: 47 additions & 6 deletions types/kms.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
export type KmsServiceStatus = "NotConfigured" | "Configured" | "Running" | { Error: string }

export type KmsBackendType = "Local" | "Vault"
export type KmsBackendType = "Local" | "Vault" | "VaultKV2" | "VaultTransit"

export interface KmsCacheSummary {
enabled?: boolean
max_keys?: number | null
max_cached_keys?: number | null
cache_ttl_seconds?: number | null
ttl_seconds?: number | null
Expand All @@ -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
Expand All @@ -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
Expand All @@ -67,7 +72,24 @@ export interface KmsVaultTokenAuthMethod {
}

export interface KmsVaultConfigPayload {
backend_type: "vault"
backend_type: "Vault"
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 KmsVaultKV2ConfigPayload {
backend_type: "VaultKV2"
address: string
auth_method: KmsVaultTokenAuthMethod
namespace?: string | null
Expand All @@ -83,7 +105,26 @@ 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
| KmsVaultKV2ConfigPayload
| KmsVaultTransitConfigPayload

export interface KmsKeyInfo {
key_id: string
Expand Down
Loading