Le regex fix (#6184)

* Lets Encrypt updated for domains that don't contain the xmlid

* updated changelog
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6759428..88afdf0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -122,6 +122,7 @@
 - Fixed Federations IMS so TR federations watcher will get updates.
 - [#5129](https://github.com/apache/trafficcontrol/issues/5129) - Updated TM so that it returns a 404 if the endpoint is not supported.
 - [#5992](https://github.com/apache/trafficcontrol/issues/5992) - Updated Traffic Router Integration tests to use a mock Traffic Monitor and Traffic Ops server
+- [#6093](https://github.com/apache/trafficcontrol/issues/6093) - Fixed Let's Encrypt to work for delivery services where the domain does not contain the XMLID.
 
 ### Changed
 - Migrated completely off of bower in favor of npm
diff --git a/traffic_ops/app/db/migrations/2021090914220900_le_dns_challenge_xml_id.down.sql b/traffic_ops/app/db/migrations/2021090914220900_le_dns_challenge_xml_id.down.sql
new file mode 100644
index 0000000..d2e7e61
--- /dev/null
+++ b/traffic_ops/app/db/migrations/2021090914220900_le_dns_challenge_xml_id.down.sql
@@ -0,0 +1,18 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership.  The ASF
+ * licenses this file to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+ALTER TABLE dnschallenges DROP COLUMN IF EXISTS xml_id;
diff --git a/traffic_ops/app/db/migrations/2021090914220900_le_dns_challenge_xml_id.up.sql b/traffic_ops/app/db/migrations/2021090914220900_le_dns_challenge_xml_id.up.sql
new file mode 100644
index 0000000..64df6a0
--- /dev/null
+++ b/traffic_ops/app/db/migrations/2021090914220900_le_dns_challenge_xml_id.up.sql
@@ -0,0 +1,18 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership.  The ASF
+ * licenses this file to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+ALTER TABLE dnschallenges ADD COLUMN xml_id text NOT NULL;
diff --git a/traffic_ops/traffic_ops_golang/deliveryservice/acme.go b/traffic_ops/traffic_ops_golang/deliveryservice/acme.go
index 044a466..e89b8e6 100644
--- a/traffic_ops/traffic_ops_golang/deliveryservice/acme.go
+++ b/traffic_ops/traffic_ops_golang/deliveryservice/acme.go
@@ -82,7 +82,8 @@
 
 // DNSProviderTrafficRouter is used in the lego library and contains a database in order to store the DNS challenges for ACME protocol.
 type DNSProviderTrafficRouter struct {
-	db *sqlx.DB
+	db    *sqlx.DB
+	xmlId *string
 }
 
 // NewDNSProviderTrafficRouter returns a new DNSProviderTrafficRouter object.
@@ -100,8 +101,8 @@
 	tx, err := d.db.Begin()
 	fqdn, value := dns01.GetRecord(domain, keyAuth)
 
-	q := `INSERT INTO dnschallenges (fqdn, record) VALUES ($1, $2)`
-	response, err := tx.Exec(q, fqdn, value)
+	q := `INSERT INTO dnschallenges (fqdn, record, xml_id) VALUES ($1, $2, $3)`
+	response, err := tx.Exec(q, fqdn, value, *d.xmlId)
 	tx.Commit()
 	if err != nil {
 		log.Errorf("Inserting dns txt record for fqdn '" + fqdn + "' record '" + value + "': " + err.Error())
@@ -439,7 +440,7 @@
 		account = acmeAccount
 	}
 
-	client, err := GetAcmeClient(account, userTx, db)
+	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)
@@ -560,7 +561,7 @@
 }
 
 // GetAcmeClient uses the ACME account information in either cdn.conf or the database to create and register an ACME client.
-func GetAcmeClient(acmeAccount *config.ConfigAcmeAccount, userTx *sql.Tx, db *sqlx.DB) (*lego.Client, error) {
+func GetAcmeClient(acmeAccount *config.ConfigAcmeAccount, userTx *sql.Tx, db *sqlx.DB, xmlId *string) (*lego.Client, error) {
 	if acmeAccount.UserEmail == "" {
 		log.Errorf("An email address must be provided to use ACME with %v", acmeAccount.AcmeProvider)
 		return nil, errors.New("An email address must be provided to use ACME with " + acmeAccount.AcmeProvider)
@@ -610,6 +611,7 @@
 		client.Challenge.Remove(challenge.TLSALPN01)
 		trafficRouterDns := NewDNSProviderTrafficRouter()
 		trafficRouterDns.db = db
+		trafficRouterDns.xmlId = xmlId
 		if err != nil {
 			log.Errorf("Error creating Traffic Router DNS provider: %s", err.Error())
 			return nil, err
diff --git a/traffic_ops/traffic_ops_golang/deliveryservice/acme_renew.go b/traffic_ops/traffic_ops_golang/deliveryservice/acme_renew.go
index 7b92526..eb7c78a 100644
--- a/traffic_ops/traffic_ops_golang/deliveryservice/acme_renew.go
+++ b/traffic_ops/traffic_ops_golang/deliveryservice/acme_renew.go
@@ -145,7 +145,7 @@
 		return nil, errors.New("No acme account information in cdn.conf for " + keyObj.AuthType), http.StatusInternalServerError
 	}
 
-	client, err := GetAcmeClient(acmeAccount, userTx, db)
+	client, err := GetAcmeClient(acmeAccount, userTx, db, &dsName)
 	if err != nil {
 		api.CreateChangeLogRawTx(api.ApiChange, "DS: "+dsName+", ID: "+strconv.Itoa(*dsID)+", ACTION: FAILED to add SSL keys with "+acmeAccount.AcmeProvider, currentUser, logTx)
 		return nil, errors.New("getting acme client: " + err.Error()), http.StatusInternalServerError
diff --git a/traffic_ops/traffic_ops_golang/deliveryservice/letsencrypt_dns_challenge.go b/traffic_ops/traffic_ops_golang/deliveryservice/letsencrypt_dns_challenge.go
index 45fb560..a9af8cd 100644
--- a/traffic_ops/traffic_ops_golang/deliveryservice/letsencrypt_dns_challenge.go
+++ b/traffic_ops/traffic_ops_golang/deliveryservice/letsencrypt_dns_challenge.go
@@ -33,6 +33,7 @@
 type DnsRecord struct {
 	Fqdn   *string `json:"fqdn" db:"fqdn"`
 	Record *string `json:"record" db:"record"`
+	XmlId  *string `json:"xmlId" db:"xml_id"`
 }
 
 func GetDnsChallengeRecords(w http.ResponseWriter, r *http.Request) {
@@ -43,7 +44,7 @@
 	}
 	defer inf.Close()
 
-	getQuery := `SELECT fqdn, record FROM dnschallenges`
+	getQuery := `SELECT fqdn, record, xml_id FROM dnschallenges`
 
 	queryParamsToQueryCols := map[string]dbhelpers.WhereColumnInfo{
 		"fqdn": dbhelpers.WhereColumnInfo{Column: "fqdn"},
@@ -73,7 +74,7 @@
 	defer rows.Close()
 	for rows.Next() {
 		record := DnsRecord{}
-		if err := rows.Scan(&record.Fqdn, &record.Record); err != nil {
+		if err := rows.Scan(&record.Fqdn, &record.Record, &record.XmlId); err != nil {
 			return nil, errors.New("scanning dns challenge records: " + err.Error())
 		}
 		records = append(records, record)
diff --git a/traffic_router/core/src/main/java/org/apache/traffic_control/traffic_router/core/ds/LetsEncryptDnsChallenge.java b/traffic_router/core/src/main/java/org/apache/traffic_control/traffic_router/core/ds/LetsEncryptDnsChallenge.java
index 8261ba0..2260825 100644
--- a/traffic_router/core/src/main/java/org/apache/traffic_control/traffic_router/core/ds/LetsEncryptDnsChallenge.java
+++ b/traffic_router/core/src/main/java/org/apache/traffic_control/traffic_router/core/ds/LetsEncryptDnsChallenge.java
@@ -26,6 +26,9 @@
     @JsonProperty
     private String record;
 
+    @JsonProperty
+    private String xmlId;
+
     public String getFqdn() {
         return fqdn;
     }
@@ -42,6 +45,14 @@
         this.record = record;
     }
 
+    public String getXmlId() {
+        return xmlId;
+    }
+
+    public void setXmlId(final String xmlId) {
+        this.xmlId = xmlId;
+    }
+
     @Override
     public boolean equals(final Object o) {
         if (this == o) {
@@ -52,11 +63,12 @@
         }
         final LetsEncryptDnsChallenge that = (LetsEncryptDnsChallenge) o;
         return fqdn.equals(that.fqdn) &&
-                record.equals(that.record);
+                record.equals(that.record) &&
+                xmlId.equals(that.xmlId);
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(fqdn, record);
+        return Objects.hash(fqdn, record, xmlId);
     }
 }
diff --git a/traffic_router/core/src/main/java/org/apache/traffic_control/traffic_router/core/ds/LetsEncryptDnsChallengeWatcher.java b/traffic_router/core/src/main/java/org/apache/traffic_control/traffic_router/core/ds/LetsEncryptDnsChallengeWatcher.java
index 957bf06..5ebc89c 100644
--- a/traffic_router/core/src/main/java/org/apache/traffic_control/traffic_router/core/ds/LetsEncryptDnsChallengeWatcher.java
+++ b/traffic_router/core/src/main/java/org/apache/traffic_control/traffic_router/core/ds/LetsEncryptDnsChallengeWatcher.java
@@ -31,6 +31,7 @@
 import java.io.*;
 import java.time.Instant;
 import java.util.HashMap;
+import java.util.Iterator;
 import java.util.List;
 
 public class LetsEncryptDnsChallengeWatcher extends AbstractResourceWatcher {
@@ -57,35 +58,33 @@
 
 
             challengeList.forEach(challenge -> {
-                final StringBuilder sb = new StringBuilder();
-                sb.append(challenge.getFqdn());
-                if (!challenge.getFqdn().endsWith(".")) {
-                    sb.append('.');
-                }
-                final String challengeDomain = sb.toString();
-                final String fqdn = challengeDomain.substring(0, challengeDomain.length() - 1).replace("_acme-challenge.", "");
-
-                ObjectNode deliveryServiceConfig = null;
-                String dsLabel = "";
-                final StringBuilder nameSb = new StringBuilder();
-                nameSb.append("_acme-challenge");
-                for (final String label : fqdn.split("\\.")) {
-                    deliveryServiceConfig = (ObjectNode) deliveryServicesNode.get(label);
-                    if (deliveryServiceConfig != null) {
-                        dsLabel = label;
-                        break;
-                    } else {
-                        nameSb.append('.');
-                        nameSb.append(label);
-                    }
+                final ObjectNode deliveryServiceConfig = (ObjectNode) deliveryServicesNode.get(challenge.getXmlId());
+                if (deliveryServiceConfig == null) {
+                    LOGGER.error("finding deliveryservice in cr-config for " + challenge.getXmlId());
+                    return;
                 }
 
-                final String name = nameSb.toString();
+                String staticEntryString = challenge.getFqdn();
+                final ArrayNode domains = (ArrayNode) deliveryServiceConfig.get("domains");
+                if (domains == null || domains.size() == 0) {
+                    LOGGER.error("no domains found in cr-config for deliveryservice " + challenge.getXmlId());
+                    return;
+                }
 
-                final ArrayNode staticDnsEntriesNode = updateStaticEntries(challenge, name, mapper, deliveryServiceConfig);
+                final Iterator<JsonNode> domainIter = domains.iterator();
+                while(domainIter.hasNext()) {
+                    final JsonNode domainNode = domainIter.next();
+                    staticEntryString = staticEntryString.replace(domainNode.asText() + ".", "");
+                }
+
+                if (staticEntryString.endsWith(".")) {
+                    staticEntryString = staticEntryString.substring(0, staticEntryString.length() - 1);
+                }
+
+                final ArrayNode staticDnsEntriesNode = updateStaticEntries(challenge, staticEntryString, mapper, deliveryServiceConfig);
 
                 deliveryServiceConfig.set("staticDnsEntries", staticDnsEntriesNode);
-                deliveryServicesNode.set(dsLabel, deliveryServiceConfig);
+                deliveryServicesNode.set(challenge.getXmlId(), deliveryServiceConfig);
 
             });