diff --git a/internal/certificate/service.go b/internal/certificate/service.go index c8e667c6..fc607b8e 100644 --- a/internal/certificate/service.go +++ b/internal/certificate/service.go @@ -18,6 +18,7 @@ import ( "github.com/certimate-go/certimate/internal/domain" "github.com/certimate-go/certimate/internal/domain/dtos" xcert "github.com/certimate-go/certimate/pkg/utils/cert" + xcertx509 "github.com/certimate-go/certimate/pkg/utils/cert/x509" xcryptokey "github.com/certimate-go/certimate/pkg/utils/crypto/key" ) @@ -255,8 +256,8 @@ func (s *CertificateService) ValidateCertificate(ctx context.Context, req *dtos. } return &dtos.CertificateValidateCertificateResp{ - IsValid: true, - Domains: strings.Join(certX509.DNSNames, ";"), + IsValid: true, + SubjectAltNames: strings.Join(xcertx509.GetSubjectAltNames(certX509), ";"), }, nil } diff --git a/internal/domain/certificate.go b/internal/domain/certificate.go index bbd3a508..5f202d88 100644 --- a/internal/domain/certificate.go +++ b/internal/domain/certificate.go @@ -9,6 +9,7 @@ import ( "github.com/go-acme/lego/v4/certcrypto" xcert "github.com/certimate-go/certimate/pkg/utils/cert" + xcertx509 "github.com/certimate-go/certimate/pkg/utils/cert/x509" xcryptokey "github.com/certimate-go/certimate/pkg/utils/crypto/key" ) @@ -39,7 +40,7 @@ type Certificate struct { } func (c *Certificate) PopulateFromX509(certX509 *x509.Certificate) *Certificate { - c.SubjectAltNames = strings.Join(certX509.DNSNames, ";") + c.SubjectAltNames = strings.Join(xcertx509.GetSubjectAltNames(certX509), ";") c.SerialNumber = strings.ToUpper(certX509.SerialNumber.Text(16)) c.IssuerOrg = strings.Join(certX509.Issuer.Organization, ";") c.ValidityNotBefore = certX509.NotBefore diff --git a/internal/domain/dtos/certificate.go b/internal/domain/dtos/certificate.go index 6b721f6c..b5617988 100644 --- a/internal/domain/dtos/certificate.go +++ b/internal/domain/dtos/certificate.go @@ -21,8 +21,8 @@ type CertificateValidateCertificateReq struct { } type CertificateValidateCertificateResp struct { - IsValid bool `json:"isValid"` - Domains string `json:"domains,omitempty"` + IsValid bool `json:"isValid"` + SubjectAltNames string `json:"subjectAltNames,omitempty"` } type CertificateValidatePrivateKeyReq struct { diff --git a/internal/workflow/engine/executor_bizmonitor.go b/internal/workflow/engine/executor_bizmonitor.go index 1b45e730..b26cc7ed 100644 --- a/internal/workflow/engine/executor_bizmonitor.go +++ b/internal/workflow/engine/executor_bizmonitor.go @@ -12,6 +12,7 @@ import ( "time" "github.com/certimate-go/certimate/internal/repository" + xcertx509 "github.com/certimate-go/certimate/pkg/utils/cert/x509" xhttp "github.com/certimate-go/certimate/pkg/utils/http" xtls "github.com/certimate-go/certimate/pkg/utils/tls" ) @@ -84,7 +85,7 @@ func (ne *bizMonitorNodeExecutor) Execute(execCtx *NodeExecutionContext) (*NodeE ne.logger.Info(fmt.Sprintf("ssl certificate retrieved (serial='%s', subject='%s', issuer='%s', not_before='%s', not_after='%s', sans='%s')", cert.SerialNumber, cert.Subject.String(), cert.Issuer.String(), cert.NotBefore.Format(time.RFC3339), cert.NotAfter.Format(time.RFC3339), - strings.Join(cert.DNSNames, ";")), + strings.Join(xcertx509.GetSubjectAltNames(cert), ";")), ) ne.setVariablesOfResult(execCtx, execRes, cert) @@ -164,7 +165,7 @@ func (ne *bizMonitorNodeExecutor) setVariablesOfResult(execCtx *NodeExecutionCon if certX509 != nil { vCommonName = certX509.Subject.CommonName - vSubjectAltNames = strings.Join(certX509.DNSNames, ";") + vSubjectAltNames = strings.Join(xcertx509.GetSubjectAltNames(certX509), ";") vNotBefore = certX509.NotBefore vNotAfter = certX509.NotAfter vHoursLeft = int32(math.Floor(time.Until(certX509.NotAfter).Hours())) diff --git a/migrations/1766419200_upgrade_v0.4.11.go b/migrations/1766419200_upgrade_v0.4.11.go index cd014753..57ef4529 100644 --- a/migrations/1766419200_upgrade_v0.4.11.go +++ b/migrations/1766419200_upgrade_v0.4.11.go @@ -1,12 +1,168 @@ package migrations import ( + "github.com/go-viper/mapstructure/v2" "github.com/pocketbase/pocketbase/core" m "github.com/pocketbase/pocketbase/migrations" ) func init() { m.Register(func(app core.App) error { + tracer := NewTracer("v0.4.11") + tracer.Printf("go ...") + // adapt to new workflow data structure + { + walker := &mWorkflowGraphWalker{} + walker.Define(func(node *mWorkflowNode) (_changed bool, _err error) { + _changed = false + _err = nil + + if node.Type != "bizUpload" { + return + } + + if node.Data == nil { + return + } + + if _, ok := node.Data["config"]; ok { + nodeCfg := node.Data["config"].(map[string]any) + + if nodeCfg["domains"] != nil { + nodeCfg["name"] = nodeCfg["domains"] + delete(nodeCfg, "domains") + + node.Data["config"] = nodeCfg + _changed = true + return + } + } + + return + }) + + // update collection `workflow` + // - migrate field `graphDraft` / `graphContent` + { + collection, err := app.FindCollectionByNameOrId("tovyif5ax6j62ur") + if err != nil { + return err + } + + records, err := app.FindAllRecords(collection) + if err != nil { + return err + } + + for _, record := range records { + changed := false + + if record.GetRaw("graphDraft") != nil { + graph := make(map[string]any) + if err := record.UnmarshalJSONField("graphDraft", &graph); err != nil { + return err + } + + if _, ok := graph["nodes"]; ok { + nodes := make([]*mWorkflowNode, 0) + if err := mapstructure.Decode(graph["nodes"], &nodes); err != nil { + return err + } + + nodesChanged, err := walker.Visit(nodes) + if err != nil { + return err + } else if nodesChanged { + graph["nodes"] = nodes + record.Set("graphDraft", graph) + changed = true + } + } + } + + if record.GetRaw("graphContent") != nil { + graph := make(map[string]any) + if err := record.UnmarshalJSONField("graphContent", &graph); err != nil { + return err + } + + if _, ok := graph["nodes"]; ok { + nodes := make([]*mWorkflowNode, 0) + if err := mapstructure.Decode(graph["nodes"], &nodes); err != nil { + return err + } + + nodesChanged, err := walker.Visit(nodes) + if err != nil { + return err + } else if nodesChanged { + graph["nodes"] = nodes + record.Set("graphContent", graph) + changed = true + } + } + } + + if changed { + if err := app.Save(record); err != nil { + return err + } + + tracer.Printf("record #%s in collection '%s' updated", record.Id, collection.Name) + } + } + } + + // update collection `workflow_run` + // - migrate field `graph` + { + collection, err := app.FindCollectionByNameOrId("qjp8lygssgwyqyz") + if err != nil { + return err + } + + records, err := app.FindAllRecords(collection) + if err != nil { + return err + } + + for _, record := range records { + changed := false + + if record.GetRaw("graph") != nil { + graph := make(map[string]any) + if err := record.UnmarshalJSONField("graph", &graph); err != nil { + return err + } + + if _, ok := graph["nodes"]; ok { + nodes := make([]*mWorkflowNode, 0) + if err := mapstructure.Decode(graph["nodes"], &nodes); err != nil { + return err + } + + nodesChanged, err := walker.Visit(nodes) + if err != nil { + return err + } else if nodesChanged { + graph["nodes"] = nodes + record.Set("graph", graph) + changed = true + } + } + } + + if changed { + if err := app.Save(record); err != nil { + return err + } + + tracer.Printf("record #%s in collection '%s' updated", record.Id, collection.Name) + } + } + } + } + // clean old migrations { migrations := []string{ @@ -23,6 +179,7 @@ func init() { } } + tracer.Printf("done") return nil }, func(app core.App) error { return nil diff --git a/pkg/utils/cert/x509/x509.go b/pkg/utils/cert/x509/x509.go new file mode 100644 index 00000000..7a5ab6a2 --- /dev/null +++ b/pkg/utils/cert/x509/x509.go @@ -0,0 +1,35 @@ +package x509 + +import ( + "crypto/x509" +) + +// 返回指定 x509.Certificate 对象的主题替代名称。 +// +// 入参: +// - cert: x509.Certificate 对象。 +// +// 出参: +// - 主题替代名称的字符串切片。 +func GetSubjectAltNames(cert *x509.Certificate) []string { + sans := make([]string, 0) + + if cert != nil { + for _, dnsName := range cert.DNSNames { + sans = append(sans, dnsName) + } + for _, ipAddr := range cert.IPAddresses { + sans = append(sans, ipAddr.String()) + } + for _, email := range cert.EmailAddresses { + sans = append(sans, email) + } + for _, uri := range cert.URIs { + if uri != nil { + sans = append(sans, uri.String()) + } + } + } + + return sans +} diff --git a/ui/src/api/certificates.ts b/ui/src/api/certificates.ts index c815f9f3..bc4b0b15 100644 --- a/ui/src/api/certificates.ts +++ b/ui/src/api/certificates.ts @@ -48,7 +48,7 @@ export const validateCertificate = async (certificate: string) => { type RespData = { isValid: boolean; - domains: string; + subjectAltNames: string; }; const resp = await pb.send>(`/api/certificates/pre-validate/certificate`, { method: "POST", diff --git a/ui/src/components/workflow/designer/forms/BizUploadNodeConfigForm.tsx b/ui/src/components/workflow/designer/forms/BizUploadNodeConfigForm.tsx index f89bc2f8..2924d44b 100644 --- a/ui/src/components/workflow/designer/forms/BizUploadNodeConfigForm.tsx +++ b/ui/src/components/workflow/designer/forms/BizUploadNodeConfigForm.tsx @@ -49,12 +49,12 @@ const BizUploadNodeConfigForm = ({ node, ...props }: BizUploadNodeConfigFormProp const handleSourceChange = (value: string) => { if (value === initialValues?.source) { - formInst.resetFields(["certificate", "privateKey", "domains"]); + formInst.resetFields(["certificate", "privateKey", "name"]); } else { setTimeout(() => { formInst.setFieldValue("certificate", ""); formInst.setFieldValue("privateKey", ""); - formInst.setFieldValue("domains", ""); + formInst.setFieldValue("name", ""); }, 0); } }; @@ -64,8 +64,8 @@ const BizUploadNodeConfigForm = ({ node, ...props }: BizUploadNodeConfigFormProp const resp = await validateCertificate(value); formInst.setFields([ { - name: "domains", - value: resp.data.domains, + name: "name", + value: resp.data.subjectAltNames, }, { name: "certificate", @@ -75,7 +75,7 @@ const BizUploadNodeConfigForm = ({ node, ...props }: BizUploadNodeConfigFormProp } catch (e) { formInst.setFields([ { - name: "domains", + name: "name", value: "", }, { @@ -120,8 +120,8 @@ const BizUploadNodeConfigForm = ({ node, ...props }: BizUploadNodeConfigFormProp - - + + @@ -198,9 +198,9 @@ const getSchema = ({ i18n = getI18n() }: { i18n?: ReturnType }) return z .object({ source: z.enum([UPLOAD_SOURCE_FORM, UPLOAD_SOURCE_LOCAL, UPLOAD_SOURCE_URL], t("workflow_node.upload.form.source.placeholder")), + name: z.string().nullish(), certificate: z.string().nonempty(), privateKey: z.string().nonempty(), - domains: z.string().nullish(), }) .superRefine((values, ctx) => { switch (values.source) { diff --git a/ui/src/components/workflow/designer/nodes/BizUploadNodeRegistry.tsx b/ui/src/components/workflow/designer/nodes/BizUploadNodeRegistry.tsx index c4421341..3b2efb88 100644 --- a/ui/src/components/workflow/designer/nodes/BizUploadNodeRegistry.tsx +++ b/ui/src/components/workflow/designer/nodes/BizUploadNodeRegistry.tsx @@ -47,8 +47,8 @@ export const BizUploadNodeRegistry: NodeRegistry = { {({ field: { value: fieldSource } }) => ( <> {fieldSource == null || fieldSource === "" || fieldSource === "form" ? ( - name="config.domains"> - {({ field: { value: fieldDomains } }) => <>{fieldDomains || t("workflow.detail.design.editor.placeholder")}} + name="config.name"> + {({ field: { value: fieldName } }) => <>{fieldName || t("workflow.detail.design.editor.placeholder")}} ) : ( name="config.certificate"> diff --git a/ui/src/i18n/locales/en/nls.workflow.nodes.json b/ui/src/i18n/locales/en/nls.workflow.nodes.json index e52705f7..b0fc95d0 100644 --- a/ui/src/i18n/locales/en/nls.workflow.nodes.json +++ b/ui/src/i18n/locales/en/nls.workflow.nodes.json @@ -145,8 +145,8 @@ "workflow_node.upload.form.source.option.form.label": "Form", "workflow_node.upload.form.source.option.local.label": "Local path", "workflow_node.upload.form.source.option.url.label": "URL", - "workflow_node.upload.form.domains.label": "Domains", - "workflow_node.upload.form.domains.placholder": "Please select certificate file", + "workflow_node.upload.form.name.label": "Domains / IP addresses", + "workflow_node.upload.form.name.placholder": "Please select certificate file", "workflow_node.upload.form.certificate_pem.label": "Certificate (PEM format)", "workflow_node.upload.form.certificate_pem.placeholder": "-----BEGIN CERTIFICATE-----...-----END CERTIFICATE-----", "workflow_node.upload.form.certificate_path.label": "Certificate file path", diff --git a/ui/src/i18n/locales/zh/nls.workflow.nodes.json b/ui/src/i18n/locales/zh/nls.workflow.nodes.json index bd0e0b14..c6aa392b 100644 --- a/ui/src/i18n/locales/zh/nls.workflow.nodes.json +++ b/ui/src/i18n/locales/zh/nls.workflow.nodes.json @@ -144,8 +144,8 @@ "workflow_node.upload.form.source.option.form.label": "表单", "workflow_node.upload.form.source.option.local.label": "本地路径", "workflow_node.upload.form.source.option.url.label": "URL 路径", - "workflow_node.upload.form.domains.label": "域名", - "workflow_node.upload.form.domains.placeholder": "上传证书文件后显示", + "workflow_node.upload.form.name.label": "证书名称", + "workflow_node.upload.form.name.placeholder": "上传证书文件后显示", "workflow_node.upload.form.certificate_pem.label": "证书文件(PEM 格式)", "workflow_node.upload.form.certificate_pem.placeholder": "-----BEGIN CERTIFICATE-----...-----END CERTIFICATE-----", "workflow_node.upload.form.certificate_path.label": "证书文件路径",