mirror of
https://github.com/1f349/orchid.git
synced 2024-12-21 23:54:12 +00:00
Retry certificate renewal after failure
This commit is contained in:
parent
7e65015b89
commit
d0fc76cd73
@ -75,7 +75,7 @@ FROM certificates AS cert
|
|||||||
WHERE cert.active = 1
|
WHERE cert.active = 1
|
||||||
AND (cert.auto_renew = 1 OR cert.not_after IS NULL)
|
AND (cert.auto_renew = 1 OR cert.not_after IS NULL)
|
||||||
AND cert.renewing = 0
|
AND cert.renewing = 0
|
||||||
AND cert.renew_failed = 0
|
AND DATETIME() > DATETIME(cert.renew_retry)
|
||||||
AND (cert.not_after IS NULL OR DATETIME(cert.not_after, 'utc', '-30 days') < DATETIME())
|
AND (cert.not_after IS NULL OR DATETIME(cert.not_after, 'utc', '-30 days') < DATETIME())
|
||||||
ORDER BY cert.temp_parent, cert.not_after DESC NULLS FIRST
|
ORDER BY cert.temp_parent, cert.not_after DESC NULLS FIRST
|
||||||
LIMIT 1
|
LIMIT 1
|
||||||
@ -107,7 +107,7 @@ SELECT cert.id,
|
|||||||
cert.auto_renew,
|
cert.auto_renew,
|
||||||
cert.active,
|
cert.active,
|
||||||
cert.renewing,
|
cert.renewing,
|
||||||
cert.renew_failed,
|
cert.renew_retry,
|
||||||
cert.not_after,
|
cert.not_after,
|
||||||
cert.updated_at,
|
cert.updated_at,
|
||||||
certificate_domains.domain
|
certificate_domains.domain
|
||||||
@ -116,14 +116,14 @@ FROM certificates AS cert
|
|||||||
`
|
`
|
||||||
|
|
||||||
type FindOwnedCertsRow struct {
|
type FindOwnedCertsRow struct {
|
||||||
ID int64 `json:"id"`
|
ID int64 `json:"id"`
|
||||||
AutoRenew bool `json:"auto_renew"`
|
AutoRenew bool `json:"auto_renew"`
|
||||||
Active bool `json:"active"`
|
Active bool `json:"active"`
|
||||||
Renewing bool `json:"renewing"`
|
Renewing bool `json:"renewing"`
|
||||||
RenewFailed bool `json:"renew_failed"`
|
RenewRetry time.Time `json:"renew_retry"`
|
||||||
NotAfter time.Time `json:"not_after"`
|
NotAfter time.Time `json:"not_after"`
|
||||||
UpdatedAt time.Time `json:"updated_at"`
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
Domain string `json:"domain"`
|
Domain string `json:"domain"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queries) FindOwnedCerts(ctx context.Context) ([]FindOwnedCertsRow, error) {
|
func (q *Queries) FindOwnedCerts(ctx context.Context) ([]FindOwnedCertsRow, error) {
|
||||||
@ -140,7 +140,7 @@ func (q *Queries) FindOwnedCerts(ctx context.Context) ([]FindOwnedCertsRow, erro
|
|||||||
&i.AutoRenew,
|
&i.AutoRenew,
|
||||||
&i.Active,
|
&i.Active,
|
||||||
&i.Renewing,
|
&i.Renewing,
|
||||||
&i.RenewFailed,
|
&i.RenewRetry,
|
||||||
&i.NotAfter,
|
&i.NotAfter,
|
||||||
&i.UpdatedAt,
|
&i.UpdatedAt,
|
||||||
&i.Domain,
|
&i.Domain,
|
||||||
@ -169,10 +169,21 @@ func (q *Queries) RemoveCertificate(ctx context.Context, id int64) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const setRetryFlag = `-- name: SetRetryFlag :exec
|
||||||
|
UPDATE certificates
|
||||||
|
SET renew_retry = DATETIME('now', '+1 day')
|
||||||
|
WHERE id = ?
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) SetRetryFlag(ctx context.Context, id int64) error {
|
||||||
|
_, err := q.db.ExecContext(ctx, setRetryFlag, id)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
const updateCertAfterRenewal = `-- name: UpdateCertAfterRenewal :exec
|
const updateCertAfterRenewal = `-- name: UpdateCertAfterRenewal :exec
|
||||||
UPDATE certificates
|
UPDATE certificates
|
||||||
SET renewing = 0,
|
SET renewing = 0,
|
||||||
renew_failed=0,
|
renew_retry=0,
|
||||||
not_after=?,
|
not_after=?,
|
||||||
updated_at=?
|
updated_at=?
|
||||||
WHERE id = ?
|
WHERE id = ?
|
||||||
@ -191,18 +202,18 @@ func (q *Queries) UpdateCertAfterRenewal(ctx context.Context, arg UpdateCertAfte
|
|||||||
|
|
||||||
const updateRenewingState = `-- name: UpdateRenewingState :exec
|
const updateRenewingState = `-- name: UpdateRenewingState :exec
|
||||||
UPDATE certificates
|
UPDATE certificates
|
||||||
SET renewing = ?,
|
SET renewing = ?,
|
||||||
renew_failed = ?
|
renew_retry = ?
|
||||||
WHERE id = ?
|
WHERE id = ?
|
||||||
`
|
`
|
||||||
|
|
||||||
type UpdateRenewingStateParams struct {
|
type UpdateRenewingStateParams struct {
|
||||||
Renewing bool `json:"renewing"`
|
Renewing bool `json:"renewing"`
|
||||||
RenewFailed bool `json:"renew_failed"`
|
RenewRetry time.Time `json:"renew_retry"`
|
||||||
ID int64 `json:"id"`
|
ID int64 `json:"id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queries) UpdateRenewingState(ctx context.Context, arg UpdateRenewingStateParams) error {
|
func (q *Queries) UpdateRenewingState(ctx context.Context, arg UpdateRenewingStateParams) error {
|
||||||
_, err := q.db.ExecContext(ctx, updateRenewingState, arg.Renewing, arg.RenewFailed, arg.ID)
|
_, err := q.db.ExecContext(ctx, updateRenewingState, arg.Renewing, arg.RenewRetry, arg.ID)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
5
database/migrations/20240920175046_retry_renewal.up.sql
Normal file
5
database/migrations/20240920175046_retry_renewal.up.sql
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
ALTER TABLE certificates
|
||||||
|
DROP COLUMN renew_failed;
|
||||||
|
|
||||||
|
ALTER TABLE certificates
|
||||||
|
ADD COLUMN renew_retry DATETIME NOT NULL DEFAULT 0;
|
@ -10,16 +10,16 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Certificate struct {
|
type Certificate struct {
|
||||||
ID int64 `json:"id"`
|
ID int64 `json:"id"`
|
||||||
Owner string `json:"owner"`
|
Owner string `json:"owner"`
|
||||||
Dns sql.NullInt64 `json:"dns"`
|
Dns sql.NullInt64 `json:"dns"`
|
||||||
AutoRenew bool `json:"auto_renew"`
|
AutoRenew bool `json:"auto_renew"`
|
||||||
Active bool `json:"active"`
|
Active bool `json:"active"`
|
||||||
Renewing bool `json:"renewing"`
|
Renewing bool `json:"renewing"`
|
||||||
RenewFailed bool `json:"renew_failed"`
|
NotAfter time.Time `json:"not_after"`
|
||||||
NotAfter time.Time `json:"not_after"`
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
UpdatedAt time.Time `json:"updated_at"`
|
TempParent sql.NullInt64 `json:"temp_parent"`
|
||||||
TempParent sql.NullInt64 `json:"temp_parent"`
|
RenewRetry time.Time `json:"renew_retry"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type CertificateDomain struct {
|
type CertificateDomain struct {
|
||||||
|
@ -5,7 +5,7 @@ FROM certificates AS cert
|
|||||||
WHERE cert.active = 1
|
WHERE cert.active = 1
|
||||||
AND (cert.auto_renew = 1 OR cert.not_after IS NULL)
|
AND (cert.auto_renew = 1 OR cert.not_after IS NULL)
|
||||||
AND cert.renewing = 0
|
AND cert.renewing = 0
|
||||||
AND cert.renew_failed = 0
|
AND DATETIME() > DATETIME(cert.renew_retry)
|
||||||
AND (cert.not_after IS NULL OR DATETIME(cert.not_after, 'utc', '-30 days') < DATETIME())
|
AND (cert.not_after IS NULL OR DATETIME(cert.not_after, 'utc', '-30 days') < DATETIME())
|
||||||
ORDER BY cert.temp_parent, cert.not_after DESC NULLS FIRST
|
ORDER BY cert.temp_parent, cert.not_after DESC NULLS FIRST
|
||||||
LIMIT 1;
|
LIMIT 1;
|
||||||
@ -15,7 +15,7 @@ SELECT cert.id,
|
|||||||
cert.auto_renew,
|
cert.auto_renew,
|
||||||
cert.active,
|
cert.active,
|
||||||
cert.renewing,
|
cert.renewing,
|
||||||
cert.renew_failed,
|
cert.renew_retry,
|
||||||
cert.not_after,
|
cert.not_after,
|
||||||
cert.updated_at,
|
cert.updated_at,
|
||||||
certificate_domains.domain
|
certificate_domains.domain
|
||||||
@ -24,14 +24,19 @@ FROM certificates AS cert
|
|||||||
|
|
||||||
-- name: UpdateRenewingState :exec
|
-- name: UpdateRenewingState :exec
|
||||||
UPDATE certificates
|
UPDATE certificates
|
||||||
SET renewing = ?,
|
SET renewing = ?,
|
||||||
renew_failed = ?
|
renew_retry = ?
|
||||||
|
WHERE id = ?;
|
||||||
|
|
||||||
|
-- name: SetRetryFlag :exec
|
||||||
|
UPDATE certificates
|
||||||
|
SET renew_retry = DATETIME('now', '+1 day')
|
||||||
WHERE id = ?;
|
WHERE id = ?;
|
||||||
|
|
||||||
-- name: UpdateCertAfterRenewal :exec
|
-- name: UpdateCertAfterRenewal :exec
|
||||||
UPDATE certificates
|
UPDATE certificates
|
||||||
SET renewing = 0,
|
SET renewing = 0,
|
||||||
renew_failed=0,
|
renew_retry=0,
|
||||||
not_after=?,
|
not_after=?,
|
||||||
updated_at=?
|
updated_at=?
|
||||||
WHERE id = ?;
|
WHERE id = ?;
|
||||||
|
@ -399,13 +399,14 @@ func (s *Service) getPrivateKey(id int64) (*rsa.PrivateKey, error) {
|
|||||||
// certificate to the certDir directory.
|
// certificate to the certDir directory.
|
||||||
func (s *Service) renewCert(localData *localCertData) error {
|
func (s *Service) renewCert(localData *localCertData) error {
|
||||||
// database synchronous state
|
// database synchronous state
|
||||||
s.setRenewing(localData.id, true, false)
|
s.setRenewing(localData.id, true)
|
||||||
Logger.Debug("No certificates to update")
|
Logger.Debug("No certificates to update")
|
||||||
|
|
||||||
// run internal renewal code and log errors
|
// run internal renewal code and log errors
|
||||||
cert, certBytes, err := s.renewCertInternal(localData)
|
cert, certBytes, err := s.renewCertInternal(localData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.setRenewing(localData.id, false, true)
|
s.setRenewing(localData.id, false)
|
||||||
|
s.setRetry(localData.id)
|
||||||
return fmt.Errorf("failed to renew cert %d: %w", localData.id, err)
|
return fmt.Errorf("failed to renew cert %d: %w", localData.id, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -501,14 +502,20 @@ func (s *Service) renewCertInternal(localData *localCertData) (*x509.Certificate
|
|||||||
|
|
||||||
// setRenewing sets the renewing and failed states in the database for a
|
// setRenewing sets the renewing and failed states in the database for a
|
||||||
// specified certificate id.
|
// specified certificate id.
|
||||||
func (s *Service) setRenewing(id int64, renewing, failed bool) {
|
func (s *Service) setRenewing(id int64, renewing bool) {
|
||||||
err := s.db.UpdateRenewingState(context.Background(), database.UpdateRenewingStateParams{
|
err := s.db.UpdateRenewingState(context.Background(), database.UpdateRenewingStateParams{
|
||||||
Renewing: renewing,
|
Renewing: renewing,
|
||||||
RenewFailed: failed,
|
ID: id,
|
||||||
ID: id,
|
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
Logger.Warn("Failed to set renewing/failed mode in database", "id", id, "err", err)
|
Logger.Warn("Failed to set renewing mode in database", "id", id, "err", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) setRetry(id int64) {
|
||||||
|
err := s.db.SetRetryFlag(context.Background(), id)
|
||||||
|
if err != nil {
|
||||||
|
Logger.Warn("Failed to set retry time in database", "id", id, "err", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -170,7 +170,7 @@ func TestPebbleRenewal(t *testing.T) {
|
|||||||
_, err := db2.Exec("DELETE FROM certificate_domains")
|
_, err := db2.Exec("DELETE FROM certificate_domains")
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
_, err = db2.Exec(`INSERT INTO certificates (owner, dns, auto_renew, active, renewing, renew_failed, not_after, updated_at) VALUES (1, 1, 1, 1, 0, 0, "2000-01-01 00:00:00+00:00", "2000-01-01 00:00:00+00:00")`)
|
_, err = db2.Exec(`INSERT INTO certificates (owner, dns, auto_renew, active, renewing, renew_retry, not_after, updated_at) VALUES (1, 1, 1, 1, 0, '2000-01-01 00:00:00+00:00', '2000-01-01 00:00:00+00:00', '2000-01-01 00:00:00+00:00');`)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
for _, j := range i.domains {
|
for _, j := range i.domains {
|
||||||
_, err = db2.Exec(`INSERT INTO certificate_domains (cert_id, domain) VALUES (1, ?)`, j)
|
_, err = db2.Exec(`INSERT INTO certificate_domains (cert_id, domain) VALUES (1, ?)`, j)
|
||||||
|
@ -25,14 +25,14 @@ type DomainStateValue struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Certificate struct {
|
type Certificate struct {
|
||||||
Id int64 `json:"id"`
|
Id int64 `json:"id"`
|
||||||
AutoRenew bool `json:"auto_renew"`
|
AutoRenew bool `json:"auto_renew"`
|
||||||
Active bool `json:"active"`
|
Active bool `json:"active"`
|
||||||
Renewing bool `json:"renewing"`
|
Renewing bool `json:"renewing"`
|
||||||
RenewFailed bool `json:"renew_failed"`
|
RenewRetry time.Time `json:"renew_retry"`
|
||||||
NotAfter time.Time `json:"not_after"`
|
NotAfter time.Time `json:"not_after"`
|
||||||
UpdatedAt time.Time `json:"updated_at"`
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
Domains []string `json:"domains"`
|
Domains []string `json:"domains"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewApiServer creates and runs a http server containing all the API
|
// NewApiServer creates and runs a http server containing all the API
|
||||||
@ -69,13 +69,13 @@ func NewApiServer(listen string, db *database.Queries, signer *mjwt.KeyStore, do
|
|||||||
// loop over query rows
|
// loop over query rows
|
||||||
for _, row := range rows {
|
for _, row := range rows {
|
||||||
c := Certificate{
|
c := Certificate{
|
||||||
Id: row.ID,
|
Id: row.ID,
|
||||||
AutoRenew: row.AutoRenew,
|
AutoRenew: row.AutoRenew,
|
||||||
Active: row.Active,
|
Active: row.Active,
|
||||||
Renewing: row.Renewing,
|
Renewing: row.Renewing,
|
||||||
RenewFailed: row.RenewFailed,
|
RenewRetry: row.RenewRetry,
|
||||||
NotAfter: row.NotAfter,
|
NotAfter: row.NotAfter,
|
||||||
UpdatedAt: row.UpdatedAt,
|
UpdatedAt: row.UpdatedAt,
|
||||||
}
|
}
|
||||||
d := row.Domain
|
d := row.Domain
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user