Introduction to Amazon Code Pipeline with Java part 13: the client token lookup service

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.

Advertisement

About Andras Nemes
I'm a .NET/Java developer living and working in Stockholm, Sweden.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

Elliot Balynn's Blog

A directory of wonderful thoughts

Software Engineering

Web development

Disparate Opinions

Various tidbits

chsakell's Blog

WEB APPLICATION DEVELOPMENT TUTORIALS WITH OPEN-SOURCE PROJECTS

Once Upon a Camayoc

Bite-size insight on Cyber Security for the not too technical.

%d bloggers like this: