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 @@ -5,6 +5,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/).

## [unreleased]
### Added
- [#7089](https://github.com/apache/trafficcontrol/issues/7089) *Traffic Router* Added the ability to specify HTTPS certificate attributes.
- [#7109](https://github.com/apache/trafficcontrol/pull/7109) *Traffic Router* Removed `dnssec.zone.diffing.enabled` and `dnssec.rrsig.cache.enabled` parameters.
- [#7075](https://github.com/apache/trafficcontrol/pull/7075) *Traffic Portal* Added the `lastUpdated` field to all delivery service forms.
- [#7055](https://github.com/apache/trafficcontrol/issues/7055) *Traffic Portal* Made `Clear Table Filters` option visible to the user.
Expand Down
27 changes: 27 additions & 0 deletions docs/source/development/traffic_router/traffic_router_api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,33 @@ To override the self signed certificates with new ones from a certificate author
The API can be configured via HTTPS on port 3443 in :file:`/opt/traffic_router/conf/server.xml` or by setting a :term:`Parameter` named ``secure.api.port`` with ``configFile`` ``server.xml`` on the Traffic Router's :term:`Profile`. When ``systemctl start traffic_router`` is run, it will generate self signed certificates at ``/opt/traffic_router/conf/``, create a new Java Keystore named :file:`/opt/traffic_router/conf/keyStore.jks`, and add the new certificate to the Keystore. The password for the Java Keystore and the Keystore location are stored in :file:`/opt/traffic_router/conf/https.properties`.
To override the self signed certificates with new ones from a certificate authority, either replace the Java Keystore in the default location or update the properties for the new Keystore location and password at :file:`/opt/traffic_router/conf/https.properties` and then restart the Traffic Router using ``systemctl``.

Other attributes of the default certificate can also be customized by specifying appropriate values for the following properties in :file:`/opt/traffic_router/conf/https.properties`. These properties are listed below:

.. table:: HTTPS Certificate Attributes

+------------------------------------------+------------------------------------------------------------------------+---------------------------------------------------------+
| Name | Description | Default |
+==========================================+========================================================================+=========================================================+
| https.certificate.location | The location of the certificate key store | |
+------------------------------------------+------------------------------------------------------------------------+---------------------------------------------------------+
| https.password | The password for the certificate key store | |
+------------------------------------------+------------------------------------------------------------------------+---------------------------------------------------------+
| https.key.size | The size for the HTTPS keys | 2048 |
+------------------------------------------+------------------------------------------------------------------------+---------------------------------------------------------+
| https.signature.algorithm | The HTTPS signing algorithm to be used | SHA1WithRSA |
+------------------------------------------+------------------------------------------------------------------------+---------------------------------------------------------+
| https.validity.years | The amount of time (in years) for which the cert is valid | 3 |
+------------------------------------------+------------------------------------------------------------------------+---------------------------------------------------------+
| https.certificate.country | The country of the certificate | US |
+------------------------------------------+------------------------------------------------------------------------+---------------------------------------------------------+
| https.certificate.state | The state of the certificate | CO |
+------------------------------------------+------------------------------------------------------------------------+---------------------------------------------------------+
| https.certificate.locality | The locality of the certificate | Denver |
+------------------------------------------+------------------------------------------------------------------------+---------------------------------------------------------+
| https.certificate.organization | The organization of the certificate | Apache Traffic Control |
+------------------------------------------+------------------------------------------------------------------------+---------------------------------------------------------+
| https.certificate.organizational.unit | The organizational unit of the certificate | Apache Foundation, Hosted by Traffic Control, CDNDefault|
+------------------------------------------+------------------------------------------------------------------------+---------------------------------------------------------+

Traffic Router API endpoints only respond to ``GET`` requests.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,15 @@
import java.util.Map;

public class CertificateRegistry {
private static final String HTTPS_PROPERTIES_FILE = "/opt/traffic_router/conf/https.properties";
private static final String HTTPS_KEY_SIZE = "https.key.size";
private static final String HTTPS_SIGNATURE_ALGORITHM = "https.signature.algorithm";
private static final String HTTPS_VALIDITY_YEARS = "https.validity.years";
private static final String HTTPS_CERTIFICATE_COUNTRY = "https.certificate.country";
private static final String HTTPS_CERTIFICATE_STATE = "https.certificate.state";
private static final String HTTPS_CERTIFICATE_LOCALITY = "https.certificate.locality";
private static final String HTTPS_CERTIFICATE_ORGANIZATION = "https.certificate.organization";
private static final String HTTPS_CERTIFICATE_OU = "https.certificate.organizational.unit";
public static final String DEFAULT_SSL_KEY = "default.invalid";
private static final Logger log = LogManager.getLogger(CertificateRegistry.class);
private CertificateDataConverter certificateDataConverter = new CertificateDataConverter();
Expand All @@ -80,8 +89,23 @@ public static CertificateRegistry getInstance() {
@SuppressWarnings({"PMD.UseArrayListInsteadOfVector", "PMD.AvoidUsingHardCodedIP"})
private static HandshakeData createDefaultSsl() {
try {
final Map<String, String> httpsProperties = (new HttpsProperties(HTTPS_PROPERTIES_FILE)).getHttpsPropertiesMap();
final KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(2048);
int keysize = 2048, validityLength = 3;
String country = "US", state = "CO", locality = "Denver", organization = "Apache Traffic Control",
organizationalUnit = ";OU=Apache Foundation; OU=Hosted by Traffic Control; OU=CDNDefault",
signingAlgorithm = "SHA1WithRSA";
if (httpsProperties != null) {
keysize = Integer.parseInt(httpsProperties.getOrDefault(HTTPS_KEY_SIZE, String.valueOf(keysize)));
country = httpsProperties.getOrDefault(HTTPS_CERTIFICATE_COUNTRY, country);
state = httpsProperties.getOrDefault(HTTPS_CERTIFICATE_STATE, state);
locality = httpsProperties.getOrDefault(HTTPS_CERTIFICATE_LOCALITY, locality);
organization = httpsProperties.getOrDefault(HTTPS_CERTIFICATE_ORGANIZATION, organization);
organizationalUnit = httpsProperties.getOrDefault(HTTPS_CERTIFICATE_OU, organizationalUnit);
validityLength = Integer.parseInt(httpsProperties.getOrDefault(HTTPS_VALIDITY_YEARS, String.valueOf(validityLength)));
signingAlgorithm = httpsProperties.getOrDefault(HTTPS_SIGNATURE_ALGORITHM, signingAlgorithm);
}
keyPairGenerator.initialize(keysize);
final KeyPair keyPair = keyPairGenerator.generateKeyPair();

//Generate self signed certificate
Expand All @@ -93,20 +117,17 @@ private static HandshakeData createDefaultSsl() {
// Generate cert details
final long now = System.currentTimeMillis();
final Date startDate = new Date(System.currentTimeMillis());

final X500Name dnName = new X500Name("C=US; ST=CO; L=Denver; " +
"O=Apache Traffic Control; OU=Apache Foundation; OU=Hosted by Traffic Control; " +
"OU=CDNDefault; CN="+DEFAULT_SSL_KEY);
final String certAttributes = "C=" + country + "; ST=" + state + "; L=" + locality + "; O=" + organization + organizationalUnit + "; CN=" + DEFAULT_SSL_KEY;
final X500Name dnName = new X500Name(certAttributes);
final BigInteger certSerialNumber = new BigInteger(Long.toString(now));

final Calendar calendar = Calendar.getInstance();
calendar.setTime(startDate);
calendar.add(Calendar.YEAR, 3);
calendar.add(Calendar.YEAR, validityLength);

final Date endDate = calendar.getTime();

// Build certificate
final ContentSigner contentSigner = new JcaContentSignerBuilder("SHA1WithRSA").build(keyPair.getPrivate());
final ContentSigner contentSigner = new JcaContentSignerBuilder(signingAlgorithm).build(keyPair.getPrivate());

final JcaX509v3CertificateBuilder certBuilder = new JcaX509v3CertificateBuilder(dnName, certSerialNumber, startDate, endDate, dnName, keyPair.getPublic());

Expand All @@ -128,7 +149,7 @@ private static HandshakeData createDefaultSsl() {
return new HandshakeData(DEFAULT_SSL_KEY, DEFAULT_SSL_KEY, chain, keyPair.getPrivate());
}
catch (Exception e) {
log.error("Could not generate the default certificate: "+e.getMessage(),e);
log.error("Could not generate the default certificate: ", e);
return null;
}
}
Expand All @@ -151,7 +172,7 @@ public void setEndPoint(final RouterNioEndpoint routerNioEndpoint) {

private HandshakeData createApiDefaultSsl() {
try {
final Map<String, String> httpsProperties = (new HttpsProperties()).getHttpsPropertiesMap();
final Map<String, String> httpsProperties = (new HttpsProperties(HTTPS_PROPERTIES_FILE)).getHttpsPropertiesMap();

final KeyStore ks = KeyStore.getInstance("JKS");
final String selfSignedKeystoreFile = httpsProperties.get("https.certificate.location");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,29 +25,51 @@

public class HttpsProperties {
private static final Logger log = LogManager.getLogger(HttpsProperties.class);
private static final String HTTPS_PROPERTIES_FILE = "/opt/traffic_router/conf/https.properties";
private static final String HTTPS_CERTIFICATE_OU = "https.certificate.organizational.unit";

private final Map<String, String> httpsPropertiesMap;

public HttpsProperties() {
this.httpsPropertiesMap = loadHttpsProperties();
public HttpsProperties(final String fileName) {
this.httpsPropertiesMap = loadHttpsProperties(fileName);
}

public Map<String, String> getHttpsPropertiesMap() {
return httpsPropertiesMap;
}

private static Map<String, String> loadHttpsProperties() {
private static Map<String, String> loadHttpsProperties(final String fileName) {
try {
final Map<String, String> httpsProperties = new HashMap<>();
Files.readAllLines(Paths.get(HTTPS_PROPERTIES_FILE)).forEach(propString -> {
Files.readAllLines(Paths.get(fileName)).forEach(propString -> {
if (!propString.startsWith("#")) { // Ignores comments in properties file
final String[] prop = propString.split("=");
httpsProperties.put(prop[0], prop[1]);
final String[] props = propString.split("=");
if (props.length < 2) {
log.error("Property malformed, should be in the form key=value");
} else {
final String key = props[0];
final String val = props[1];
if (key.equals(HTTPS_CERTIFICATE_OU)) {
if (val.equals("") || val.length() < 2) {
log.error("Malformed " + HTTPS_CERTIFICATE_OU + " property value");
} else {
final String[] orgUnits = val.split(",");
String organizationalUnit = "";
StringBuilder sb = new StringBuilder(organizationalUnit);
for (final String ou : orgUnits) {
sb = sb.append("; OU=" + ou);
}
organizationalUnit = sb.toString();
httpsProperties.put(key, organizationalUnit);
}
} else {
httpsProperties.put(key, val);
}
}
}
});
return httpsProperties;
} catch (Exception e) {
log.error("Error loading https properties file.");
log.error("Error loading https properties file at "+ fileName+ ", error: " +e.getMessage());
return null;
}
}
Expand Down
24 changes: 24 additions & 0 deletions traffic_router/connector/src/test/java/conf/https.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#
#
# Licensed 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.
#
https.certificate.location=/opt/traffic_router/conf/keyStore.jks
https.password=changeit
https.key.size=1024
https.signature.algorithm=TestAlgorithm
https.validity.years=TestValidity
https.certificate.country=TestCountry
https.certificate.state=TestState
https.certificate.locality=TestLocality
https.certificate.organization=TestOrg
https.certificate.organizational.unit=Test Org Unit, Test Org Unit 2
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
*
* Licensed 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.
*/

package utils;

import org.apache.traffic_control.traffic_router.utils.HttpsProperties;
import org.junit.Test;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
import java.util.Map;

public class HttpsPropertiesTest {
@Test
public void checkGetHttpsProperties() throws Exception {
final String fileName = "src/test/java/conf/https.properties";
HttpsProperties httpsProperties = new HttpsProperties(fileName);
Map<String, String> propsMap = httpsProperties.getHttpsPropertiesMap();
assertThat(propsMap.get("https.certificate.location"), equalTo("/opt/traffic_router/conf/keyStore.jks"));
assertThat(propsMap.get("https.password"), equalTo("changeit"));
assertThat(propsMap.get("https.key.size"), equalTo("1024"));
assertThat(propsMap.get("https.signature.algorithm"), equalTo("TestAlgorithm"));
assertThat(propsMap.get("https.validity.years"), equalTo("TestValidity"));
assertThat(propsMap.get("https.certificate.country"), equalTo("TestCountry"));
assertThat(propsMap.get("https.certificate.state"), equalTo("TestState"));
assertThat(propsMap.get("https.certificate.locality"), equalTo("TestLocality"));
assertThat(propsMap.get("https.certificate.organization"), equalTo("TestOrg"));
assertThat(propsMap.get("https.certificate.organizational.unit"), equalTo("; OU=Test Org Unit; OU= Test Org Unit 2"));
}
}