Client Side Service Identity Authentication
In order to contact Athenz Services (ZMS/ZTS) or other Athenz Enabled services, your client needs to establish a HTTPS connection using its Athenz issued x.509 certificate. This section contains some examples how to utilize Athenz x.509 certificates for service authentication
Table of Contents¶
Java¶
In the following set of examples we're going to assume that the service has already obtained its x.509 certificate from Athenz.
ZMS Client¶
We're going to use our ZMS Java client to communicate with ZMS
running in AWS to carry out a centralized access check to see if
principal user.john
has read
access to sports:nhl-scores
resource.
First we need to update our Java project pom.xml
file to indicate
our dependency on the ZMS Java Client and Certificate Refresh Helper
libraries
<dependencies>
<dependency>
<groupId>com.yahoo.athenz</groupId>
<artifactId>athenz-zms-java-client</artifactId>
<version>VERSION-NUMBER</version>
</dependency>
<dependency>
<groupId>com.yahoo.athenz</groupId>
<artifactId>athenz-cert-refresher</artifactId>
<version>VERSION-NUMBER</version>
</dependency>
</dependencies>
Next, let's assume the Athenz identity for the service is
sports.api
and SIA running on this host has already generated
the private key for the service and retrieved the X.509 certificate
from ZTS Server:
/var/lib/sia/keys/sports.api.key.pem
/var/lib/sia/certs/sports.api.cert.pem
The ZMS server is running with a public X.509 certificate so
we're going to use the standard jdk truststore for our connection
which has a default password of changeit
.
import javax.net.ssl.SSLContext;
import com.oath.auth.KeyRefresher;
import com.oath.auth.Utils;
final String zmsUrl = "https://zms-address/zms/v1";
final String keyPath = "/var/lib/sia/keys/sports.api.key.pem";
final String certPath = "/var/lib/sia/certs/sports.api.cert.pem";
final String trustStorePath = javaHome + "/jre/lib/security/cacerts";
final String trustStorePassword = "changeit";
try {
// Create our SSL Context object based on our private key and
// certificate and jdk truststore
KeyRefresher keyRefresher = Utils.generateKeyRefresher(trustStorePath, trustStorePassword,
certPath, keyPath);
// Default refresh period is every hour.
keyRefresher.startup();
// Can be adjusted to use other values in milliseconds. However,
// only one keyRefresher.startup call must be present.
// keyRefresher.startup(900000);
SSLContext sslContext = Utils.buildSSLContext(keyRefresher.getKeyManagerProxy(),
keyRefresher.getTrustManagerProxy());
// create our zms client and execute request
try (ZMSClient zmsClient = new ZMSClient(zmsUrl, sslContext)) {
try {
Access access = zmsClient.getAccess("read", "sports:nhl-scores", null, "user.john");
System.out.println("Access: " + access.getGranted());
} catch (ZMSClientException ex) {
LOGGER.error("Unable to carry out access check: {}", ex.getMessage());
return;
}
}
} catch (Exception ex) {
LOGGER.error("Unable to process request", ex);
return;
}
ZTS Client¶
We're going to use our ZTS Java client to communicate with
ZTS Server running in AWS to retrieve the public key for
the weather.api
service with key id weather.api.key
.
First we need to update our Java project pom.xml
file to indicate
our dependency on the ZTS Java Client and Certificate Refresh Helper
libraries
<dependencies>
<dependency>
<groupId>com.yahoo.athenz</groupId>
<artifactId>athenz-zts-java-client</artifactId>
<version>VERSION-NUMBER</version>
</dependency>
<dependency>
<groupId>com.yahoo.athenz</groupId>
<artifactId>athenz-cert-refresher</artifactId>
<version>VERSION-NUMBER</version>
</dependency>
</dependencies>
Next, let's assume the Athenz identity for the service is
sports.api
and SIA running on this host has already generated
the private key for the service and retrieved the X.509 certificate
from ZTS Server:
/var/lib/sia/keys/sports.api.key.pem
/var/lib/sia/certs/sports.api.cert.pem
The ZTS server is running with a public X.509 certificate so
we're going to use the standard jdk truststore for our connection
which has a default password of changeit
.
import javax.net.ssl.SSLContext;
import com.oath.auth.KeyRefresher;
import com.oath.auth.Utils;
final String ztsUrl = "https://zts-address/zts/v1";
final String keyPath = "/var/lib/sia/keys/sports.api.key.pem";
final String certPath = "/var/lib/sia/certs/sports.api.cert.pem";
final String trustStorePath = javaHome + "/jre/lib/security/cacerts";
final String trustStorePassword = "changeit";
try {
// Create our SSL Context object based on our private key and
// certificate and jdk truststore
KeyRefresher keyRefresher = Utils.generateKeyRefresher(trustStorePath, trustStorePassword,
certPath, keyPath);
// Default refresh period is every hour.
keyRefresher.startup();
// Can be adjusted to use other values in milliseconds.
//keyRefresher.startup(900000);
SSLContext sslContext = Utils.buildSSLContext(keyRefresher.getKeyManagerProxy(),
keyRefresher.getTrustManagerProxy());
// create our zts client and execute request
try (ZTSClient ztsClient = new ZTSClient(ztsUrl, sslContext)) {
try {
PublicKeyEntry publicKey = ztsClient.getPublicKeyEntry("weather", "api", "weather.api.key");
System.out.println("PublicKey: " + publicKey.getKey());
} catch (ZTSClientException ex) {
LOGGER.error("Unable to retrieve public key: {}", ex.getMessage());
return;
}
}
} catch (Exception ex) {
LOGGER.error("Unable to process request", ex);
return;
}
** Important **
During the shutdown of the application, ZTSClient.cancelPrefetch()
must be called to stop the timer thread that automatically fetches
and refreshes any cached tokens in the ZTS Client.
Apache HTTPClient¶
This example demonstrates how to correctly set up Apache HTTPClient for mutual TLS with persistent connections and connection pooling, with automatic certificate refreshing.
import com.oath.auth.KeyRefresher;
import com.oath.auth.Utils;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.client.NoopUserTokenHandler;
import org.apache.http.util.EntityUtils;
import java.io.IOException;
import java.net.URI;
import javax.net.ssl.SSLContext;
public class Example {
private final KeyRefresher keyRefresher;
private final CloseableHttpClient httpClient;
// These parameters normally point to files generated by SIA on managed hosts
public Example(String trustStorePath, String trustStorePassword, String certPath,
String keyPath) throws Exception {
// Create a key refresher to automatically reload key/cert when updated by SIA
this.keyRefresher = Utils.generateKeyRefresher(trustStorePath, trustStorePassword, certPath,
keyPath);
keyRefresher.startup();
// Create TLS context
// Note that this may create a TLS 1.3 context when supported
SSLContext sslContext = Utils.buildSSLContext(keyRefresher.getKeyManagerProxy(),
keyRefresher.getTrustManagerProxy());
// Create the actual HTTP client
this.httpClient = HttpClients.custom()
// Use the context that has our keys and trusted CAs
.setSSLContext(sslContext)
// Enable connection pooling - when mutual TLS used this gets disabled by default!
.setUserTokenHandler(NoopUserTokenHandler.INSTANCE)
// You can set more options here as desired, for example number of connections to use
.build();
}
public void callSomeService(URI uri) {
HttpResponse response = null;
try {
HttpGet request = new HttpGet(uri);
response = httpClient.execute(request);
// Do something with the response
System.err.println("Got response: " + response.getStatusLine().getStatusCode());
} catch (IOException e) {
// Handle connection level errors here
e.printStackTrace();
} finally {
// Ensure the entire request entity is consumed to release the connection for reuse
// Note that calling CloseableHttpResponse.close() will make the connection ineligible
// for reuse so it must be avoided
if (response != null) {
EntityUtils.consumeQuietly(response.getEntity());
}
}
}
}
HTTPSUrlConnection Client¶
Note: This method does not support connection pooling and is only included as a demonstration, production code should use fully featured HTTP client.
We're going to use a HTTPSUrlConnection client to communicate with an HTTPS Server running to retrieve some data for a given url.
First we need to update our Java project pom.xml
file to indicate
our dependency on the Certificate Refresh Helper library.
<dependencies>
<dependency>
<groupId>com.yahoo.athenz</groupId>
<artifactId>athenz-cert-refresher</artifactId>
<version>VERSION-NUMBER</version>
</dependency>
</dependencies>
Next, let's assume the Athenz identity for the service is
sports.api
and SIA running on this host has already generated
the private key for the service and retrieved the X.509 certificate
from ZTS Server:
/var/lib/sia/keys/sports.api.key.pem
/var/lib/sia/certs/sports.api.cert.pem
The HTTPS server is running with an Athenz issued certificate so our
truststore must include the Athenz CA certificates. For the following
example, the truststore containing the Athenz CA certificates will be located
at /home/example/athenz_certificate_bundle.jks
with a default password
of changeit
.
import javax.net.ssl.SSLContext;
import com.oath.auth.KeyRefresher;
import com.oath.auth.Utils;
final String keyPath = "/var/lib/sia/keys/sports.api.key.pem";
final String certPath = "/var/lib/sia/certs/sports.api.cert.pem";
final String trustStorePath = "/home/example/athenz_certificate_bundle.jks";
final String trustStorePassword = "changeit";
try {
KeyRefresher keyRefresher = Utils.generateKeyRefresher(trustStorePath, trustStorePassword,
certPath, keyPath);
// Default refresh period is every hour.
keyRefresher.startup();
// Can be adjusted to use other values in milliseconds.
//keyRefresher.startup(900000);
SSLContext sslContext = Utils.buildSSLContext(keyRefresher.getKeyManagerProxy(),
keyRefresher.getTrustManagerProxy());
HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.getSocketFactory());
HttpsURLConnection con = (HttpsURLConnection) new URL(url).openConnection();
con.setReadTimeout(15000);
con.setDoOutput(true);
con.connect();
try (BufferedReader br = new BufferedReader(new InputStreamReader(con.getInputStream()))) {
StringBuilder sb = new StringBuilder();
String line;
while ((line = br.readLine()) != null) {
sb.append(line);
}
System.out.println("Data output: " + sb.toString());
}
} catch (Exception ex) {
LOGGER.error("Unable to process request", ex);
return;
}