in traffic_ops/traffic_ops_golang/deliveryservice/acme.go [330:540]
func GetAcmeCertificates(cfg *config.Config, req tc.DeliveryServiceAcmeSSLKeysReq, ctx context.Context, cancelTx context.CancelFunc, shouldCancelTx bool, currentUser *auth.CurrentUser, asyncStatusId int, tv trafficvault.TrafficVault) error {
defer func() {
if shouldCancelTx {
defer cancelTx()
}
if err := recover(); err != nil {
db, dbErr := api.GetDB(ctx)
if dbErr != nil {
log.Errorf(*req.DeliveryService+": Error getting db for recover async update: %s", dbErr.Error())
log.Errorf("panic: (err: %v) stacktrace:\n%s\n", err, util.Stacktrace())
return
}
if asyncErr := api.UpdateAsyncStatus(db, api.AsyncFailed, "ACME renewal failed.", asyncStatusId, true); asyncErr != nil {
log.Errorf("updating async status for id %v: %v", asyncStatusId, asyncErr)
}
log.Errorf("panic: (err: %v) stacktrace:\n%s\n", err, util.Stacktrace())
return
}
}()
db, err := api.GetDB(ctx)
if err != nil {
log.Errorf(*req.DeliveryService+": 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 err
}
tx, err := db.Begin()
if err != nil {
log.Errorf(*req.DeliveryService+": 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 err
}
userTx, err := db.Begin()
if err != nil {
log.Errorf(*req.DeliveryService+": Error getting userTx: %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 err
}
defer userTx.Commit()
logTx, err := db.Begin()
if err != nil {
log.Errorf(*req.DeliveryService+": 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 err
}
defer logTx.Commit()
domainName := *req.HostName
deliveryService := *req.DeliveryService
provider := *req.AuthType
dsID, _, ok, err := getDSIDAndCDNIDFromName(tx, *req.DeliveryService)
if err != nil {
log.Errorf("deliveryservice.GenerateSSLKeys: getting DS ID from name " + err.Error())
api.CreateChangeLogRawTx(api.ApiChange, "DS: "+*req.DeliveryService+", ID: "+strconv.Itoa(dsID)+", ACTION: FAILED to add SSL keys with "+provider, currentUser, logTx)
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 fmt.Errorf("deliveryservice.GenerateSSLKeys: getting DS ID from name: %v", err)
} else if !ok {
log.Errorf("no DS with name " + *req.DeliveryService)
api.CreateChangeLogRawTx(api.ApiChange, "DS: "+*req.DeliveryService+", ID: "+strconv.Itoa(dsID)+", ACTION: FAILED to add SSL keys with "+provider, currentUser, logTx)
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 errors.New("no DS with name " + *req.DeliveryService)
}
tx.Commit()
if cfg == nil {
log.Errorf("acme: config was nil for provider %s", provider)
api.CreateChangeLogRawTx(api.ApiChange, "DS: "+*req.DeliveryService+", ID: "+strconv.Itoa(dsID)+", ACTION: FAILED to add SSL keys with "+provider, currentUser, logTx)
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 errors.New("acme: config was nil")
}
var account *config.ConfigAcmeAccount
if provider == tc.LetsEncryptAuthType {
letsEncryptAccount := config.ConfigAcmeAccount{
UserEmail: cfg.ConfigLetsEncrypt.Email,
AcmeProvider: tc.LetsEncryptAuthType,
}
if strings.EqualFold(cfg.ConfigLetsEncrypt.Environment, "staging") {
letsEncryptAccount.AcmeUrl = lego.LEDirectoryStaging // provides certificate signed by invalid authority for testing purposes
} else {
letsEncryptAccount.AcmeUrl = lego.LEDirectoryProduction // provides certificate signed by valid LE authority
}
account = &letsEncryptAccount
} else {
acmeAccount := GetAcmeAccountConfig(cfg, provider)
if acmeAccount == nil {
log.Errorf("acme: no account information found for %s", provider)
api.CreateChangeLogRawTx(api.ApiChange, "DS: "+*req.DeliveryService+", ID: "+strconv.Itoa(dsID)+", ACTION: FAILED to add SSL keys with "+provider, currentUser, logTx)
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 errors.New("No acme account information in cdn.conf for " + provider)
}
account = acmeAccount
}
client, err := GetAcmeClient(account, userTx, db, req.Key)
if err != nil {
log.Errorf("acme: getting acme client for provider %s: %v", provider, err)
api.CreateChangeLogRawTx(api.ApiChange, "DS: "+*req.DeliveryService+", ID: "+strconv.Itoa(dsID)+", ACTION: FAILED to add SSL keys with "+provider, currentUser, logTx)
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 fmt.Errorf("getting acme client: %v", err)
}
priv, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
log.Errorf(deliveryService + ": Error generating private key: " + err.Error())
api.CreateChangeLogRawTx(api.ApiChange, "DS: "+*req.DeliveryService+", ID: "+strconv.Itoa(dsID)+", ACTION: FAILED to add SSL keys with "+provider, currentUser, logTx)
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 err
}
request := certificate.ObtainRequest{
Domains: []string{domainName},
Bundle: true,
PrivateKey: priv,
}
certificates, err := client.Certificate.Obtain(request)
if err != nil {
log.Errorf(deliveryService+": Error obtaining acme certificate from %s: %s", provider, err.Error())
api.CreateChangeLogRawTx(api.ApiChange, "DS: "+*req.DeliveryService+", ID: "+strconv.Itoa(dsID)+", ACTION: FAILED to add SSL keys with "+provider, currentUser, logTx)
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 err
}
// Save certs into Traffic Vault
dsSSLKeys := tc.DeliveryServiceSSLKeys{
AuthType: provider,
CDN: *req.CDN,
DeliveryService: *req.DeliveryService,
Key: *req.DeliveryService,
Hostname: *req.HostName,
Version: *req.Version,
}
keyPem, err := ConvertPrivateKeyToKeyPem(priv)
if err != nil {
log.Errorf(deliveryService + ": Error converting private key to PEM: " + err.Error())
api.CreateChangeLogRawTx(api.ApiChange, "DS: "+*req.DeliveryService+", ID: "+strconv.Itoa(dsID)+", ACTION: FAILED to add SSL keys with "+provider, currentUser, logTx)
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 err
}
// remove extra line if LE returns it
trimmedCert := bytes.ReplaceAll(certificates.Certificate, []byte("\n\n"), []byte("\n"))
dsSSLKeys.Certificate = tc.DeliveryServiceSSLKeysCertificate{
Crt: string(EncodePEMToLegacyPerlRiakFormat(trimmedCert)),
Key: string(EncodePEMToLegacyPerlRiakFormat(keyPem)),
CSR: string(EncodePEMToLegacyPerlRiakFormat([]byte("ACME Generated"))),
}
if err := tv.PutDeliveryServiceSSLKeys(dsSSLKeys, tx, context.Background()); err != nil {
log.Errorf("Error putting ACME certificate in Traffic Vault: %s", err.Error())
api.CreateChangeLogRawTx(api.ApiChange, "DS: "+*req.DeliveryService+", ID: "+strconv.Itoa(dsID)+", ACTION: FAILED to add SSL keys with "+provider, currentUser, logTx)
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 fmt.Errorf(deliveryService+": putting keys in Traffic Vault: %v", err)
}
tx2, err := db.Begin()
if err != nil {
log.Errorf("starting sql transaction for delivery service " + *req.DeliveryService + ": " + 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 fmt.Errorf("starting sql transaction for delivery service "+*req.DeliveryService+": %v", err)
}
if err := updateSSLKeyVersion(*req.DeliveryService, req.Version.ToInt64(), tx2); err != nil {
log.Errorf("updating SSL key version for delivery service '" + *req.DeliveryService + "': " + 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 fmt.Errorf("updating SSL key version for delivery service '"+*req.DeliveryService+"': %v", err)
}
tx2.Commit()
api.CreateChangeLogRawTx(api.ApiChange, "DS: "+*req.DeliveryService+", ID: "+strconv.Itoa(dsID)+", ACTION: Added SSL keys with "+provider, currentUser, logTx)
if asycErr := api.UpdateAsyncStatus(db, api.AsyncSucceeded, "ACME renewal complete.", asyncStatusId, true); asycErr != nil {
log.Errorf("updating async status for id %v: %v", asyncStatusId, asycErr)
}
return nil
}