mirror of
https://github.com/rustfs/console.git
synced 2026-08-28 19:47:21 +08:00
feat: add custom TLS options for bucket replication (#164)
This commit is contained in:
@@ -7,6 +7,7 @@ import { RiAddLine, RiDeleteBinLine } from "@remixicon/react"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import { Switch } from "@/components/ui/switch"
|
||||
import { Textarea } from "@/components/ui/textarea"
|
||||
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog"
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
|
||||
import { Field, FieldContent, FieldError, FieldLabel } from "@/components/ui/field"
|
||||
@@ -14,6 +15,7 @@ import { useBucket } from "@/hooks/use-bucket"
|
||||
import { useMessage } from "@/lib/feedback/message"
|
||||
import { getBytes, randomUUID } from "@/lib/functions"
|
||||
import { isMissingBucketConfiguration, normalizeReplicationRulesForRolelessConfig } from "@/lib/bucket-configuration"
|
||||
import { buildBucketReplicationTlsPayload, type BucketReplicationTlsMode } from "@/lib/bucket-replication-tls"
|
||||
|
||||
interface ReplicationNewFormProps {
|
||||
open: boolean
|
||||
@@ -39,6 +41,8 @@ export function ReplicationNewForm({ open, onOpenChange, bucketName, onSuccess }
|
||||
const [level, setLevel] = useState("1")
|
||||
const [endpoint, setEndpoint] = useState("")
|
||||
const [tls, setTls] = useState(false)
|
||||
const [tlsMode, setTlsMode] = useState<BucketReplicationTlsMode>("verify")
|
||||
const [caCertPem, setCaCertPem] = useState("")
|
||||
const [accessKey, setAccessKey] = useState("")
|
||||
const [secretKey, setSecretKey] = useState("")
|
||||
const [bucket, setBucket] = useState("")
|
||||
@@ -61,6 +65,7 @@ export function ReplicationNewForm({ open, onOpenChange, bucketName, onSuccess }
|
||||
accessKey?: string
|
||||
secretKey?: string
|
||||
timecheck?: string
|
||||
caCertPem?: string
|
||||
}>({})
|
||||
|
||||
const modeOptions = useMemo(
|
||||
@@ -84,6 +89,8 @@ export function ReplicationNewForm({ open, onOpenChange, bucketName, onSuccess }
|
||||
setLevel("1")
|
||||
setEndpoint("")
|
||||
setTls(false)
|
||||
setTlsMode("verify")
|
||||
setCaCertPem("")
|
||||
setAccessKey("")
|
||||
setSecretKey("")
|
||||
setBucket("")
|
||||
@@ -134,6 +141,9 @@ export function ReplicationNewForm({ open, onOpenChange, bucketName, onSuccess }
|
||||
if (modeType === "async" && Number(timecheck) < 1) {
|
||||
errors.timecheck = t("Please enter valid health check interval")
|
||||
}
|
||||
if (tls && tlsMode === "custom-ca" && !caCertPem.trim()) {
|
||||
errors.caCertPem = t("Custom CA certificate is required")
|
||||
}
|
||||
setFieldErrors(errors)
|
||||
const firstErrorId = errors.endpoint
|
||||
? "replication-endpoint"
|
||||
@@ -145,7 +155,9 @@ export function ReplicationNewForm({ open, onOpenChange, bucketName, onSuccess }
|
||||
? "replication-secret-key"
|
||||
: errors.timecheck
|
||||
? "replication-health-check-interval"
|
||||
: null
|
||||
: errors.caCertPem
|
||||
? "replication-ca-certificate"
|
||||
: null
|
||||
if (firstErrorId) document.getElementById(firstErrorId)?.focus()
|
||||
return !firstErrorId
|
||||
}
|
||||
@@ -161,6 +173,7 @@ export function ReplicationNewForm({ open, onOpenChange, bucketName, onSuccess }
|
||||
setSaveError("")
|
||||
let remoteTargetSaved = false
|
||||
try {
|
||||
const tlsConfig = buildBucketReplicationTlsPayload(tls, tlsMode, caCertPem)
|
||||
const config: Record<string, unknown> = {
|
||||
sourcebucket: bucketName,
|
||||
endpoint,
|
||||
@@ -171,6 +184,8 @@ export function ReplicationNewForm({ open, onOpenChange, bucketName, onSuccess }
|
||||
},
|
||||
targetbucket: bucket,
|
||||
secure: tls,
|
||||
skipTlsVerify: tlsConfig.skipTlsVerify,
|
||||
caCertPem: tlsConfig.caCertPem,
|
||||
region,
|
||||
path: "auto",
|
||||
api: "s3v4",
|
||||
@@ -566,9 +581,91 @@ export function ReplicationNewForm({ open, onOpenChange, bucketName, onSuccess }
|
||||
{t("Enable secure transport when connecting to endpoint.")}
|
||||
</p>
|
||||
</div>
|
||||
<Switch id="replication-use-tls" name="replication-use-tls" checked={tls} onCheckedChange={setTls} />
|
||||
<Switch
|
||||
id="replication-use-tls"
|
||||
name="replication-use-tls"
|
||||
checked={tls}
|
||||
disabled={submitting}
|
||||
onCheckedChange={(checked) => {
|
||||
setTls(checked)
|
||||
if (!checked) {
|
||||
setTlsMode("verify")
|
||||
setCaCertPem("")
|
||||
setFieldErrors((current) => ({ ...current, caCertPem: undefined }))
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{tls ? (
|
||||
<div className="space-y-3 border-s-2 border-border ps-4">
|
||||
<Field>
|
||||
<FieldLabel htmlFor="replication-tls-verification">{t("TLS Verification")}</FieldLabel>
|
||||
<FieldContent>
|
||||
<Select
|
||||
value={tlsMode}
|
||||
onValueChange={(value) => {
|
||||
if (value) setTlsMode(value as BucketReplicationTlsMode)
|
||||
setFieldErrors((current) => ({ ...current, caCertPem: undefined }))
|
||||
}}
|
||||
disabled={submitting}
|
||||
>
|
||||
<SelectTrigger id="replication-tls-verification" className="w-full">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="verify">{t("Default certificate verification")}</SelectItem>
|
||||
<SelectItem value="custom-ca">{t("Custom CA certificate")}</SelectItem>
|
||||
<SelectItem value="skip">{t("Skip TLS verification")}</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</FieldContent>
|
||||
</Field>
|
||||
|
||||
{tlsMode === "custom-ca" ? (
|
||||
<Field>
|
||||
<FieldLabel htmlFor="replication-ca-certificate">{t("Custom CA certificate")}</FieldLabel>
|
||||
<FieldContent>
|
||||
<Textarea
|
||||
id="replication-ca-certificate"
|
||||
name="replication-ca-certificate"
|
||||
value={caCertPem}
|
||||
onChange={(event) => {
|
||||
setCaCertPem(event.target.value)
|
||||
if (event.target.value.trim()) {
|
||||
setFieldErrors((current) => ({ ...current, caCertPem: undefined }))
|
||||
}
|
||||
}}
|
||||
aria-invalid={Boolean(fieldErrors.caCertPem)}
|
||||
aria-describedby={
|
||||
fieldErrors.caCertPem
|
||||
? "replication-ca-certificate-error"
|
||||
: "replication-ca-certificate-description"
|
||||
}
|
||||
className="min-h-32 font-mono"
|
||||
placeholder="-----BEGIN CERTIFICATE-----"
|
||||
disabled={submitting}
|
||||
spellCheck={false}
|
||||
/>
|
||||
</FieldContent>
|
||||
<p id="replication-ca-certificate-description" className="text-xs text-muted-foreground">
|
||||
{t("Paste the CA certificate in PEM format.")}
|
||||
</p>
|
||||
<FieldError id="replication-ca-certificate-error">{fieldErrors.caCertPem}</FieldError>
|
||||
</Field>
|
||||
) : null}
|
||||
|
||||
{tlsMode === "skip" ? (
|
||||
<p
|
||||
role="alert"
|
||||
className="border border-destructive/40 bg-destructive/5 p-3 text-xs text-destructive"
|
||||
>
|
||||
{t("Certificate verification is disabled. Only use this for trusted networks.")}
|
||||
</p>
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<label htmlFor="replication-existing-object" className="text-sm font-medium">
|
||||
|
||||
@@ -1061,6 +1061,12 @@
|
||||
"Size": "الحجم",
|
||||
"Skip": "تخطي",
|
||||
"Skip TLS verification": "تخطي التحقق من TLS",
|
||||
"Certificate verification is disabled. Only use this for trusted networks.": "Certificate verification is disabled. Only use this for trusted networks.",
|
||||
"Custom CA certificate": "Custom CA certificate",
|
||||
"Custom CA certificate is required": "Custom CA certificate is required",
|
||||
"Default certificate verification": "Default certificate verification",
|
||||
"Paste the CA certificate in PEM format.": "Paste the CA certificate in PEM format.",
|
||||
"TLS Verification": "TLS Verification",
|
||||
"Skip to main content": "تخطي إلى المحتوى الرئيسي",
|
||||
"Sort by": "ترتيب حسب",
|
||||
"Source": "المصدر",
|
||||
|
||||
@@ -1066,6 +1066,12 @@
|
||||
"Size": "Größe",
|
||||
"Skip": "Überspringen",
|
||||
"Skip TLS verification": "TLS-Überprüfung überspringen",
|
||||
"Certificate verification is disabled. Only use this for trusted networks.": "Certificate verification is disabled. Only use this for trusted networks.",
|
||||
"Custom CA certificate": "Custom CA certificate",
|
||||
"Custom CA certificate is required": "Custom CA certificate is required",
|
||||
"Default certificate verification": "Default certificate verification",
|
||||
"Paste the CA certificate in PEM format.": "Paste the CA certificate in PEM format.",
|
||||
"TLS Verification": "TLS Verification",
|
||||
"Skip to main content": "Zum Hauptinhalt springen",
|
||||
"Sort by": "Sortieren nach",
|
||||
"Source": "Quelle",
|
||||
|
||||
@@ -1066,6 +1066,12 @@
|
||||
"Size": "Size",
|
||||
"Skip": "Skip",
|
||||
"Skip TLS verification": "Skip TLS verification",
|
||||
"Certificate verification is disabled. Only use this for trusted networks.": "Certificate verification is disabled. Only use this for trusted networks.",
|
||||
"Custom CA certificate": "Custom CA certificate",
|
||||
"Custom CA certificate is required": "Custom CA certificate is required",
|
||||
"Default certificate verification": "Default certificate verification",
|
||||
"Paste the CA certificate in PEM format.": "Paste the CA certificate in PEM format.",
|
||||
"TLS Verification": "TLS Verification",
|
||||
"Skip to main content": "Skip to main content",
|
||||
"Sort by": "Sort by",
|
||||
"Source": "Source",
|
||||
|
||||
@@ -1066,6 +1066,12 @@
|
||||
"Size": "Tamaño",
|
||||
"Skip": "Omitir",
|
||||
"Skip TLS verification": "Omitir verificación TLS",
|
||||
"Certificate verification is disabled. Only use this for trusted networks.": "Certificate verification is disabled. Only use this for trusted networks.",
|
||||
"Custom CA certificate": "Custom CA certificate",
|
||||
"Custom CA certificate is required": "Custom CA certificate is required",
|
||||
"Default certificate verification": "Default certificate verification",
|
||||
"Paste the CA certificate in PEM format.": "Paste the CA certificate in PEM format.",
|
||||
"TLS Verification": "TLS Verification",
|
||||
"Skip to main content": "Saltar al contenido principal",
|
||||
"Sort by": "Ordenar por",
|
||||
"Source": "Origen",
|
||||
|
||||
@@ -1066,6 +1066,12 @@
|
||||
"Size": "Taille",
|
||||
"Skip": "Ignorer",
|
||||
"Skip TLS verification": "Ignorer la vérification TLS",
|
||||
"Certificate verification is disabled. Only use this for trusted networks.": "Certificate verification is disabled. Only use this for trusted networks.",
|
||||
"Custom CA certificate": "Custom CA certificate",
|
||||
"Custom CA certificate is required": "Custom CA certificate is required",
|
||||
"Default certificate verification": "Default certificate verification",
|
||||
"Paste the CA certificate in PEM format.": "Paste the CA certificate in PEM format.",
|
||||
"TLS Verification": "TLS Verification",
|
||||
"Skip to main content": "Aller au contenu principal",
|
||||
"Sort by": "Trier par",
|
||||
"Source": "Source",
|
||||
|
||||
@@ -1066,6 +1066,12 @@
|
||||
"Size": "Ukuran",
|
||||
"Skip": "Lewati",
|
||||
"Skip TLS verification": "Lewati verifikasi TLS",
|
||||
"Certificate verification is disabled. Only use this for trusted networks.": "Certificate verification is disabled. Only use this for trusted networks.",
|
||||
"Custom CA certificate": "Custom CA certificate",
|
||||
"Custom CA certificate is required": "Custom CA certificate is required",
|
||||
"Default certificate verification": "Default certificate verification",
|
||||
"Paste the CA certificate in PEM format.": "Paste the CA certificate in PEM format.",
|
||||
"TLS Verification": "TLS Verification",
|
||||
"Skip to main content": "Lewati ke konten utama",
|
||||
"Sort by": "Urutkan berdasarkan",
|
||||
"Source": "Sumber",
|
||||
|
||||
@@ -1066,6 +1066,12 @@
|
||||
"Size": "Dimensione",
|
||||
"Skip": "Salta",
|
||||
"Skip TLS verification": "Salta verifica TLS",
|
||||
"Certificate verification is disabled. Only use this for trusted networks.": "Certificate verification is disabled. Only use this for trusted networks.",
|
||||
"Custom CA certificate": "Custom CA certificate",
|
||||
"Custom CA certificate is required": "Custom CA certificate is required",
|
||||
"Default certificate verification": "Default certificate verification",
|
||||
"Paste the CA certificate in PEM format.": "Paste the CA certificate in PEM format.",
|
||||
"TLS Verification": "TLS Verification",
|
||||
"Skip to main content": "Vai al contenuto principale",
|
||||
"Sort by": "Ordina per",
|
||||
"Source": "Sorgente",
|
||||
|
||||
@@ -1061,6 +1061,12 @@
|
||||
"Size": "サイズ",
|
||||
"Skip": "スキップ",
|
||||
"Skip TLS verification": "TLS 検証をスキップ",
|
||||
"Certificate verification is disabled. Only use this for trusted networks.": "Certificate verification is disabled. Only use this for trusted networks.",
|
||||
"Custom CA certificate": "Custom CA certificate",
|
||||
"Custom CA certificate is required": "Custom CA certificate is required",
|
||||
"Default certificate verification": "Default certificate verification",
|
||||
"Paste the CA certificate in PEM format.": "Paste the CA certificate in PEM format.",
|
||||
"TLS Verification": "TLS Verification",
|
||||
"Skip to main content": "メインコンテンツへスキップ",
|
||||
"Sort by": "並び替え",
|
||||
"Source": "ソース",
|
||||
|
||||
@@ -1061,6 +1061,12 @@
|
||||
"Size": "크기",
|
||||
"Skip": "건너뛰기",
|
||||
"Skip TLS verification": "TLS 검증 건너뛰기",
|
||||
"Certificate verification is disabled. Only use this for trusted networks.": "Certificate verification is disabled. Only use this for trusted networks.",
|
||||
"Custom CA certificate": "Custom CA certificate",
|
||||
"Custom CA certificate is required": "Custom CA certificate is required",
|
||||
"Default certificate verification": "Default certificate verification",
|
||||
"Paste the CA certificate in PEM format.": "Paste the CA certificate in PEM format.",
|
||||
"TLS Verification": "TLS Verification",
|
||||
"Skip to main content": "기본 콘텐츠로 건너뛰기",
|
||||
"Sort by": "정렬 기준",
|
||||
"Source": "소스",
|
||||
|
||||
@@ -1066,6 +1066,12 @@
|
||||
"Size": "Tamanho",
|
||||
"Skip": "Pular",
|
||||
"Skip TLS verification": "Ignorar verificação TLS",
|
||||
"Certificate verification is disabled. Only use this for trusted networks.": "Certificate verification is disabled. Only use this for trusted networks.",
|
||||
"Custom CA certificate": "Custom CA certificate",
|
||||
"Custom CA certificate is required": "Custom CA certificate is required",
|
||||
"Default certificate verification": "Default certificate verification",
|
||||
"Paste the CA certificate in PEM format.": "Paste the CA certificate in PEM format.",
|
||||
"TLS Verification": "TLS Verification",
|
||||
"Skip to main content": "Ir para o conteúdo principal",
|
||||
"Sort by": "Ordenar por",
|
||||
"Source": "Origem",
|
||||
|
||||
@@ -1066,6 +1066,12 @@
|
||||
"Size": "Размер",
|
||||
"Skip": "Пропустить",
|
||||
"Skip TLS verification": "Пропустить проверку TLS",
|
||||
"Certificate verification is disabled. Only use this for trusted networks.": "Certificate verification is disabled. Only use this for trusted networks.",
|
||||
"Custom CA certificate": "Custom CA certificate",
|
||||
"Custom CA certificate is required": "Custom CA certificate is required",
|
||||
"Default certificate verification": "Default certificate verification",
|
||||
"Paste the CA certificate in PEM format.": "Paste the CA certificate in PEM format.",
|
||||
"TLS Verification": "TLS Verification",
|
||||
"Skip to main content": "Перейти к основному содержимому",
|
||||
"Sort by": "Сортировать по",
|
||||
"Source": "Источник",
|
||||
|
||||
@@ -1066,6 +1066,12 @@
|
||||
"Size": "Boyut",
|
||||
"Skip": "Atla",
|
||||
"Skip TLS verification": "TLS doğrulamasını atla",
|
||||
"Certificate verification is disabled. Only use this for trusted networks.": "Certificate verification is disabled. Only use this for trusted networks.",
|
||||
"Custom CA certificate": "Custom CA certificate",
|
||||
"Custom CA certificate is required": "Custom CA certificate is required",
|
||||
"Default certificate verification": "Default certificate verification",
|
||||
"Paste the CA certificate in PEM format.": "Paste the CA certificate in PEM format.",
|
||||
"TLS Verification": "TLS Verification",
|
||||
"Skip to main content": "Ana içeriğe geç",
|
||||
"Sort by": "Göre sırala",
|
||||
"Source": "Kaynak",
|
||||
|
||||
@@ -1066,6 +1066,12 @@
|
||||
"Size": "Kích thước",
|
||||
"Skip": "Bỏ qua",
|
||||
"Skip TLS verification": "Bỏ qua xác minh TLS",
|
||||
"Certificate verification is disabled. Only use this for trusted networks.": "Certificate verification is disabled. Only use this for trusted networks.",
|
||||
"Custom CA certificate": "Custom CA certificate",
|
||||
"Custom CA certificate is required": "Custom CA certificate is required",
|
||||
"Default certificate verification": "Default certificate verification",
|
||||
"Paste the CA certificate in PEM format.": "Paste the CA certificate in PEM format.",
|
||||
"TLS Verification": "TLS Verification",
|
||||
"Skip to main content": "Chuyển đến nội dung chính",
|
||||
"Sort by": "Sắp xếp theo",
|
||||
"Source": "Nguồn",
|
||||
|
||||
@@ -1061,6 +1061,12 @@
|
||||
"Size": "大小",
|
||||
"Skip": "跳过",
|
||||
"Skip TLS verification": "跳过 TLS 校验",
|
||||
"Certificate verification is disabled. Only use this for trusted networks.": "证书校验已禁用。仅可在可信网络中使用。",
|
||||
"Custom CA certificate": "自定义 CA 证书",
|
||||
"Custom CA certificate is required": "必须填写自定义 CA 证书",
|
||||
"Default certificate verification": "默认证书校验",
|
||||
"Paste the CA certificate in PEM format.": "请粘贴 PEM 格式的 CA 证书。",
|
||||
"TLS Verification": "TLS 校验",
|
||||
"Skip to main content": "跳到主要内容",
|
||||
"Sort by": "排序方式",
|
||||
"Source": "来源",
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
export type BucketReplicationTlsMode = "verify" | "custom-ca" | "skip"
|
||||
|
||||
export interface BucketReplicationTlsPayload {
|
||||
skipTlsVerify: boolean
|
||||
caCertPem: string
|
||||
}
|
||||
|
||||
export function buildBucketReplicationTlsPayload(
|
||||
secure: boolean,
|
||||
mode: BucketReplicationTlsMode,
|
||||
caCertPem: string,
|
||||
): BucketReplicationTlsPayload {
|
||||
if (!secure) {
|
||||
return { skipTlsVerify: false, caCertPem: "" }
|
||||
}
|
||||
|
||||
if (mode === "skip") {
|
||||
return { skipTlsVerify: true, caCertPem: "" }
|
||||
}
|
||||
|
||||
if (mode === "custom-ca") {
|
||||
return { skipTlsVerify: false, caCertPem: caCertPem.trim() }
|
||||
}
|
||||
|
||||
return { skipTlsVerify: false, caCertPem: "" }
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
import assert from "node:assert/strict"
|
||||
import test from "node:test"
|
||||
import { readFile } from "node:fs/promises"
|
||||
|
||||
const loadTlsHelpers = () => import(new URL("../../lib/bucket-replication-tls.ts", import.meta.url).href)
|
||||
|
||||
test("bucket replication TLS payload defaults to certificate verification", async () => {
|
||||
const { buildBucketReplicationTlsPayload } = await loadTlsHelpers()
|
||||
|
||||
assert.deepEqual(buildBucketReplicationTlsPayload(true, "verify", "unused"), {
|
||||
skipTlsVerify: false,
|
||||
caCertPem: "",
|
||||
})
|
||||
})
|
||||
|
||||
test("bucket replication TLS payload enables exactly one custom trust mode", async () => {
|
||||
const { buildBucketReplicationTlsPayload } = await loadTlsHelpers()
|
||||
|
||||
assert.deepEqual(buildBucketReplicationTlsPayload(true, "skip", "unused"), {
|
||||
skipTlsVerify: true,
|
||||
caCertPem: "",
|
||||
})
|
||||
assert.deepEqual(buildBucketReplicationTlsPayload(true, "custom-ca", " certificate pem "), {
|
||||
skipTlsVerify: false,
|
||||
caCertPem: "certificate pem",
|
||||
})
|
||||
})
|
||||
|
||||
test("bucket replication TLS payload clears TLS-only settings for HTTP targets", async () => {
|
||||
const { buildBucketReplicationTlsPayload } = await loadTlsHelpers()
|
||||
|
||||
assert.deepEqual(buildBucketReplicationTlsPayload(false, "skip", "certificate pem"), {
|
||||
skipTlsVerify: false,
|
||||
caCertPem: "",
|
||||
})
|
||||
})
|
||||
|
||||
test("bucket replication form submits custom TLS fields and exposes accessible validation", async () => {
|
||||
const source = await readFile(new URL("../../components/replication/new-form.tsx", import.meta.url), "utf8")
|
||||
|
||||
assert.match(source, /buildBucketReplicationTlsPayload/)
|
||||
assert.match(source, /skipTlsVerify/)
|
||||
assert.match(source, /caCertPem/)
|
||||
assert.match(source, /replication-ca-certificate-error/)
|
||||
assert.match(source, /role="alert"/)
|
||||
})
|
||||
Reference in New Issue
Block a user