in traffic_ops/traffic_ops_golang/deliveryservice/autorenewcerts.go [143:309]
func RunAutorenewal(existingCerts []ExistingCerts, cfg *config.Config, ctx context.Context, cancelTx context.CancelFunc, currentUser *auth.CurrentUser, asyncStatusId int, tv trafficvault.TrafficVault) {
defer cancelTx()
db, err := api.GetDB(ctx)
if err != nil {
log.Errorf("Error getting db: %s", err.Error())
if asycErr := api.UpdateAsyncStatus(db, api.AsyncFailed, "ACME renewal failed.", asyncStatusId, true); asycErr != nil {
log.Errorf("updating async status for id %v: %v", asyncStatusId, asycErr)
}
return
}
tx, err := db.Begin()
if err != nil {
log.Errorf("Error getting tx: %s", err.Error())
if asycErr := api.UpdateAsyncStatus(db, api.AsyncFailed, "ACME renewal failed.", asyncStatusId, true); asycErr != nil {
log.Errorf("updating async status for id %v: %v", asyncStatusId, asycErr)
}
return
}
defer tx.Commit()
logTx, err := db.Begin()
if err != nil {
log.Errorf("Error getting logTx: %s", err.Error())
if asycErr := api.UpdateAsyncStatus(db, api.AsyncFailed, "ACME renewal failed.", asyncStatusId, true); asycErr != nil {
log.Errorf("updating async status for id %v: %v", asyncStatusId, asycErr)
}
return
}
defer logTx.Commit()
keysFound := ExpirationSummary{}
renewedCount := 0
errorCount := 0
for _, ds := range existingCerts {
if !ds.Version.Valid || ds.Version.Int64 == 0 {
continue
}
dsExpInfo := DsExpirationInfo{}
keyObj, ok, err := tv.GetDeliveryServiceSSLKeys(ds.XmlId, strconv.Itoa(int(ds.Version.Int64)), tx, ctx)
if err != nil {
log.Errorf("getting ssl keys for xmlId: %s and version: %d : %s", ds.XmlId, ds.Version.Int64, err.Error())
dsExpInfo.XmlId = ds.XmlId
dsExpInfo.Version = util.JSONIntStr(int(ds.Version.Int64))
dsExpInfo.Error = errors.New("getting ssl keys for xmlId: " + ds.XmlId + " and version: " + strconv.Itoa(int(ds.Version.Int64)) + " :" + err.Error())
keysFound.OtherExpirations = append(keysFound.OtherExpirations, dsExpInfo)
continue
}
if !ok {
log.Errorf("no object found for the specified key with xmlId: %s and version: %d", ds.XmlId, ds.Version.Int64)
dsExpInfo.XmlId = ds.XmlId
dsExpInfo.Version = util.JSONIntStr(int(ds.Version.Int64))
dsExpInfo.Error = errors.New("no object found for the specified key with xmlId: " + ds.XmlId + " and version: " + strconv.Itoa(int(ds.Version.Int64)))
keysFound.OtherExpirations = append(keysFound.OtherExpirations, dsExpInfo)
continue
}
err = Base64DecodeCertificate(&keyObj.Certificate)
if err != nil {
log.Errorf("cert autorenewal: error getting SSL keys for XMLID '%s': %s", ds.XmlId, err.Error())
dsExpInfo.XmlId = ds.XmlId
dsExpInfo.Version = util.JSONIntStr(int(ds.Version.Int64))
dsExpInfo.Error = errors.New("decoding the certificate for xmlId: " + ds.XmlId + " and version: " + strconv.Itoa(int(ds.Version.Int64)))
keysFound.OtherExpirations = append(keysFound.OtherExpirations, dsExpInfo)
continue
}
expiration, _, err := ParseExpirationAndSansFromCert([]byte(keyObj.Certificate.Crt), keyObj.Hostname)
if err != nil {
log.Errorf("cert autorenewal: %s: %s", ds.XmlId, err.Error())
dsExpInfo.XmlId = ds.XmlId
dsExpInfo.Version = util.JSONIntStr(int(ds.Version.Int64))
dsExpInfo.Error = errors.New("parsing the expiration for xmlId: " + ds.XmlId + " and version: " + strconv.Itoa(int(ds.Version.Int64)))
keysFound.OtherExpirations = append(keysFound.OtherExpirations, dsExpInfo)
continue
}
// Renew only certificates within configured limit. Default is 30 days.
if cfg.ConfigAcmeRenewal.RenewDaysBeforeExpiration == 0 {
cfg.ConfigAcmeRenewal.RenewDaysBeforeExpiration = 30
}
if expiration.After(time.Now().Add(time.Hour * 24 * time.Duration(cfg.ConfigAcmeRenewal.RenewDaysBeforeExpiration))) {
continue
}
log.Debugf("renewing certificate for xmlId = %s, version = %d, and auth type = %s ", ds.XmlId, ds.Version.Int64, keyObj.AuthType)
newVersion := util.JSONIntStr(keyObj.Version.ToInt64() + 1)
dsExpInfo.XmlId = keyObj.DeliveryService
dsExpInfo.Version = keyObj.Version
dsExpInfo.Expiration = expiration
dsExpInfo.AuthType = keyObj.AuthType
if keyObj.AuthType == tc.LetsEncryptAuthType || (keyObj.AuthType == tc.SelfSignedCertAuthType && cfg.ConfigLetsEncrypt.ConvertSelfSigned) {
req := tc.DeliveryServiceAcmeSSLKeysReq{
DeliveryServiceSSLKeysReq: tc.DeliveryServiceSSLKeysReq{
HostName: &keyObj.Hostname,
DeliveryService: &keyObj.DeliveryService,
CDN: &keyObj.CDN,
Version: &newVersion,
AuthType: &keyObj.AuthType,
Key: &keyObj.Key,
},
}
if err := GetAcmeCertificates(cfg, req, ctx, nil, false, currentUser, 0, tv); err != nil {
dsExpInfo.Error = err
errorCount++
} else {
renewedCount++
}
keysFound.LetsEncryptExpirations = append(keysFound.LetsEncryptExpirations, dsExpInfo)
} else if keyObj.AuthType == tc.SelfSignedCertAuthType {
keysFound.SelfSignedExpirations = append(keysFound.SelfSignedExpirations, dsExpInfo)
} else {
acmeAccount := GetAcmeAccountConfig(cfg, keyObj.AuthType)
if acmeAccount == nil {
keysFound.OtherExpirations = append(keysFound.OtherExpirations, dsExpInfo)
} else {
// background httpCtx since this is run in a goroutine spawned off the original http request
// so the context isn't cancelled when the http connection is closed
userErr, sysErr, statusCode := renewAcmeCerts(cfg, keyObj.DeliveryService, ctx, context.Background(), currentUser, tv)
if userErr != nil {
errorCount++
dsExpInfo.Error = userErr
} else if sysErr != nil {
errorCount++
dsExpInfo.Error = sysErr
} else if statusCode != http.StatusOK {
errorCount++
dsExpInfo.Error = errors.New("Status code not 200: " + strconv.Itoa(statusCode))
} else {
renewedCount++
}
keysFound.AcmeExpirations = append(keysFound.AcmeExpirations, dsExpInfo)
}
}
if asycErr := api.UpdateAsyncStatus(db, api.AsyncPending, "ACME renewal in progress. "+strconv.Itoa(renewedCount)+" certs renewed, "+strconv.Itoa(errorCount)+" errors.", asyncStatusId, false); asycErr != nil {
log.Errorf("updating async status for id %v: %v", asyncStatusId, asycErr)
}
}
// put status as succeeded if any certs were successfully renewed
asyncStatus := api.AsyncSucceeded
if errorCount > 0 && renewedCount == 0 {
asyncStatus = api.AsyncFailed
}
if asycErr := api.UpdateAsyncStatus(db, asyncStatus, "ACME renewal complete. "+strconv.Itoa(renewedCount)+" certs renewed, "+strconv.Itoa(errorCount)+" errors.", asyncStatusId, true); asycErr != nil {
log.Errorf("updating async status for id %v: %v", asyncStatusId, asycErr)
}
if cfg.SMTP.Enabled && cfg.ConfigAcmeRenewal.SummaryEmail != "" {
errCode, userErr, sysErr := AlertExpiringCerts(keysFound, *cfg)
if userErr != nil || sysErr != nil {
log.Errorf("cert autorenewal: sending email: errCode: %d userErr: %v sysErr: %v", errCode, userErr, sysErr)
return
}
}
}