From 43c51621add32f14da99a8e60f1cd9c8c4f498c8 Mon Sep 17 00:00:00 2001 From: xuans Date: Mon, 31 Aug 2026 01:50:59 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E5=96=84=E8=AF=81=E4=B9=A6=E6=8E=A5?= =?UTF-8?q?=E5=8F=A3=E9=94=99=E8=AF=AF=E4=BF=A1=E6=81=AF=E8=84=B1=E6=95=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/internal/httpapi/certificate_api.go | 10 +++- .../internal/httpapi/certificate_api_test.go | 46 +++++++++++++++++++ .../httpapi/certificate_restore_api.go | 5 +- .../httpapi/certificate_restore_api_test.go | 8 +++- backend/internal/httpapi/renewal_api.go | 5 +- 5 files changed, 69 insertions(+), 5 deletions(-) diff --git a/backend/internal/httpapi/certificate_api.go b/backend/internal/httpapi/certificate_api.go index 61a455d..3585041 100644 --- a/backend/internal/httpapi/certificate_api.go +++ b/backend/internal/httpapi/certificate_api.go @@ -114,7 +114,10 @@ func (s *Server) deployCertificateCurrent(c *gin.Context) { result, report, err := s.deployCertificate(c.Request.Context(), strings.TrimSpace(c.Param("id"))) response := gin.H{"certificate": result, "deployment": report} if err != nil { - response["error"] = err.Error() + if s.logger != nil { + s.logger.Error("证书部署失败", "error", err) + } + response["error"] = "证书部署失败" c.JSON(http.StatusBadGateway, response) return } @@ -136,7 +139,10 @@ func (s *Server) importCertificateFiles(c *gin.Context) { result, report, err := s.importCertificate(c.Request.Context(), request) response := gin.H{"certificate": result, "deployment": report} if err != nil { - response["error"] = err.Error() + if s.logger != nil { + s.logger.Error("证书导入失败", "error", err) + } + response["error"] = "证书导入失败" c.JSON(http.StatusBadGateway, response) return } diff --git a/backend/internal/httpapi/certificate_api_test.go b/backend/internal/httpapi/certificate_api_test.go index cd2b06c..4f0f22c 100644 --- a/backend/internal/httpapi/certificate_api_test.go +++ b/backend/internal/httpapi/certificate_api_test.go @@ -99,6 +99,52 @@ func TestCertificateListHidesReaderErrors(t *testing.T) { t.Fatalf("certificate list did not include the safe error message: %s", body) } } +func TestCertificateDeployAPIHidesWorkflowErrors(t *testing.T) { + server := New(Dependencies{ + Config: readyTestConfig(t.TempDir()), + Repository: &stubDomainRepository{}, + DeployCertificate: func(context.Context, string) (certificate.IssueResult, deployment.Report, error) { + return certificate.IssueResult{}, deployment.Report{}, errors.New("private-key-secret-path") + }, + }) + response := httptest.NewRecorder() + serveAdminHTTP(server, response, httptest.NewRequest(http.MethodPost, "/api/v1/domains/domain-1/deploy", nil)) + if response.Code != http.StatusBadGateway { + t.Fatalf("certificate deployment failure returned an unexpected status: %d %s", response.Code, response.Body.String()) + } + body := response.Body.String() + if strings.Contains(body, "private-key-secret-path") { + t.Fatalf("certificate deployment leaked an internal error: %s", body) + } + if !strings.Contains(body, "\u8bc1\u4e66\u90e8\u7f72\u5931\u8d25") { + t.Fatalf("certificate deployment did not include the safe error message: %s", body) + } +} + +func TestCertificateImportAPIHidesWorkflowErrors(t *testing.T) { + server := New(Dependencies{ + Config: readyTestConfig(t.TempDir()), + Repository: &stubDomainRepository{}, + ImportCertificate: func(context.Context, certificate.ImportRequest) (certificate.IssueResult, deployment.Report, error) { + return certificate.IssueResult{}, deployment.Report{}, errors.New("private-key-secret-path") + }, + }) + body := `{"certificate_path":"/ssl/cert.pem","private_key_path":"/ssl/key.pem"}` + request := httptest.NewRequest(http.MethodPost, "/api/v1/domains/domain-1/certificate/import", strings.NewReader(body)) + request.Header.Set("Content-Type", "application/json") + response := httptest.NewRecorder() + serveAdminHTTP(server, response, request) + if response.Code != http.StatusBadGateway { + t.Fatalf("certificate import failure returned an unexpected status: %d %s", response.Code, response.Body.String()) + } + responseBody := response.Body.String() + if strings.Contains(responseBody, "private-key-secret-path") { + t.Fatalf("certificate import leaked an internal error: %s", responseBody) + } + if !strings.Contains(responseBody, "\u8bc1\u4e66\u5bfc\u5165\u5931\u8d25") { + t.Fatalf("certificate import did not include the safe error message: %s", responseBody) + } +} func TestCertificateImportAPIAcceptsMultipartFiles(t *testing.T) { var received certificate.ImportRequest importer := func(_ context.Context, request certificate.ImportRequest) (certificate.IssueResult, deployment.Report, error) { diff --git a/backend/internal/httpapi/certificate_restore_api.go b/backend/internal/httpapi/certificate_restore_api.go index 5d6995e..e0bab35 100644 --- a/backend/internal/httpapi/certificate_restore_api.go +++ b/backend/internal/httpapi/certificate_restore_api.go @@ -18,10 +18,13 @@ func (s *Server) restoreCertificateVersion(c *gin.Context) { version := strings.TrimSpace(c.Param("version")) result, err := s.restoreVersion(c.Request.Context(), strings.TrimSpace(c.Param("id")), version) if err != nil { + if s.logger != nil { + s.logger.Error("恢复证书历史版本失败", "domain_id", c.Param("id"), "version", version, "error", err) + } if s.audit != nil { _ = s.audit.CreateAuditLog(c.Request.Context(), repository.AuditLog{ Action: "certificate_version_restore", ResourceType: "certificate", ResourceID: c.Param("id"), Status: "failed", - Message: "恢复证书历史版本失败:" + err.Error(), + Message: "恢复证书历史版本失败", }) } s.writeError(c, certificateErrorStatus(err), "恢复证书历史版本失败", err) diff --git a/backend/internal/httpapi/certificate_restore_api_test.go b/backend/internal/httpapi/certificate_restore_api_test.go index 74fd508..c7c50d0 100644 --- a/backend/internal/httpapi/certificate_restore_api_test.go +++ b/backend/internal/httpapi/certificate_restore_api_test.go @@ -82,7 +82,7 @@ func TestRestoreCertificateVersionAPIRecordsSuccessAudit(t *testing.T) { func TestRestoreCertificateVersionAPIRecordsFailureAudit(t *testing.T) { server, audit := newRestoreAPITestServer(t, func(context.Context, string, string) (certificate.IssueResult, error) { - return certificate.IssueResult{}, errors.New("simulated restore failure") + return certificate.IssueResult{}, errors.New("private-key-secret-path") }) request := httptest.NewRequest(http.MethodPost, "/api/v1/domains/domain-1/certificate/versions/bad/restore", nil) request.AddCookie(&http.Cookie{Name: authCookieName, Value: "test-session"}) @@ -91,6 +91,12 @@ func TestRestoreCertificateVersionAPIRecordsFailureAudit(t *testing.T) { if response.Code != http.StatusUnprocessableEntity { t.Fatalf("unexpected failure response: %d %s", response.Code, response.Body.String()) } + if strings.Contains(response.Body.String(), "private-key-secret-path") { + t.Fatalf("certificate restore leaked an internal error: %s", response.Body.String()) + } + if strings.Contains(audit.items[0].Message, "private-key-secret-path") { + t.Fatalf("certificate restore audit leaked an internal error: %+v", audit.items[0]) + } if len(audit.items) != 1 || audit.items[0].Status != "failed" || audit.items[0].Action != "certificate_version_restore" { t.Fatalf("failure audit was not recorded: %+v", audit.items) } diff --git a/backend/internal/httpapi/renewal_api.go b/backend/internal/httpapi/renewal_api.go index d45b16e..fa6ea83 100644 --- a/backend/internal/httpapi/renewal_api.go +++ b/backend/internal/httpapi/renewal_api.go @@ -15,7 +15,10 @@ func (s *Server) runRenewalNow(c *gin.Context) { report, err := s.runRenewal(c.Request.Context()) response := gin.H{"report": report} if err != nil { - response["error"] = err.Error() + if s.logger != nil { + s.logger.Error("执行证书续期失败", "error", err) + } + response["error"] = "执行证书续期失败" c.JSON(http.StatusConflict, response) return }