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
87 changes: 77 additions & 10 deletions app/(dashboard)/sse/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ const ADVANCED_CONFIG_FIELDS = new Set([
])

type ConfigFormState = {
backendType: "local" | "vault-kv2" | "vault-transit"
backendType: "local" | "vault-kv2" | "vault-transit" | "static"
keyDir: string
filePermissions: string
defaultKeyId: string
Expand All @@ -80,6 +80,8 @@ type ConfigFormState = {
kvMount: string
keyPathPrefix: string
skipTlsVerify: boolean
secretKey: string
staticKeyId: string
}

type KeyActionState = {
Expand Down Expand Up @@ -109,6 +111,8 @@ const INITIAL_FORM_STATE: ConfigFormState = {
kvMount: "secret",
keyPathPrefix: "rustfs/kms/keys",
skipTlsVerify: false,
secretKey: "",
staticKeyId: "",
}

function getStatusKind(status: KmsServiceStatusResponse | null): "NotConfigured" | "Configured" | "Running" | "Error" {
Expand Down Expand Up @@ -154,6 +158,8 @@ function normalizeBackendType(value?: string | null): ConfigFormState["backendTy
return "vault-kv2"
case "VaultTransit":
return "vault-transit"
case "Static":
return "static"
default:
return "local"
}
Expand Down Expand Up @@ -183,6 +189,8 @@ function buildFormStateFromStatus(status: KmsServiceStatusResponse | null): Conf
mountPath: backendSummary?.mount_path ?? "transit",
kvMount: backendSummary?.kv_mount ?? "secret",
keyPathPrefix: backendSummary?.key_path_prefix ?? "rustfs/kms/keys",
secretKey: "",
staticKeyId: backendSummary?.key_id ?? "",
skipTlsVerify: backendSummary?.skip_tls_verify ?? false,
}
}
Expand Down Expand Up @@ -534,6 +542,30 @@ export default function SSEPage() {
}
}

if (values.backendType === "static") {
if (!values.secretKey.trim()) {
return {
error: t(
"Please enter the static KMS secret key (base64-encoded 32-byte AES-256 key).",
),
field: "secretKey",
}
}
return {
payload: {
backend_type: "Static",
key_id: values.defaultKeyId.trim() || values.staticKeyId || "static-key",
secret_key: values.secretKey.trim(),
default_key_id: defaultKeyId || undefined,
timeout_seconds: timeoutSeconds ?? 30,
retry_attempts: retryAttempts ?? 3,
enable_cache: values.enableCache,
max_cached_keys: values.enableCache ? (maxCachedKeys ?? 1000) : undefined,
cache_ttl_seconds: values.enableCache ? (cacheTtlSeconds ?? 3600) : undefined,
},
}
}

if (!values.address.trim()) {
return { error: t("Please enter Vault server address"), field: "vaultAddress" }
}
Expand Down Expand Up @@ -906,9 +938,11 @@ export default function SSEPage() {

const mutationLocked = Boolean(activeMutation || statusError || loadingStatus)
const localKmsReadOnly = hasConfiguration && formState.backendType === "local"
const formDisabled = mutationLocked || loadingStatus || submittingConfig || localKmsReadOnly
const staticKmsReadOnly = hasConfiguration && formState.backendType === "static"
const formDisabled = mutationLocked || loadingStatus || submittingConfig || localKmsReadOnly || staticKmsReadOnly
const mutationInFlight = Boolean(activeMutation || submittingConfig || creatingKey || processingKeyAction)
const canSetCreatedKeyAsDefault = status?.backend_type !== "Local" && !isConfigDirty
const canSetCreatedKeyAsDefault =
status?.backend_type !== "Local" && status?.backend_type !== "Static" && !isConfigDirty

return (
<>
Expand Down Expand Up @@ -1107,6 +1141,7 @@ export default function SSEPage() {
</SelectItem>
<SelectItem value="vault-kv2">{t("HashiCorp Vault KV2")}</SelectItem>
<SelectItem value="vault-transit">{t("HashiCorp Vault Transit Engine")}</SelectItem>
<SelectItem value="static">{t("Static single-key (built-in)")}</SelectItem>
</SelectContent>
</Select>
</FieldContent>
Expand Down Expand Up @@ -1136,9 +1171,39 @@ export default function SSEPage() {

<fieldset className="space-y-4 border-t pt-4">
<legend className="pe-2 text-sm font-semibold">
{formState.backendType === "local" ? t("Local filesystem") : t("Vault connection")}
{formState.backendType === "local"
? t("Local filesystem")
: formState.backendType === "static"
? t("Static key configuration")
: t("Vault connection")}
</legend>
{formState.backendType === "local" ? (
{formState.backendType === "static" ? (
<FieldGroup className="grid gap-4 lg:grid-cols-2">
<Field>
<FieldLabel htmlFor="secretKey">{t("Secret Key")}</FieldLabel>
<FieldContent>
<Input
id="secretKey"
name="secretKey"
type="password"
value={formState.secretKey}
onChange={(event) => updateFormState("secretKey", event.target.value)}
autoComplete="off"
placeholder={t("Base64-encoded 32-byte AES-256 key")}
spellCheck={false}
disabled={formDisabled}
required
aria-required="true"
aria-invalid={configFormErrorField === "secretKey"}
aria-describedby={configFormErrorField === "secretKey" ? "kms-config-error" : undefined}
/>
</FieldContent>
<FieldDescription>
{t("The base64-encoded 32-byte AES-256 key used to derive data encryption keys.")}
</FieldDescription>
</Field>
</FieldGroup>
) : formState.backendType === "local" ? (
<FieldGroup className="grid gap-4 lg:grid-cols-2">
<Field>
<FieldLabel htmlFor="keyDir">{t("Local Key Directory")}</FieldLabel>
Expand Down Expand Up @@ -1483,7 +1548,7 @@ export default function SSEPage() {
<Button
size="sm"
onClick={() => setCreateKeyOpen(true)}
disabled={Boolean(activeMutation) || loadingKeys || loadingStatus || Boolean(keysError)}
disabled={Boolean(activeMutation) || loadingKeys || loadingStatus || Boolean(keysError) || staticKmsReadOnly}
>
<RiAddLine className="size-4" aria-hidden />
{t("Create Key")}
Expand Down Expand Up @@ -1577,7 +1642,7 @@ export default function SSEPage() {
variant="outline"
className="min-h-11 flex-1 sm:flex-none"
disabled={
isDefaultKey || Boolean(activeMutation) || loadingStatus || Boolean(keysError)
isDefaultKey || Boolean(activeMutation) || loadingStatus || Boolean(keysError) || staticKmsReadOnly
}
onClick={() => setPendingKeyAction({ type: "scheduleDelete", key })}
>
Expand All @@ -1589,7 +1654,7 @@ export default function SSEPage() {
variant="destructive"
className="min-h-11 flex-1 sm:flex-none"
disabled={
isDefaultKey || Boolean(activeMutation) || loadingStatus || Boolean(keysError)
isDefaultKey || Boolean(activeMutation) || loadingStatus || Boolean(keysError) || staticKmsReadOnly
}
onClick={() => setPendingKeyAction({ type: "forceDelete", key })}
>
Expand Down Expand Up @@ -1657,7 +1722,8 @@ export default function SSEPage() {
isDefaultKey ||
Boolean(activeMutation) ||
loadingStatus ||
Boolean(keysError)
Boolean(keysError) ||
staticKmsReadOnly
}
>
{t("Schedule Deletion")}
Expand All @@ -1678,7 +1744,8 @@ export default function SSEPage() {
isDefaultKey ||
Boolean(activeMutation) ||
loadingStatus ||
Boolean(keysError)
Boolean(keysError) ||
staticKmsReadOnly
}
className="text-destructive focus:text-destructive"
>
Expand Down
5 changes: 5 additions & 0 deletions i18n/locales/en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@
"Backend Services": "Backend Services",
"Backend Status": "Backend Status",
"Backend Type": "Backend Type",
"Base64-encoded 32-byte AES-256 key": "Base64-encoded 32-byte AES-256 key",
"Bandwidth Limit": "Bandwidth Limit",
"Batch allocation policies": "Batch allocation policies",
"Bitrot": "Bitrot",
Expand Down Expand Up @@ -800,6 +801,7 @@
"Please enter STS session token": "Please enter STS session token",
"Please enter STS username": "Please enter STS username",
"Please enter Secret Key": "Please enter Secret Key",
"Please enter the static KMS secret key (base64-encoded 32-byte AES-256 key).": "Please enter the static KMS secret key (base64-encoded 32-byte AES-256 key).",
"Please enter Vault server address": "Please enter Vault server address",
"Please enter Vault token": "Please enter Vault token",
"Please enter Vault transit mount path": "Please enter Vault transit mount path",
Expand Down Expand Up @@ -1099,6 +1101,8 @@
"Start Upload": "Start Upload",
"Starting": "Starting",
"State": "State",
"Static key configuration": "Static key configuration",
"Static single-key (built-in)": "Static single-key (built-in)",
"Status": "Status",
"Status refreshed successfully": "Status refreshed successfully",
"Stop": "Stop",
Expand Down Expand Up @@ -1493,6 +1497,7 @@
"Changing KMS service state may briefly interrupt SSE requests.": "Changing KMS service state may briefly interrupt SSE requests.",
"KMS will be stopped. SSE and key management will be unavailable until you start KMS again.": "KMS will be stopped. SSE and key management will be unavailable until you start KMS again.",
"You have unsaved KMS changes. Do you want to discard them?": "You have unsaved KMS changes. Do you want to discard them?",
"The base64-encoded 32-byte AES-256 key used to derive data encryption keys.": "The base64-encoded 32-byte AES-256 key used to derive data encryption keys.",
"The operation result is uncertain. Current status has been refreshed.": "The operation result is uncertain. Current status has been refreshed.",
"Please enter a Local KMS master key.": "Please enter a Local KMS master key.",
"Enter this every time you save Local KMS configuration. It is never shown or retained.": "Enter this every time you save Local KMS configuration. It is never shown or retained.",
Expand Down
16 changes: 15 additions & 1 deletion types/kms.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
export type KmsServiceStatus = "NotConfigured" | "Configured" | "Running" | { Error: string }

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

export interface KmsCacheSummary {
enabled?: boolean
Expand All @@ -22,6 +22,7 @@ export interface KmsBackendSummary {
kv_mount?: string | null
key_path_prefix?: string | null
skip_tls_verify?: boolean | null
key_id?: string | null
}

export interface KmsConfigSummary {
Expand Down Expand Up @@ -120,11 +121,24 @@ export interface KmsVaultTransitConfigPayload {
cache_ttl_seconds?: number
}

export interface KmsStaticConfigPayload {
backend_type: "Static"
key_id: string
secret_key: string
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
| KmsStaticConfigPayload

export interface KmsKeyInfo {
key_id: string
Expand Down
Loading