Here is the approach I have researched for this issue.
The basic requirements are
1. Enable TLS for the API endpoint in TR
2. Should be able to switch it on or off at a TR instance level
3. When TLS is on, the unencrypted API should not be accessible
4. TP and TO should still be able to access the API when TLS is on
**General Approach**
The Traffic Router RPM will generate a cert for the TR host and a CA cert for
signing the host cert. The CA cert will be added to the server's trusted CA's
and the TR host will be added to the local keystore. The self-signed cert
serves as the ‘_default_’ cert to initialize TR. Once initialized one of the
keys from RIAK (if there are any) will be selected by TR as the default for the
HTTPS Delivery Services. For the /crs API calls the self-signed key will remain
the default key. The client can take the generated CA cert and add it to their
trusted CA store if they want to SSL to the /crs API. The CA cert will also be
added to the trust store on the TP servers so they can continue to call the
/crs API.
The TR RPM will not overwrite the self-signed cert or the CA cert if they
already exist on the server from an earlier install, or if the operations team
has replaced it with an official certificate from an established CA. This
approach will also resolve an issue in Traffic Router where it will not start
up properly when there are no certificates for HTTPS delivery services
installed in Traffic Vault.
**Configuration**
To enable TLS on a specific server the server.xml will first need to be
modified. Replace the Connector for port 3333 with the following:
` <Connector port="3333"
protocol="com.comcast.cdn.traffic_control.traffic_router.protocol.LanguidNioProtocol"
maxThreads="10000"
scheme="https" secure="true" SSLEnabled="true"
clientAuth="false" sslProtocol="TLS" connectionTimeout="10000"
mbeanPath="traffic-router:name=languidState"
readyAttribute="Ready" portAttribute="ApiPort" sendReasonPhrase="true"
sslImplementationName="com.comcast.cdn.traffic_control.traffic_router.protocol.RouterSslImplementation">
</Connector>`
**TR Code Changes**
The Traffic Router code will definitely need to be modified. The suggested
implementation would modify the source in the TR connector:
com.comcast.cdn.traffic_control.traffic_router.protocol.RouterNioEndpoint
com.comcast.cdn.traffic_control.traffic_router.secure.CertificateRegistry
In RouterNioEndpoint call the 'loadDefaultCert' method during initialization:
protected void initialiseSsl() throws Exception {
if (isSSLEnabled()) {
destroySsl();
sslHostConfigs.clear();
final KeyManager keyManager = new KeyManager();
final CertificateRegistry certificateRegistry =
keyManager.getCertificateRegistry();
certificateRegistry.loadDefaultCert();
replaceSSLHosts(certificateRegistry.getHandshakeData());
//Now let initialiseSsl do it's thing.
super.initialiseSsl();
certificateRegistry.setEndPoint(this);
}
}
synchronized private void replaceSSLHosts(final Map<String, HandshakeData>
sslHostsData) {
final Set<String> aliases = sslHostsData.keySet();
boolean firstAlias = true;
String lastHostName = "";
for (final String alias : aliases) {
final SSLHostConfig sslHostConfig = new SSLHostConfig();
final SSLHostConfigCertificate cert = new
SSLHostConfigCertificate(sslHostConfig, SSLHostConfigCertificate.Type.RSA);
cert.setCertificateKeyAlias(alias);
sslHostConfig.addCertificate(cert);
sslHostConfig.setCertificateKeyAlias(alias);
sslHostConfig.setHostName(sslHostsData.get(alias).getHostname());
sslHostConfig.setProtocols("all");
sslHostConfig.setConfigType(getSslConfigType());
sslHostConfig.setCertificateVerification("none");
LOGGER.info("sslHostConfig: "+sslHostConfig.getHostName()+"
"+sslHostConfig.getTruststoreAlgorithm());
if (!sslHostConfig.getHostName().equals(lastHostName)) {
addSslHostConfig(sslHostConfig, true);
lastHostName = sslHostConfig.getHostName();
}
if (firstAlias && ! "".equals(alias)) {
// One of the configs must be set as the default
setDefaultSSLHostConfigName(sslHostConfig.getHostName());
firstAlias = false;
}
if (InetAddress.getLocalHost().getHostName().equals(alias))
{
setDefaultSSLHostConfigName(alias);
}
}
}
In CertificateRegistry add the 'loadDefaultCert' method and adjust the
'importCertificateData' method:
public void loadDefaultCert() throws Exception {
final String hostkeyAlias =
InetAddress.getLocalHost().getHostName();
HandshakeData handshakeData = null;
// Create the HandshakeData or load if it already exists
if (getHandshakeData(hostkeyAlias) == null) {
KeyStore ks = KeyStore.getInstance("JKS");
if (new File(SELF_SIGNED_KEYSTORE_FILE).exists()) {
InputStream readStream = new
FileInputStream(SELF_SIGNED_KEYSTORE_FILE);
ks.load(readStream, KEYSTORE_PASS);
readStream.close();
handshakeData = new HandshakeData("localhost",
hostkeyAlias,
ks.getCertificateChain(hostkeyAlias), ks.getKey(hostKeyAlias));
} else {
handshakeData = generateSelfSignedHostCert();
// this executes the Bash script for
// generating the default cert and its CA
signing cert which should have been run
// during the RPM installation.
}
getHandshakeData().putIfAbsent(hostkeyAlias,
handshakeData);
}
// else nothing to do
}
@SuppressWarnings("PMD.AccessorClassGeneration")
private static class CertificateRegistryHolder {
private static final CertificateRegistry
DELIVERY_SERVICE_CERTIFICATES = new CertificateRegistry();
}
synchronized public void importCertificateDataList(final
List<CertificateData> certificateDataList) {
final Map<String, HandshakeData> changes = new HashMap<>();
final Map<String, HandshakeData> master = new HashMap<>();
// find CertificateData which has changed
for (final CertificateData certificateData :
certificateDataList) {
try {
final HandshakeData handshakeData =
certificateDataConverter.toHandshakeData(certificateData);
final String alias =
handshakeData.getHostname().replaceFirst("\\*\\.", "");
master.put(alias, handshakeData);
if
(certificateData.equals(previousData.get(certificateData.getHostname()))) {
continue;
}
changes.put(alias, handshakeData);
log.warn("Imported handshake data with alias " + alias);
} catch (Exception e) {
log.error("Failed to import certificate data
for delivery service: '" + certificateData.getDeliveryservice() + "', hostname:
'" + certificateData.getHostname() + "'");
}
}
// find CertificateData which has been removed
for (final String hostname : previousData.keySet())
{
if (!master.containsKey(hostname.replaceFirst("\\*\\.",
"")) && sslEndpoint != null)
{
sslEndpoint.removeSslHostConfig(hostname);
log.warn("Removed handshake data with
hostname " + hostname);
}
}
// store the result for the next import
previousData.clear();
for (final CertificateData certificateData :
certificateDataList) {
previousData.put(certificateData.getHostname(),
certificateData);
}
String localHostKey = InetAddress.getLocalHost().getHostName();
master.putIfAbsent(localHostKey,handshakeDataMap.get(localHostKey));
handshakeDataMap = master;
if (sslEndpoint != null) {
sslEndpoint.reloadSSLHosts(changes);
}
}
**Traffic Ops Code Changes**
The Traffic Ops API will need to be modified so that it knows to use 'https'
when constructing its URL for the Traffic Router API. These changes will most
likely occur in the source files:
traffic_ops/app/lib/Utils/CCR.pm
traffic_ops/app/lib/UI/Cdn.pm
**Traffic Router RPM changes**
The RPM should generate the CA cert and self-signed cert. The
traffic_router/core/src/main/scripts/preinstall.sh script is a good candidate.
The Bash scripts written by Jeff Bevill for CDN in-a-box should be adaptable
for this task. Preferably the scripts would be modified to add the self-signed
key for the host to a Java keystore file in the /opt/traffic_router/ directory
structure. They should not regenerate the certificates if they already exist on
the file system. The RPM should also copy the CA certificate to the Traffic Ops
servers and marked as a trusted CA where Perl and Go look for their CA
certificates.
[ Full content available at:
https://github.com/apache/trafficcontrol/issues/2817 ]
This message was relayed via gitbox.apache.org for [email protected]