Introduction to Amazon Code Pipeline with Java part 13: the client token lookup service
May 29, 2016 Leave a comment
Introduction
In the previous post we started looking at some code within the Code Pipeline job agent. We saw the ServletContextListener implementation that serves as the entry point into the application. The listener is wired up in the web.xml file. It is executed in the beginning of the application lifetime, i.e. when the web app starts. The implemented contextInitialized function constructs the necessary objects and dependencies that are necessary for the job agent.
In this post we’ll continue going through the code bit by bit.
The SSL related interfaces
There were 2 interfaces in the servlet context initialiser related to making HTTPS calls. These are actually not necessary to communicate with Code Pipeline since the AWS SDK hides such communication details. They are rather used to make API calls to our own backend services. Therefore we won’t really see them in action. However, I thought that they still could be interesting for you to see as they help initialise SSL before making HTTPS calls in your Java code.
Here are the interfaces:
import javax.net.ssl.HostnameVerifier; public interface ISslHostnameVerifier { public HostnameVerifier hostnamesToTrust(); }
import javax.net.ssl.TrustManager; public interface ISslTrustManager { public TrustManager[] certificatesToTrust(); }
…and here are the bare-minimum implementations:
import javax.net.ssl.HostnameVerifier; import javax.net.ssl.SSLSession; public class LtpWebApiHostnameVerifier implements ISslHostnameVerifier { @Override public HostnameVerifier hostnamesToTrust() { return (String hostname, SSLSession session) -> { return true; }; } }
import java.security.cert.X509Certificate; import javax.net.ssl.TrustManager; import javax.net.ssl.X509TrustManager; public class LtpWebApiTrustManager implements ISslTrustManager { @Override public TrustManager[] certificatesToTrust() { return new TrustManager[] { new X509TrustManager() { @Override public X509Certificate[] getAcceptedIssuers() { return null; // Not relevant. } @Override public void checkClientTrusted(X509Certificate[] certs, String authType) { // Do nothing. Just allow them all. } @Override public void checkServerTrusted(X509Certificate[] certs, String authType) { // Do nothing. Just allow them all. } } }; } }
The above elements are use to initialise SSL before making a HTTPS call:
public void initSSL() throws NoSuchAlgorithmException, KeyManagementException { TrustManager[] trustAllCertificates = this.sslTrustManager.certificatesToTrust(); HostnameVerifier trustAllHostnames = this.sslHostnameVerifier.hostnamesToTrust(); System.setProperty("jsse.enableSNIExtension", "false"); SSLContext sc = SSLContext.getInstance("SSL"); sc.init(null, trustAllCertificates, new SecureRandom()); HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory()); HttpsURLConnection.setDefaultHostnameVerifier(trustAllHostnames); }
After calling the initSSL method we can construct a java.net.URI object with https:// as its scheme and convert it to a java.net.URL object:
URI uri = new URI(...exact URI elements ommitted, scheme is HTTPS...); URL url = uri.toURL(); HttpsURLConnection httpsCon = (HttpsURLConnection) url.openConnection(); httpsCon.setRequestMethod("GET"); int responseCode = httpsCon.getResponseCode();
The Code Pipeline client ID verification
Then came an interface called ICodePipelineService. It has a single method which serves as a placeholder to look up the code pipeline related details based on the client ID returned by Code Pipeline. Recall that CP only sends us the job details if the client token can be verified beforehand. We’re responsible for storing the client ID and token details in our backend store.
Here’s the ICodePipelineService interface:
public interface ICodePipelineService { public AwsCodePipelineLookupResponse getAwsCodePipelineSetup(String clientId); }
AwsCodePipelineLookupResponse is a DTO object to hold the return values from our backend API. Our job agent implementation has no direct dependency on a database. We instead communicate with our backend system using our public API.
Here’s the AwsCodePipelineLookupResponse with some JSON-related notation in order to map the JSON response from the API into the properties of the Java object. Keep in mind that this is not specific to Code Pipeline, you can have a very different implementation. However, you may get some inspiration from these examples. The main point here is that the job agent must be able to locate the CP client token based on the client ID:
import com.apica.awscodepipelinebuildrunner.model.AwsCodePipelineSetupResponse; import com.fasterxml.jackson.annotation.JsonProperty; public class AwsCodePipelineLookupResponse { @JsonProperty("AwsCodePipelineSetup") private AwsCodePipelineSetupResponse awsCodePipelineSetupResponse; private String exception; public AwsCodePipelineLookupResponse() { this.exception = ""; } public AwsCodePipelineSetupResponse getAwsCodePipelineSetupResponse() { return awsCodePipelineSetupResponse; } public String getException() { return exception; } public void setException(String exception) { this.exception = exception; } }
AwsCodePipelineSetupResponse looks as follows:
import com.apica.awscodepipelinebuildrunner.model.thresholds.ThresholdResponse; import com.fasterxml.jackson.annotation.JsonProperty; import java.util.List; public class AwsCodePipelineSetupResponse { @JsonProperty("PresetName") private String presetName; @JsonProperty("ScenarioFileName") private String scenarioFileName; @JsonProperty("ClientId") private String clientId; @JsonProperty("ClientToken") private String clientToken; @JsonProperty("PipelineName") private String pipelineName; @JsonProperty("StageName") private String stageName; @JsonProperty("PipelineRegion") private String pipelineRegion; @JsonProperty("RequestCorrelationId") private String requestCorrelationId; @JsonProperty("Thresholds") private List<ThresholdResponse> thresholds; //getters for the private fields ignored }
If you look at some previous screenshots from the Apica Loadtest third party action then you may recognise the following job parameters that the CP user had to set up:
- The preset name
- The scenario file name
- The thresholds
Don’t worry about those, they are specific to our business. However, it’s good to know that we store those values along with the CP related properties:
- The Code Pipeline client ID
- The CP client token
- The pipeline name where our third party action was installed
- The stage where the action was installed within the pipeline
- The AWS region where the pipeline was set up
- The request correlation ID which we discussed before
All these terms should sound familiar to you from earlier posts. The most important property is the client token, the rest can be ignored for communicating with CP. We keep them for informational and statistical purposes.
The ICodePipelineService interface is implemented by the LtpWebApiCodePipelineService class which depends on the SSL interfaces and the application properties:
import com.amazonaws.util.json.Jackson; import com.apica.awscodepipelinebuildrunner.ssl.ISslHostnameVerifier; import com.apica.awscodepipelinebuildrunner.ssl.ISslTrustManager; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.net.MalformedURLException; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.security.KeyManagementException; import java.security.NoSuchAlgorithmException; import java.util.Properties; import javax.net.ssl.HttpsURLConnection; public class LtpWebApiCodePipelineService extends LtpWebApiServiceBase implements ICodePipelineService { public LtpWebApiCodePipelineService(ISslHostnameVerifier sslHostnameVerifier, ISslTrustManager sslTrustManager, Properties properties) { super(sslHostnameVerifier, sslTrustManager, properties); } @Override public AwsCodePipelineLookupResponse getAwsCodePipelineSetup(String clientId) { AwsCodePipelineLookupResponse awsCodePipelineLookupResponse = new AwsCodePipelineLookupResponse(); try { super.initSSL(); String endpointExtension = "awscodepipeline"; URI uri = new URI(...details ignored...); URL url = uri.toURL(); HttpsURLConnection httpsCon = (HttpsURLConnection) url.openConnection(); httpsCon.setRequestMethod("GET"); int responseCode = httpsCon.getResponseCode(); if (responseCode < 300) { //read the JSON response from the body and convert it to AwsCodePipelineLookupResponse, implementation ignored } else { //handle the error } } catch (URISyntaxException ex) { //ignored } catch (MalformedURLException ex) { //ignored } catch (IOException | NoSuchAlgorithmException | KeyManagementException ex) { //ignored } return awsCodePipelineLookupResponse; } }
The above implementation derives from an abstract base class that holds some common functions for all service classes. We’ve actually seen the most interesting function, the initSSL(). The rest of the code simply holds getters for some application properties:
import com.apica.awscodepipelinebuildrunner.repository.ConfigKeys; import com.apica.awscodepipelinebuildrunner.ssl.ISslHostnameVerifier; import com.apica.awscodepipelinebuildrunner.ssl.ISslTrustManager; import java.security.KeyManagementException; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.util.Properties; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.SSLContext; import javax.net.ssl.TrustManager; public abstract class LtpWebApiServiceBase { //a range of private variables that hold application settings such as the API endpoint, they are ignored here //... //SSL related dependencies private final ISslHostnameVerifier sslHostnameVerifier; private final ISslTrustManager sslTrustManager; public LtpWebApiServiceBase(ISslHostnameVerifier sslHostnameVerifier, ISslTrustManager sslTrustManager, Properties properties) { //read the application properties from the Properties object this.sslHostnameVerifier = sslHostnameVerifier; this.sslTrustManager = sslTrustManager; } public void initSSL() throws NoSuchAlgorithmException, KeyManagementException { TrustManager[] trustAllCertificates = this.sslTrustManager.certificatesToTrust(); HostnameVerifier trustAllHostnames = this.sslHostnameVerifier.hostnamesToTrust(); System.setProperty("jsse.enableSNIExtension", "false"); SSLContext sc = SSLContext.getInstance("SSL"); sc.init(null, trustAllCertificates, new SecureRandom()); HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory()); HttpsURLConnection.setDefaultHostnameVerifier(trustAllHostnames); } //property getters, implementation ignored }
We’re done with an important part of the application infrastructure. We’ll continue in the next post.
View all posts related to Amazon Web Services and Big Data here.