func RunAutorenewal()

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
		}

	}
}