Skip to content
This repository was archived by the owner on Nov 24, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/).

### Fixed
- Fixed Traffic Router crs/stats to prevent overflow and to correctly record the time used in averages.
- [#5893](https://github.com/apache/trafficcontrol/issues/5893) - A self signed certificate is created when an HTTPS delivery service is created or an HTTP delivery service is updated to HTTPS.

### Changed
- Updated `t3c` to request less unnecessary deliveryservice-server assignment and invalidation jobs data via new query params supported by Traffic Ops
Expand Down
8 changes: 8 additions & 0 deletions docs/source/admin/traffic_ops.rst
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,14 @@ This file deals with the configuration parameters of running Traffic Ops itself.
:renew_days_before_expiration: Set the number of days before expiration date to renew certificates.
:summary_email: The email address to use for summarizing certificate expiration and renewal status. If it is blank, no email will be sent.

:default_certificate_info: This is an optional object to define default values when generating a self signed certificate when an HTTPS delivery service is created or updated. If this is an empty object or not present in the :ref:`cdn.conf` then the term "Placeholder" will be used for all fields.

:business_unit: An optional field which, if present, will represent the business unit for which the SSL certificate was generated
:city: An optional field which, if present, will represent the resident city of the generated SSL certificate
:organization: An optional field which, if present, will represent the organization for which the SSL certificate was generated
:country: An optional field which, if present, will represent the resident country of the generated SSL certificate
:state: An optional field which, if present, will represent the resident state or province of the generated SSL certificate

:geniso: This object contains configuration options for system ISO generation.

:iso_root_path: Sets the filesystem path to the root of the ISO generation directory. For default installations, this should usually be set to :file:`/opt/traffic_ops/app/public`.
Expand Down
9 changes: 8 additions & 1 deletion traffic_ops/app/conf/cdn.conf
Original file line number Diff line number Diff line change
Expand Up @@ -90,5 +90,12 @@
"kid" : "",
"hmac_encoded" : ""
}
]
],
"default_certificate_info" : {
"business_unit" : "",
"city" : "",
"organization" : "",
"country" : "",
"state" : ""
}
}
40 changes: 39 additions & 1 deletion traffic_ops/testing/api/v4/deliveryservices_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ func TestDeliveryServices(t *testing.T) {
header.Set(rfc.IfModifiedSince, ti)
header.Set(rfc.IfUnmodifiedSince, ti)
if includeSystemTests {
t.Run("Verify SSL key generation on DS creation", VerifySSLKeysOnDsCreationTest)
t.Run("Update CDN for a Delivery Service with SSL keys", SSLDeliveryServiceCDNUpdateTest)
t.Run("Create URL Signature keys for a Delivery Service", CreateTestDeliveryServicesURLSignatureKeys)
t.Run("Retrieve URL Signature keys for a Delivery Service", GetTestDeliveryServicesURLSignatureKeys)
Expand Down Expand Up @@ -137,7 +138,7 @@ func CUDDeliveryServiceWithLocks(t *testing.T) {
if len(types.Response) < 1 {
t.Fatal("expected at least one type")
}
customDS := getCustomDS(cdn.ID, types.Response[0].ID, "cdn_locks_test_ds_name", "routingName", "https://test_cdn_locks.com", "cdn_locks_test_ds_xml_id")
customDS := getCustomDS(cdn.ID, types.Response[0].ID, "cdn-locks-test-ds-name", "edge", "https://test-cdn-locks.com", "cdn-locks-test-ds-xml-id")

// Create a lock for this user
_, _, err = userSession.CreateCDNLock(tc.CDNLock{
Expand Down Expand Up @@ -706,6 +707,43 @@ func DeliveryServiceSSLKeys(t *testing.T) {
}
}

func VerifySSLKeysOnDsCreationTest(t *testing.T) {
for _, ds := range testData.DeliveryServices {
if !(*ds.Protocol == tc.DSProtocolHTTPS || *ds.Protocol == tc.DSProtocolHTTPAndHTTPS || *ds.Protocol == tc.DSProtocolHTTPToHTTPS) {
continue
}
var err error
dsSSLKey := new(tc.DeliveryServiceSSLKeys)
for tries := 0; tries < 5; tries++ {
time.Sleep(time.Second)
var sslKeysResp tc.DeliveryServiceSSLKeysResponse
sslKeysResp, _, err = TOSession.GetDeliveryServiceSSLKeys(*ds.XMLID, client.RequestOptions{})
*dsSSLKey = sslKeysResp.Response
if err == nil && dsSSLKey != nil {
break
}
}

if err != nil || dsSSLKey == nil {
t.Fatalf("unable to get DS %s SSL key: %v", *ds.XMLID, err)
}
if dsSSLKey.Certificate.Key == "" {
t.Errorf("expected a valid key but got nothing")
}
if dsSSLKey.Certificate.Crt == "" {
t.Errorf("expected a valid certificate, but got nothing")
}
if dsSSLKey.Certificate.CSR == "" {
t.Errorf("expected a valid CSR, but got nothing")
}

err = deliveryservice.Base64DecodeCertificate(&dsSSLKey.Certificate)
if err != nil {
t.Fatalf("couldn't decode certificate: %v", err)
}
}
}

func SSLDeliveryServiceCDNUpdateTest(t *testing.T) {
cdnNameOld := "sslkeytransfer"
oldCdn := createBlankCDN(cdnNameOld, t)
Expand Down
37 changes: 35 additions & 2 deletions traffic_ops/traffic_ops_golang/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,9 @@ type Config struct {
InfluxEnabled bool
InfluxDBConfPath string `json:"influxdb_conf_path"`
Version string
UseIMS bool `json:"use_ims"`
RoleBasedPermissions bool `json:"role_based_permissions"`
UseIMS bool `json:"use_ims"`
RoleBasedPermissions bool `json:"role_based_permissions"`
DefaultCertificateInfo *DefaultCertificateInfo `json:"default_certificate_info"`
}

// ConfigHypnotoad carries http setting for hypnotoad (mojolicious) server
Expand Down Expand Up @@ -173,6 +174,38 @@ type ConfigAcmeAccount struct {
HmacEncoded string `json:"hmac_encoded"`
}

type DefaultCertificateInfo struct {
Comment thread
rawlinp marked this conversation as resolved.
Outdated
BusinessUnit string `json:"business_unit"`
City string `json:"city"`
Organization string `json:"organization"`
Country string `json:"country"`
State string `json:"state"`
}

func (d *DefaultCertificateInfo) Validate() (error, bool) {
missingList := []string{}
if d.BusinessUnit == "" {
missingList = append(missingList, "BusinessUnit")
}
if d.City == "" {
missingList = append(missingList, "City")
}
if d.Organization == "" {
missingList = append(missingList, "Organization")
}
if d.Country == "" {
missingList = append(missingList, "Country")
}
if d.State == "" {
missingList = append(missingList, "State")
}

if len(missingList) != 0 {
return fmt.Errorf("default certificate information is missing: %s", missingList), false
}
return nil, true
}

// ConfigDatabase reflects the structure of the database.conf file
type ConfigDatabase struct {
Description string `json:"description"`
Expand Down
11 changes: 11 additions & 0 deletions traffic_ops/traffic_ops_golang/dbhelpers/db_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -1603,3 +1603,14 @@ func GetDSIDFromStaticDNSEntry(tx *sql.Tx, staticDNSEntryID int) (int, error) {
}
return dsID, nil
}

// GetCDNNameDomain returns the name and domain for a given CDN ID.
func GetCDNNameDomain(cdnID int, tx *sql.Tx) (string, string, error) {
q := `SELECT cdn.name, cdn.domain_name from cdn where cdn.id = $1`
cdnName := ""
cdnDomain := ""
if err := tx.QueryRow(q, cdnID).Scan(&cdnName, &cdnDomain); err != nil {
return "", "", fmt.Errorf("getting cdn name and domain for cdn '%v': "+err.Error(), cdnID)
}
return cdnName, cdnDomain, nil
}
17 changes: 17 additions & 0 deletions traffic_ops/traffic_ops_golang/deliveryservice/deliveryservices.go
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,7 @@ func CreateV40(w http.ResponseWriter, r *http.Request) {
}
alerts := res.TLSVersionsAlerts()
alerts.AddNewAlert(tc.SuccessLevel, "Delivery Service creation was successful")

w.Header().Set("Location", fmt.Sprintf("/api/4.0/deliveryservices?id=%d", *res.ID))
api.WriteAlertsObj(w, r, http.StatusCreated, alerts, []tc.DeliveryServiceV40{*res})
}
Expand Down Expand Up @@ -548,6 +549,13 @@ func createV40(w http.ResponseWriter, r *http.Request, inf *api.APIInfo, dsV40 t

dsV40 = ds

if inf.Config.TrafficVaultEnabled && ds.Protocol != nil && (*ds.Protocol == tc.DSProtocolHTTPS || *ds.Protocol == tc.DSProtocolHTTPAndHTTPS || *ds.Protocol == tc.DSProtocolHTTPToHTTPS) {
err, errCode := GeneratePlaceholderSelfSignedCert(dsV40, inf, r.Context())
if err != nil || errCode != http.StatusOK {
return nil, errCode, nil, fmt.Errorf("creating self signed default cert: %v", err)
}
}

return &dsV40, http.StatusOK, nil, nil
}

Expand Down Expand Up @@ -715,6 +723,7 @@ func UpdateV40(w http.ResponseWriter, r *http.Request) {
}
alerts := res.TLSVersionsAlerts()
alerts.AddNewAlert(tc.SuccessLevel, "Delivery Service update was successful")

api.WriteAlertsObj(w, r, http.StatusOK, alerts, []tc.DeliveryServiceV40{*res})
}

Expand Down Expand Up @@ -1124,6 +1133,14 @@ func updateV40(w http.ResponseWriter, r *http.Request, inf *api.APIInfo, dsV40 *
}

dsV40 = (*tc.DeliveryServiceV40)(&ds)

if inf.Config.TrafficVaultEnabled && ds.Protocol != nil && (*ds.Protocol == tc.DSProtocolHTTPS || *ds.Protocol == tc.DSProtocolHTTPAndHTTPS || *ds.Protocol == tc.DSProtocolHTTPToHTTPS) {
err, errCode := GeneratePlaceholderSelfSignedCert(*dsV40, inf, r.Context())
if err != nil || errCode != http.StatusOK {
return nil, errCode, nil, fmt.Errorf("creating self signed default cert: %v", err)
}
}

return dsV40, http.StatusOK, nil, nil
}

Expand Down
80 changes: 77 additions & 3 deletions traffic_ops/traffic_ops_golang/deliveryservice/sslkeys.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,12 @@ import (
"errors"
"net/http"
"strconv"
"strings"

"github.com/apache/trafficcontrol/lib/go-tc"
"github.com/apache/trafficcontrol/lib/go-util"
"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/api"
"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/config"
"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/dbhelpers"
"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/tenant"
"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/trafficvault"
Expand Down Expand Up @@ -70,7 +73,7 @@ func GenerateSSLKeys(w http.ResponseWriter, r *http.Request) {
api.HandleErr(w, r, inf.Tx.Tx, statusCode, userErr, sysErr)
return
}
if err := generatePutRiakKeys(req, inf.Tx.Tx, inf.Vault, r.Context()); err != nil {
if err := generatePutTrafficVaultSSLKeys(req, inf.Tx.Tx, inf.Vault, r.Context()); err != nil {
api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, nil, errors.New("generating and putting SSL keys: "+err.Error()))
return
}
Expand All @@ -82,9 +85,9 @@ func GenerateSSLKeys(w http.ResponseWriter, r *http.Request) {
api.WriteResp(w, r, "Successfully created ssl keys for "+*req.DeliveryService)
}

// generatePutRiakKeys generates a certificate, csr, and key from the given request, and insert it into the Riak key database.
// generatePutTrafficVaultSSLKeys generates a certificate, csr, and key from the given request, and insert it into the Riak key database.
// The req MUST be validated, ensuring required fields exist.
func generatePutRiakKeys(req tc.DeliveryServiceGenSSLKeysReq, tx *sql.Tx, tv trafficvault.TrafficVault, ctx context.Context) error {
func generatePutTrafficVaultSSLKeys(req tc.DeliveryServiceGenSSLKeysReq, tx *sql.Tx, tv trafficvault.TrafficVault, ctx context.Context) error {
dsSSLKeys := tc.DeliveryServiceSSLKeys{
CDN: *req.CDN,
DeliveryService: *req.DeliveryService,
Expand All @@ -110,3 +113,74 @@ func generatePutRiakKeys(req tc.DeliveryServiceGenSSLKeysReq, tx *sql.Tx, tv tra
}
return nil
}

// GeneratePlaceholderSelfSignedCert generates a self-signed SSL certificate as a placeholder when a new HTTPS
// delivery service is created or an HTTP delivery service is updated to use HTTPS.
func GeneratePlaceholderSelfSignedCert(ds tc.DeliveryServiceV4, inf *api.APIInfo, context context.Context) (error, int) {
tx := inf.Tx.Tx
tv := inf.Vault
_, ok, err := tv.GetDeliveryServiceSSLKeys(*ds.XMLID, "", tx, context)
if err != nil {
return errors.New("getting latest ssl keys for xmlId: " + *ds.XMLID + " : " + err.Error()), http.StatusInternalServerError
}
if ok {
return nil, http.StatusOK
}

version := util.JSONIntStr(1)

cdnName, cdnDomain, err := dbhelpers.GetCDNNameDomain(*ds.CDNID, tx)
if err != nil {
return err, http.StatusInternalServerError
}

cdnNameStr := string(cdnName)

if ds.ExampleURLs == nil {
ds.ExampleURLs = MakeExampleURLs(ds.Protocol, *ds.Type, *ds.RoutingName, *ds.MatchList, cdnDomain)
}

hostname := strings.Split(ds.ExampleURLs[0], "://")[1]
if ds.Type.IsHTTP() {
parts := strings.Split(hostname, ".")
parts[0] = "*"
hostname = strings.Join(parts, ".")
}

req := tc.DeliveryServiceGenSSLKeysReq{
DeliveryServiceSSLKeysReq: tc.DeliveryServiceSSLKeysReq{
CDN: &cdnNameStr,
DeliveryService: ds.XMLID,
HostName: &hostname,
Key: ds.XMLID,
Version: &version,
BusinessUnit: util.StrPtr("Placeholder"),
City: util.StrPtr("Placeholder"),
Organization: util.StrPtr("Placeholder"),
Country: util.StrPtr("Placeholder"),
State: util.StrPtr("Placeholder"),
},
}

if (inf.Config.DefaultCertificateInfo != nil && *inf.Config.DefaultCertificateInfo != config.DefaultCertificateInfo{}) {
defaultCertInfo := inf.Config.DefaultCertificateInfo
if err, ok := defaultCertInfo.Validate(); !ok {
return err, http.StatusInternalServerError
}

req.BusinessUnit = &defaultCertInfo.BusinessUnit
req.City = &defaultCertInfo.City
req.Organization = &defaultCertInfo.Organization
req.Country = &defaultCertInfo.Country
req.State = &defaultCertInfo.State
}

if err := generatePutTrafficVaultSSLKeys(req, tx, inf.Vault, context); err != nil {
return errors.New("generating and putting SSL keys: " + err.Error()), http.StatusInternalServerError
}
if err := updateSSLKeyVersion(*req.DeliveryService, req.Version.ToInt64(), tx); err != nil {
return errors.New("generating SSL keys for delivery service '" + *req.DeliveryService + "': " + err.Error()), http.StatusInternalServerError
}

return nil, http.StatusOK
}