Wednesday, April 6, 2022

Host a dark service with Java, Spring Boot, and OpenZiti

Hide your application services to protect them against hackers.

This article’s headline mentions hosting a dark service. Huh? Why would you want your application services to be dark?

Open ports are everywhere. You need open ports, right? How are your users and applications going to connect to your services if there are no open ports? Well, they can still connect—but only if they are trusted.

Oracle Java Certification, Core Java, Java Tutorial and Material, Oracle Java Learning, Oracle Java Career, Java Jobs, Java Skills

A dark service is a service that’s had zero trust (ZT) principles applied across the board and, therefore, not only to the network layer. Indeed, the principles of ZT access are baked directly into the application itself.

Dark services have no listening ports, taking them off the internet (and even off the local network) where port scanners and nefarious actors are only an IP hop away.

What is zero trust?

The zero trust phrase has been thrown around a lot lately, but what does it really mean? Here’s how the concept is defined by NIST’s Computer Security Resource Center.

Zero trust (ZT) is the term for an evolving set of cybersecurity paradigms that move defenses from static, network-based perimeters to focus on users, assets, and resources. A zero trust architecture (ZTA) uses zero trust principles to plan industrial and enterprise infrastructure and workflows. Zero trust assumes there is no implicit trust granted to assets or user accounts based solely on their physical or network location (i.e., local area networks versus the internet) or based on asset ownership (enterprise or personally owned). Authentication and authorization (both subject and device) are discrete functions performed before a session to an enterprise resource is established. Zero trust is a response to enterprise network trends that include remote users, bring your own device (BYOD), and cloud-based assets that are not located within an enterprise-owned network boundary. Zero trust focuses on protecting resources (assets, services, workflows, network accounts, etc.), not network segments, as the network location is no longer seen as the prime component to the security posture of the resource.

In practice, you can boil this down to five basic tenants that apply to everything on your network, including both human users and automated processes.

◉ Deny access by default. Nobody can connect to the network unless you let them.

◉ Authorize on connect. Only allow connections to the network that use a strong, well-defined and known identity.

◉ Use explicit access grants. Even if they can connect, actors cannot do anything unless you let them. Limiting access prevents things such as traversal attacks and information leakage.

◉ Enforce least privilege access. Granting only permissions that are absolutely required for an actor to complete its tasks limits the damage that can be done when an account is compromised.

◉ Constantly monitor for security compliance. Zero trust is not just about the initial setup. It includes constantly monitoring policies, connections, and permissions to ensure that ZT principles continue to be enforced over time.

ZT principles should not stop at the network

You can apply all the ZT principles to your network, but even after that your application is still listening on a port after that. If there is a port open, your application can be attacked. That’s why the concept of app embedded zero trust moves the edge of the network into the application. Bringing ZT into your app programmatically via an SDK has several benefits.

◉ No listening ports. Your application can become totally dark with no open ports. When there are no open ports, the application cannot be scanned or attacked from any random person on the network.

◉ Zero trust of the entire network. When ZT is in the application, it extends across all networks, including the Internet, local-area networks, and even the operating system. Not trusting the network operating system makes applications immune to network-based side-channel attacks from malicious actors or ransomware that’s trying to attack the application from a stolen or infected device.

◉ Direct access. Applications are accessed directly from clients; they are not discovered. If the client doesn’t know where the application is, it can’t access it.

◉ Portability. Once ZT is configured and becomes part of the architectural overlay, your application and your clients need only outbound, commodity internet.

◉ Encrypted data. Application data is encrypted from the client all the way to the server and back. ZT enforces this.

◉ Micro segmentation. Applications cannot talk to other applications unless explicitly authorized to do so.

In terms of portability, embedding ZT into an application can help in common use cases. If you want to change clouds or deploy your application into a new data center, no changes need to be made. Simply develop your app once and deploy it anywhere, and it just works. What about mobile clients that want access through a secure shell (ssh) from home, a client site, or the airport? No problem! It doesn’t matter where the client is; trusted connectivity works from anywhere.

How is this achieved? When using OpenZiti, every endpoint (SDK for in-app access, tunnelers for the operating system or edge routers for the network) must have an identity with provisioned certificates. The certificates allow Ziti to perform authentication and authorization before any data flows across secure communications channels. These endpoints reach out of the private network to talk to the controller (control plane) and make connections to join the network fabric mesh (data plane). Therefore, services and endpoints in your private networks only make outbound connections—and thus, no holes are opened for inbound traffic.

An example of app with embedded zero trust

This example will use OpenZiti to provide the ZT overlay network and application SDKs. Spring Boot and Tomcat will host the service. The project will take about half an hour. You will need the following:

◉ Your favorite text editor or IDE

◉ JDK 11 or later

◉ Access to a Linux environment with the bash shell—or, for Windows users, a VM or Windows Subsystem for Linux 2 (WSL 2)

If you don’t have access to a Linux environment but wish to use it, you can grab a Linux VM from the Oracle Cloud free tier.

What is OpenZiti? It’s an open source project sponsored by NetFoundry. Oracle embraces open source and zero trust security, so Oracle partnered with NetFoundry to provide zero trust connections to applications, including Java applications, running in Oracle Cloud Infrastructure (OCI). NetFoundry’s Edge Router software is staged within the OCI Marketplace for deployment within any OCI region.

Get the code. The example code can be downloaded from here. Alternatively, you can clone it using Git with the following shell command:

git clone https://github.com/netfoundry/openziti-spring-boot

As with most Spring guides, you can start from scratch and complete each step, or you can bypass basic setup steps that are already familiar to you. Either way, you end up with working code.

Create the test network. This example will use a very simple OpenZiti network, shown in Figure 1.

Oracle Java Certification, Core Java, Java Tutorial and Material, Oracle Java Learning, Oracle Java Career, Java Jobs, Java Skills
Figure 1. A simple OpenZiti network

For this article, it isn’t important for you to fully understand the components of the OpenZiti network; however, there are two important things to know.

◉ The controller manages the network, and it’s responsible for the configuration, authentication, and authorization of components that connect to the OpenZiti network.
◉ The router delivers traffic from the client to the server and back again.

To explore the architecture a little deeper, see “Overview of a Ziti Network” or watch this 54-minute video on YouTube.

OpenZiti provides a script that contains set of shell functions that bootstrap the OpenZiti client and network. As with any script, it is a good idea to download it and look it over before adding it to your shell. After running the following instructions, leave this terminal window open, because you’ll need it to configure the network.

# Pull the shell extensions
wget -q https://raw.githubusercontent.com/openziti/ziti/release-next/quickstart/docker/image/ziti-cli-functions.sh

# Source the shell extensions
. ziti-cli-functions.sh

# Pull the latest Ziti CLI and put it on your shell's classpath
getLatestZiti yes

The shell script above includes a few functions to initialize a network. To start the OpenZiti network overlay, run the following in the same terminal window:

expressInstall
startZitiController
waitForController
startExpressEdgeRouter

What do those functions do?

◉ expressInstall creates cryptographic material and configuration files required to run an OpenZiti network.
◉ startZitiController starts the network controller.
◉ startExpressEdgerouter starts the edge router.

Log in to the new network. The OpenZiti network is now up and running. The next step is to log in to the controller and establish the administrative session that you will use to configure the example services and identities. ziti-cli-functions has the following function to do that:

zitiLogin

Configure the new network. Use a script to configure the OpenZiti network. The code for the example contains a network directory. To configure the network, run the following command in the same terminal you used to start the OpenZiti network:

./express-network-config.sh

If the script produces errors with a lot of ziti: command not found statements, run the following shell command to put ziti in your terminal path:

getLatestZiti yes

The script will write out the two identity files (client.json and private-service.json) needed for the Java code you’ll write shortly. Note: The repository includes a file called NETWORK-SETUP.md that explains what the script is doing and why.

Reset the Ziti demo network. If you wish to start over, these are the commands that need to be run to stop the Ziti network and clean up.

stopAllEdgeRouters
stopZitiController
unsetZitiEnv
rm -rf ~/.ziti/quickstart

Host a dark service using Spring Boot


Now, you’re at the good part! There are three things that need to be done to host an OpenZiti service in a Spring Boot application.

◉ Add the OpenZiti Spring Boot dependency.

◉ Add two properties to the service to configure the service identity and service name.

◉ Add an OpenZiti Tomcat customizer to the main application component scan.

The example code contains an initial/server project. Pull that up in your favorite editor and follow along.

Add the OpenZiti Spring Boot dependency. The OpenZiti Spring Boot dependency is hosted on Maven Central.

If you are using Gradle, add the following to build.gradle:

implementation 'org.openziti:ziti-springboot:0.23.12'

If you prefer Maven, add the following to pom.xml:

<dependency>
         <groupId>org.openziti</groupId>
         <artifactId>ziti-springboot</artifactId>
         <version>0.23.12</version>
</dependency>

Add application properties. Open the application properties file: src/main/resources/application.properties. The Tomcat customizer provided by OpenZiti needs an identity and the name of the service that the identity will bind. If you followed along with the network setup above, the values will be the following:

ziti.id = ../../network/private-service.json
ziti.serviceName = demo-service

Configure the OpenZiti Tomcat customizer. The Tomcat customizer replaces the standard socket protocol with an OpenZiti protocol that knows how to bind a service to accept connections over the Ziti network. To enable this adapter, open the main application class: com.example.restservice.RestServiceApplication. Then replace

@SpringBootApplication

with

@SpringBootApplication (scanBasePackageClasses = {ZitiTomcatCustomizer.class, GreetingController.class})

Run the application. The OpenZiti Java SDK will connect to the test network, authenticate, and bind your service so that other OpenZiti overlay network clients can connect to it.

If you use Gradle, enter the following in a terminal window in your project directory:

./gradlew bootRun

If you use Maven, run the following in a terminal window in your project directory:

./mvnw spring-boot:run

Test the new Spring Boot service. The Spring Boot service you just created is now totally dark, with no listening ports. You can verify this by using the following command in a terminal window:

netstat -anp | grep 8080

You should find nothing marked as LISTENING. Now, the only way to access the service is via the OpenZiti network. Let’s write a simple client to connect to the service and check that everything is working correctly.

Create a sample Java client application


This section will use the OpenZiti Java SDK to connect to the OpenZiti network. The example source code includes a project and a class that takes care of the boilerplate stuff for you.

Connect to OpenZiti. The Java SDK needs to be initialized with an OpenZiti identity. It is polite to destroy the context once the code is done, so you will wrap it up in a try-catch construct with a finally block. Here is the code.

ZitiContext zitiContext = null;
try {
  zitiContext = Ziti.newContext(identityFile, "".toCharArray());
  long end = System.currentTimeMillis() + 10000;

  while (null == zitiContext.getService(serviceName) && System.currentTimeMillis() < end) {
    log.info("Waiting for {} to become available", serviceName);
    Thread.sleep(200);
  }

  if (null == zitiContext.getService(serviceName)) {
    throw new IllegalArgumentException(String.format("Service %s is not available on the OpenZiti network",serviceName));
  }
} catch (Throwable t) {
  log.error("OpenZiti network test failed", t);
}
finally {
  if( null != zitiContext ) zitiContext.destroy();
}

What’s going on here?

◉ Ziti.newContext loads the OpenZiti identity and starts the connection process.

◉ while() inserts a delay. It can take a little while to establish the connection with the OpenZiti network fabric. For long-running applications, this is typically not a problem, but for this little client you need to give the network some time to get everything ready.

◉ zitiContext.destroy() disposes of the context and cleans up resources locally and on the OpenZiti network.

Send a request to the service. The client now has a connection to the test OpenZiti network. Now the client can ask OpenZiti to dial the service and send some data.

Important: This client is for demonstration purposes only! You should never, ever write a raw HTTP request like this in a real app. OpenZiti has a couple of examples on GitHub that use OKHttp and Netty if you want to work up this code using a real HTTP client.

log.info("Dialing service");
ZitiConnection conn = zitiContext.dial(serviceName);
String request = "GET /greeting?name=MyName HTTP/1.1\n" +
"Accept: */*\n" +
"Host: example.web\n" +
"\n";
log.info("Sending request");
conn.write(request.getBytes(StandardCharsets.UTF_8));

Here’s an explanation of what the code does.

◉ ZitiConnection is a socket connection over the OpenZiti network fabric that can be used to exchange data with a Ziti service.

◉ zitiContext.dial opens a connection through the OpenZiti network to the service.

◉ request is used because the connection is essentially a plain socket. The request string is a plain HTTP GET command to the greeting endpoint in the Spring Boot app.

◉ conn.write sends the request over the OpenZiti network.

Read the service response. The service will respond to the request with a JSON greeting. Read the greeting and write it to the log.

byte[] buff = new byte[1024];
int i;
log.info("Reading response");
while (0 < (i = conn.read(buff,0, buff.length))) {
 log.info("=== " + new String(buff, 0, i) );
}

What’s happening? conn.read reads the data sent back from the Spring Boot service via the OpenZiti connection.

Run the client. If you use Gradle, run the following in a terminal window in the client project:

./gradlew build run

If you use Maven, run the following in a terminal window in the client project:

./mvnw package exec:java

Source: oracle.com

Related Posts

0 comments:

Post a Comment