Securing APIs with Transport Layer Security (TLS) is the most common form of protection we see in any API deployment. If you are new to TLS, please check Appendix first, which explains TLS in detail and how it works. In securing APIs, we use TLS to secure or encrypt the communication—or protect the data in transit—and also we use TLS mutual authentication to make sure only the legitimate clients can access the APIs.
In this chapter, we discuss how to deploy an API implemented in Java Spring Boot, enable TLS, and protect an API with mutual TLS.
Setting Up the Environment
In this section, we’ll see how we can develop an API using Spring Boot from scratch. Spring Boot (https://projects.spring.io/spring-boot/) is the most popular microservices development framework for Java developers. To be precise, Spring Boot offers an opinionated runtime for Spring, which takes out a lot of complexities. Even though Spring Boot is opinionated, it also gives developers to override many of its default picks. Due to the fact that many Java developers are familiar with Spring, and the ease of development is a key success factor in the microservices world, many adopted Spring Boot. Even for Java developers who are not using Spring, still it is a household name. If you have worked on Spring, you surely would have worried how painful it was to deal with large, chunky XML configuration files. Unlike Spring, Spring Boot believes in convention over configuration—no more XML hell! In this book, we use Spring Boot to implement our APIs. Even if you are not familiar with Java, you will be able to get started with no major learning curve, as we provide all the code examples.
To run the samples, you will need
Java 8 or latest,
Maven 3.2 or latest, and a
git client. Once you are successfully done with the installation, run the following two commands in the command line to make sure everything is working fine. If you’d like some help in setting up Java or Maven, there are plenty of
online resources out there.
>java -version
java version "1.8.0_121" Java(TM) SE Runtime Environment (build 1.8.0_121-b13)
Java HotSpot(TM) 64-Bit Server VM (build 25.121-b13, mixed mode)
>mvn -version
Apache Maven 3.5.0 (ff8f5e7444045639af65f6095c62210b5713f426; 2017-04-03T12:39:06-07:00)
Maven home: /usr/local/Cellar/maven/3.5.0/libexec
Java version: 1.8.0_121, vendor: Oracle Corporation
Java home: /Library/Java/JavaVirtualMachines/jdk1.8.0_121.jdk/Contents/Home/jre Default locale: en_US, platform encoding: UTF-8 OS name: "mac os x", version: "10.12.6", arch: "x86_64", family: "mac
All the samples used in this book are available in the
https://github.com/apisecurity/samples.git git repository. Use the following git command to clone it. All the samples related to this chapter are inside the
directory ch03.
> git clone https://github.com/apisecurity/samples.git
> cd samples/ch03
To anyone who loves Maven, the best way to get started with a Spring Boot project would be with a Maven archetype. Unfortunately, it is no more supported. One option we have is to create a template project via https://start.spring.io/ –which is known as the Spring Initializer. There you can pick which type of project you want to create, project dependencies, give a name, and download a maven project as a zip file. The other option is to use the Spring Tool Suite (STS). It’s an IDE (integrated development environment) built on top of the Eclipse platform, with many useful plugins to create Spring projects. However, in this book, we provide you all the fully coded samples in the preceding git repository.
Deploying Order API
This is the simplest API ever. You can find the code
inside the directory
ch03/sample01. To build the project with Maven, use the following command:
> cd sample01
> mvn clean install
Before we delve deep into the code, let’s have a look at some of the notable Maven dependencies and plugins added into ch03/sample01/pom.xml.
Spring Boot comes with different
starter dependencies to integrate with different Spring modules. The
spring-boot-starter-web dependency brings in Tomcat and Spring MVC and, does all the wiring between the components, making the developer’s work to a minimum. The
spring-boot-starter-actuator dependency
helps you monitor and manage your application.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
In the
pom.xml file, we also have the
spring-boot-maven-plugin plugin, which lets you start the Spring Boot API from Maven itself.
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
Now let’s have a look at the
checkOrderStatus method in the class file
src/main/java/com/apress/ch03/sample01/service/OrderProcessing.java. This method accepts an order id and returns back the status of the order. There are three notable annotations used in the following code. The
@RestController
is a class-level annotation that marks the corresponding class as a REST endpoint, which accepts and produces JSON payloads. The
@RequestMapping annotation can be defined both at the class level and the method level. The
value attribute at the class-level annotation defines the path under which the corresponding endpoint is registered. The same at the method level appends to the class-level path. Anything defined within curly braces is a placeholder for any variable value in the path. For example, a GET request on
/order/101 and /
order/102 (where 101 and 102 are the order ids), both hit the method
checkOrderStatus. In fact, the value of the
value attribute is a URI template.
The annotation
@PathVariable
extracts the provided variable from the URI template defined under the
value attribute of the
@RequestMapping
annotation and binds it to the variable defined in the method signature.
@RestController
@RequestMapping(value = "/order")
public class OrderProcessing {
@RequestMapping(value = "/{id}", method = RequestMethod.GET)
public String checkOrderStatus(@PathVariable("id") String orderId)
{
return ResponseEntity.ok("{'status' : 'shipped'}");
}
}
There is another important class file at
src/main/java/com/apress/ch03/sample01/OrderProcessingApp.java worth having a look at. This is the class which spins up our API in its own application server, in this case the embedded Tomcat. By default the API starts on port 8080, and you can change the port by adding, say, for example,
server.port=9000 to the
sample01/src/main/resources/application.properties file. This will set the server port to 9000. The following shows the code snippet from
OrderProcessingApp class, which spins up our API. The
@SpringBootApplication annotation, which is defined at the class level, is being used as a shortcut for four other annotations defined in Spring:
@Configuration,
@EnableAutoConfiguration,
@EnableWebMvc, and
@ComponentScan.@SpringBootApplication
public class OrderProcessingApp {
public static void main(String[] args) {
SpringApplication.run(OrderProcessingApp.class, args);
}
}
Now, let’s see how to run our API and talk to it with a cURL client. The following command executed from
ch03/sample01 directory shows how to start our Spring Boot application with Maven.
To test the API with a cURL client, use the following command from a different command console. It will print the output as shown in the following, after the initial command.
> curl http://localhost:8080/order/11
{"customer_id":"101021","order_id":"11","payment_method":{"card_type":"VISA","expiration":"01/22","name":"John Doe","billing_address":"201, 1st Street, San Jose, CA"},"items": [{"code":"101","qty":1},{"code":"103","qty":5}],"shipping_address":"201, 1st Street, San Jose, CA"}
Securing Order API with Transport Layer Security (TLS)
To enable Transport Layer Security (TLS), first we need to create a public/private key
pair. The following command uses
keytool that comes with the default Java distribution to generate a key pair and stores it in
keystore.jks file. This file is also known as a keystore, and it can be in different formats. Two most popular formats are
Java KeyStore (JKS) and PKCS#12. JKS is specific to Java, while PKCS#12 is a standard, which belongs to the family of standards defined under
Public Key Cryptography Standards (PKCS). In the following command, we specify the keystore type with the
storetype argument, which is set to
JKS.
> keytool -genkey -alias spring -keyalg RSA -keysize 4096 -validity 3650 -dname "CN=foo,OU=bar,O=zee,L=sjc,S=ca,C=us" -keypass springboot -keystore keystore.jks -storeType jks -storepass springboot
The alias argument
in the preceding command specifies how to identify the generated keys stored in the keystore. There can be multiple keys stored in a given keystore, and the value of the corresponding alias must be unique. Here we use spring as the alias. The validity argument
specifies that the generated keys are only valid for 10 years or 3650 days. The keysize and keystore arguments specify the length of the generated key and the name of the keystore, where the keys are stored. The genkey is the option, which instructs the keytool to generate new keys; instead of genkey, you can also use genkeypair option. Once the preceding command is executed, it will create a keystore file called keystore.jks, which is protected with the password springboot.
The certificate created in this example is known as a self-signed certificate. In other words, there is no external certificate authority (CA). Typically, in a production deployment, either you will use a public certificate authority or an enterprise-level certificate authority to sign the public certificate, so any client, who trusts the certificate authority, can verify it. If you are using certificates to secure service-to-service communications in a microservices deployment or for an internal API deployment, then you need not worry about having a public certificate authority; you can have your own certificate authority. But for APIs, which you expose to external client applications, you would need to get your certificates signed by a public certificate authority.
To enable TLS for the Spring Boot API, copy the keystore file (
keystore.jks), which we created earlier, to the home directory of the sample (e.g.,
ch03/sample01/) and add the following to the
sample01/src/main/resources/application.properties file. The samples that you download from the
samples git repository already have these values (and you only need to uncomment them), and we are using
springboot as the password for both the keystore and the private key.
server.ssl.key-store: keystore.jks
server.ssl.key-store-password: springboot
server.ssl.keyAlias: spring
To validate that everything works fine, use the following command from
ch03/sample01/ directory to spin up the
Order API and notice the line which prints the HTTPS port.
> mvn spring-boot:run
Tomcat started on port(s): 8080 (https) with context path "
To test the API with a cURL client, use the following command from a different command console. It will print the output as shown in the following, after the initial command. Instead of HTTP, we are using HTTPS here.
> curl –k https://localhost:8080/order/11
{"customer_id":"101021","order_id":"11","payment_method":{"card_type":"VISA","expiration":"01/22","name":"John Doe","billing_address":"201, 1st Street, San Jose, CA"},"items": [{"code":"101","qty":1},{"code":"103","qty":5}],"shipping_address":"201, 1st Street, San Jose, CA"}
We used the
-k option in the preceding cURL command. Since we have a self-signed (untrusted) certificate to secure our HTTPS endpoint, we need to pass the –k parameter to advise cURL to ignore the trust validation. In a production deployment with proper certificate authority–signed certificates, you do not need to do that. Also, if you have a self-signed certificate, you can still avoid using
–k, by pointing cURL to the corresponding public certificate.
> curl --cacert ca.crt https://localhost:8080/order/11
You can use the following keytool command from
ch03/sample01/ to export the public certificate of the Order API to ca.crt file in PEM (with the
-rfc argument) format.
> keytool -export -file ca.crt -alias spring –rfc -keystore keystore.jks -storePass springboot
The preceding curl command with the
ca.crt will result in the following error. It complains that the common name in the public certificate of the Order API, which is foo, does not match with the hostname (localhost) in the cURL command.
curl: (51) SSL: certificate subject name 'foo' does not match target host name 'localhost'
Ideally in a production deployment when you create a certificate, its common name should match the hostname. In this case, since we do not have a
Domain Name Service (DNS) entry for the foo hostname, you can use the following workaround, with cURL.
> curl --cacert ca.crt https://foo:8080/order/11 --resolve foo:8080:127.0.0.1
Protecting Order API with Mutual TLS
In this section, we’ll see how to enable TLS mutual authentication between the
Order API
and the cURL client. In most of the cases, TLS mutual authentication is used to enable system-to-system authentication. First make sure that we have the keystore at
sample01/keystore.jks, and then to enable TLS mutual authentication, uncomment the following property in the
sample01/src/main/resources/application.properties file.
server.ssl.client-auth:need
Now we can test the flow by invoking the
Order API using cURL. First, use the following command from
ch03/sample01/ directory to spin up the
Order API and notice the line which prints the HTTPS port.
> mvn spring-boot:run
Tomcat started on port(s): 8080 (https) with context path ''
To test the API with a cURL client, use the following command from a different command console.
> curl –k https://localhost:8080/order/11
Since we have protected the API with TLS mutual authentication, the preceding command will result in the following error message, which means the API (or the server) has refused to connect with the cURL client, because it didn’t present a valid client certificate.
curl: (35) error:1401E412:SSL routines:CONNECT_CR_FINISHED:sslv3 alert bad certificate
To fix this, we need to create a key pair (a public key and a private key) for the cURL client and configure Order API to trust the public key. Then we can use the key pair we generated along with the cURL command to access the API, which is protected with mutual TLS.
To generate a private key and a public key for the cURL client, we use the following OpenSSL command. OpenSSL is a commercial-grade toolkit and cryptographic library for TLS and available for multiple platforms. You can download and set up the distribution that fits your platform from
www.openssl.org/source. If not, the easiest way is to use an OpenSSL Docker image. In the next section, we discuss how to run OpenSSL as a Docker container.
> openssl genrsa -out privkey.pem 4096
Now, to generate a self-signed certificate, corresponding to the preceding private key (
privkey.pem), use the following OpenSSL command.
> openssl req -key privkey.pem -new -x509 -sha256 -nodes -out client.crt -subj "/C=us/ST=ca/L=sjc/O=zee/OU=bar/CN=client"
Let’s take down the
Order API, if it is still running, and import the public certificate (
client.crt) we created in the preceding step to
sample01/keystore.jks, using the following command.
> keytool -import -file client.crt -alias client -keystore keystore.jks -storepass springboot
Now we can test the flow by invoking the
Order API using cURL. First, use the following command from
ch03/sample01/ directory to spin up the
Order API.
> mvn spring-boot:run
Tomcat started on port(s): 8080 (https) with context path ''
To test the API with a cURL client, use the following command from a different command console.
> curl -k --key privkey.pem --cert client.crt https://localhost:8080/order/11
In case we use a key pair, which is not known to the
Order API, or in other words not imported into the
sample01/keystore.jks file, you will see the following error, when you execute the preceding cURL command.
curl: (35) error:1401E416:SSL routines:CONNECT_CR_FINISHED:sslv3 alert certificate unknown
Running OpenSSL on Docker
In the last few years, Docker revolutionized the way we distribute software. Docker provides a containerized environment to run software in self-contained manner. A complete overview of Docker is out of the scope of this book—and if you are interested in learning more, we recommend you check out the book Docker in Action
(Manning Publications, 2019) by Jeff Nickoloff and Stephen Kuenzli.
Setting up Docker in your local machine is quite straightforward, following the steps in Docker documentation available at
https://docs.docker.com/install/. Once you get Docker installed, run the following command to verify the installation, and it will show the version of Docker engine client and server.
To start OpenSSL as a Docker container, use the following command from the
ch03/sample01 directory.
> docker run -it -v $(pwd):/export prabath/openssl
#
When you run the preceding command for the first time, it will take a couple of minutes to execute and ends with a command prompt, where you can execute your OpenSSL commands to create the keys, which we used toward the end of the previous sections. The preceding docker run command
starts OpenSSL in a Docker container, with a volume mount, which maps ch03/sample01 (or the current directory, which is indicated by $(pwd) in the preceding command) directory from the host file system to the /export directory of the container file system. This volume mount helps you to share part of the host file system with the container file system. When the OpenSSL container generates certificates, those are written to the /export directory of the container file system. Since we have a volume mount, everything inside the /export directory of the container file system is also accessible from the ch03/sample01 directory of the host file system.
To generate a private key and a public key for the cURL client, we use the following
OpenSSL command.
# openssl genrsa -out /export/privkey.pem 4096
Now, to generate a self-signed certificate, corresponding to the preceding private key (
privkey.pem), use the following OpenSSL command.
# openssl req -key /export/privkey.pem -new -x509 -sha256 -nodes -out client.crt -subj "/C=us/ST=ca/L=sjc/O=zee/OU=bar/CN=client"
Summary
Transport Layer Security (TLS) is fundamental in securing any API.
Securing APIs with TLS is the most common form of protection we see in any API deployment.
TLS protects data in transit for confidentiality and integrity, and mutual TLS (mTLS) protects your APIs from intruders by enforcing client authentication.
OpenSSL is a commercial-grade toolkit and cryptographic library for TLS and available for multiple platforms.