diff --git a/internal/api/v1/cert/info.go b/internal/api/v1/cert/info.go index 882f3763..412642e7 100644 --- a/internal/api/v1/cert/info.go +++ b/internal/api/v1/cert/info.go @@ -1,6 +1,7 @@ package certapi import ( + "errors" "net/http" "github.com/gin-gonic/gin" @@ -8,46 +9,33 @@ import ( apitypes "github.com/yusing/goutils/apitypes" ) -type CertInfo struct { - Subject string `json:"subject"` - Issuer string `json:"issuer"` - NotBefore int64 `json:"not_before"` - NotAfter int64 `json:"not_after"` - DNSNames []string `json:"dns_names"` - EmailAddresses []string `json:"email_addresses"` -} // @name CertInfo - // @x-id "info" // @BasePath /api/v1 // @Summary Get cert info // @Description Get cert info // @Tags cert // @Produce json -// @Success 200 {object} CertInfo -// @Failure 403 {object} apitypes.ErrorResponse -// @Failure 404 {object} apitypes.ErrorResponse -// @Failure 500 {object} apitypes.ErrorResponse -// @Router /cert/info [get] +// @Success 200 {array} autocert.CertInfo +// @Failure 403 {object} apitypes.ErrorResponse "Unauthorized" +// @Failure 404 {object} apitypes.ErrorResponse "No certificates found or autocert is not enabled" +// @Failure 500 {object} apitypes.ErrorResponse "Internal server error" +// @Router /cert/info [get] func Info(c *gin.Context) { - autocert := autocert.ActiveProvider.Load() - if autocert == nil { + provider := autocert.ActiveProvider.Load() + if provider == nil { c.JSON(http.StatusNotFound, apitypes.Error("autocert is not enabled")) return } - cert, err := autocert.GetCert(nil) + certInfos, err := provider.GetCertInfos() if err != nil { + if errors.Is(err, autocert.ErrNoCertificates) { + c.JSON(http.StatusNotFound, apitypes.Error("no certificate found")) + return + } c.Error(apitypes.InternalServerError(err, "failed to get cert info")) return } - certInfo := CertInfo{ - Subject: cert.Leaf.Subject.CommonName, - Issuer: cert.Leaf.Issuer.CommonName, - NotBefore: cert.Leaf.NotBefore.Unix(), - NotAfter: cert.Leaf.NotAfter.Unix(), - DNSNames: cert.Leaf.DNSNames, - EmailAddresses: cert.Leaf.EmailAddresses, - } - c.JSON(http.StatusOK, certInfo) + c.JSON(http.StatusOK, certInfos) } diff --git a/internal/api/v1/docs/swagger.json b/internal/api/v1/docs/swagger.json index da35ecc3..aad503bf 100644 --- a/internal/api/v1/docs/swagger.json +++ b/internal/api/v1/docs/swagger.json @@ -328,23 +328,26 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/CertInfo" + "type": "array", + "items": { + "$ref": "#/definitions/CertInfo" + } } }, "403": { - "description": "Forbidden", + "description": "Unauthorized", "schema": { "$ref": "#/definitions/ErrorResponse" } }, "404": { - "description": "Not Found", + "description": "No certificates found or autocert is not enabled", "schema": { "$ref": "#/definitions/ErrorResponse" } }, "500": { - "description": "Internal Server Error", + "description": "Internal server error", "schema": { "$ref": "#/definitions/ErrorResponse" } diff --git a/internal/api/v1/docs/swagger.yaml b/internal/api/v1/docs/swagger.yaml index 1865cedd..e4dd69b5 100644 --- a/internal/api/v1/docs/swagger.yaml +++ b/internal/api/v1/docs/swagger.yaml @@ -1886,17 +1886,19 @@ paths: "200": description: OK schema: - $ref: '#/definitions/CertInfo' + items: + $ref: '#/definitions/CertInfo' + type: array "403": - description: Forbidden + description: Unauthorized schema: $ref: '#/definitions/ErrorResponse' "404": - description: Not Found + description: No certificates found or autocert is not enabled schema: $ref: '#/definitions/ErrorResponse' "500": - description: Internal Server Error + description: Internal server error schema: $ref: '#/definitions/ErrorResponse' summary: Get cert info diff --git a/internal/autocert/provider.go b/internal/autocert/provider.go index 4be580c2..df3e56a8 100644 --- a/internal/autocert/provider.go +++ b/internal/autocert/provider.go @@ -55,10 +55,20 @@ type ( } CertExpiries map[string]time.Time - RenewMode uint8 + + CertInfo struct { + Subject string `json:"subject"` + Issuer string `json:"issuer"` + NotBefore int64 `json:"not_before"` + NotAfter int64 `json:"not_after"` + DNSNames []string `json:"dns_names"` + EmailAddresses []string `json:"email_addresses"` + } // @name CertInfo + + RenewMode uint8 ) -var ErrNoCertificate = errors.New("no certificate found") +var ErrNoCertificates = errors.New("no certificates found") const ( // renew failed for whatever reason, 1 hour cooldown @@ -98,7 +108,7 @@ func NewProvider(cfg *Config, user *User, legoCfg *lego.Config) (*Provider, erro func (p *Provider) GetCert(hello *tls.ClientHelloInfo) (*tls.Certificate, error) { if p.tlsCert == nil { - return nil, ErrNoCertificate + return nil, ErrNoCertificates } if hello == nil || hello.ServerName == "" { return p.tlsCert, nil @@ -109,6 +119,29 @@ func (p *Provider) GetCert(hello *tls.ClientHelloInfo) (*tls.Certificate, error) return p.tlsCert, nil } +func (p *Provider) GetCertInfos() ([]CertInfo, error) { + allProviders := p.allProviders() + certInfos := make([]CertInfo, 0, len(allProviders)) + for _, provider := range allProviders { + if provider.tlsCert == nil { + continue + } + certInfos = append(certInfos, CertInfo{ + Subject: provider.tlsCert.Leaf.Subject.CommonName, + Issuer: provider.tlsCert.Leaf.Issuer.CommonName, + NotBefore: provider.tlsCert.Leaf.NotBefore.Unix(), + NotAfter: provider.tlsCert.Leaf.NotAfter.Unix(), + DNSNames: provider.tlsCert.Leaf.DNSNames, + EmailAddresses: provider.tlsCert.Leaf.EmailAddresses, + }) + } + + if len(certInfos) == 0 { + return nil, ErrNoCertificates + } + return certInfos, nil +} + func (p *Provider) GetName() string { if p.cfg.idx == 0 { return "main"