mirror of
https://github.com/certimate-go/certimate.git
synced 2026-06-13 21:01:32 +08:00
feat: support uploading ip addresses certificates
This commit is contained in:
parent
773e4fad36
commit
761454d2e2
@ -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
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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()))
|
||||
|
||||
@ -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
|
||||
|
||||
35
pkg/utils/cert/x509/x509.go
Normal file
35
pkg/utils/cert/x509/x509.go
Normal file
@ -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
|
||||
}
|
||||
@ -48,7 +48,7 @@ export const validateCertificate = async (certificate: string) => {
|
||||
|
||||
type RespData = {
|
||||
isValid: boolean;
|
||||
domains: string;
|
||||
subjectAltNames: string;
|
||||
};
|
||||
const resp = await pb.send<BaseResponse<RespData>>(`/api/certificates/pre-validate/certificate`, {
|
||||
method: "POST",
|
||||
|
||||
@ -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
|
||||
</Form.Item>
|
||||
|
||||
<Show when={fieldSource === UPLOAD_SOURCE_FORM}>
|
||||
<Form.Item name="domains" label={t("workflow_node.upload.form.domains.label")} rules={[formRule]}>
|
||||
<Input variant="filled" placeholder={t("workflow_node.upload.form.domains.placeholder")} readOnly />
|
||||
<Form.Item name="name" label={t("workflow_node.upload.form.name.label")} rules={[formRule]}>
|
||||
<Input variant="filled" placeholder={t("workflow_node.upload.form.name.placeholder")} readOnly />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item name="certificate" label={t("workflow_node.upload.form.certificate_pem.label")} rules={[formRule]}>
|
||||
@ -198,9 +198,9 @@ const getSchema = ({ i18n = getI18n() }: { i18n?: ReturnType<typeof getI18n> })
|
||||
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) {
|
||||
|
||||
@ -47,8 +47,8 @@ export const BizUploadNodeRegistry: NodeRegistry = {
|
||||
{({ field: { value: fieldSource } }) => (
|
||||
<>
|
||||
{fieldSource == null || fieldSource === "" || fieldSource === "form" ? (
|
||||
<Field<string> name="config.domains">
|
||||
{({ field: { value: fieldDomains } }) => <>{fieldDomains || t("workflow.detail.design.editor.placeholder")}</>}
|
||||
<Field<string> name="config.name">
|
||||
{({ field: { value: fieldName } }) => <>{fieldName || t("workflow.detail.design.editor.placeholder")}</>}
|
||||
</Field>
|
||||
) : (
|
||||
<Field<string> name="config.certificate">
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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": "证书文件路径",
|
||||
|
||||
Loading…
Reference in New Issue
Block a user