In praise of modern infra tooling

I’ve been managing servers in one capacity or another since 1999 or so. A lot has changed since then. Many changes have been gradual improvements. Hardware slowly got cheaper, colo slowly got cheaper, bandwidth got a lot cheaper really quickly, partially managed servers and VPSes have become dirt cheap.

With all those improvements on the hardware side of infra, it’s easy to overlook the improvements on the software side. Thanks to tools like Ansible and Let’s Encrypt, I can now deploy an HTTPS site on new hardware in minutes. Where most of those minutes is spent downloading software updates and rebooting into a new kernel. This may sound silly and trivial in 2022. 20 years ago, this would have taken DAYS, and would have involved any number of manual steps. Not only would it have cost me a lot more time, it would have also cost a lot more money.

So cheers. Remind me to buy the Ansible & Let’s Encrypt folks a beer or two. They’ve certainly earned it.


Apache HTTPD and LDAP attributes

More fun with Apache HTTPD. This time I wanted to access a user’s email address and user id, both stored in LDAP, in an application without LDAP integration. Thankfully, this turned out to be pretty easy. But first, some background.

The application is a very simple Java application, which sits behind HTTPD as a Reverse Proxy. The Reverse Proxy handles authentication, because reinveting the wheel gets a bit dull after a while, and mod_authnz_ldap works just fine. But while adding a feature to the application, I suddenly needed access to the user’s userid and email address, for Reasons™.

The documentation turned out to be somewhat helpful, if cryptic. There is a relatively easy way to expose LDAP attributes to CGI scripts using mod_authnz_ldap — which can set environment variables for perusal by the CGI script. Using the same mechanism in combination with a reverse proxy was a bit trickier; after all, the Java application is running in a Jetty container and it talks to the proxy using HTTP. That doesn’t really offer room for injecting environment variables.


On names

Trust no one

Input validation has been something of an obsession of mine ever since I picked up PHP back in the 90s. You can never trust user input. At all. To give an example, back when contact forms (without CAPTCHAs) were a thing that existed, many of them were abused to send spam to random people. How? By abusing naïve input validation in combination with PHP’s extremely shitty mail() header implementation. Instead of accepting headers as an array of headers – and only as an array of headers; it accepts them as a CRLF deliminated list of strings. So if you have an input field with the sender’s email address, and you want to helpfully include this in a From header in your email, you’d better validate the thing properly … because if not, spammers will abuse that From address to include CC or BCC headers and they will peddle their prick enhancing pills using your mail server. Or so it used to go, PHP’s implementation has improved somewhat, and developers aren’t as dumb as we were back then.

Ahem. I got a bit sidetracked there. Input validation. Important, yes.


Please accept Accept-Language

A bit of backstory …

Living in Belgium can have pretty interesting consequences. The puny country has 3 official languages (Dutch, French & German). Many more languages are spoken, of course, this is the 21st century and there are people of every possible origin here, and most countries are pretty similar in that regard. What’s less common is Belgium’s language divide. The country is split into multiple regions, with each region having a single official language — I’m conveniently ignoring Brussels here. This made some sense historically, after all, people had to fight (quite literally) for the right to speak their language. At least in the Dutch part of the country. French was the lingua franca for long time, and the tiny German speaking bit is the result of a couple of wars and is irrelevant in this discussion.


Protecting credentials from untrusted applications with HTTPD

Why?

Sometimes you just want to deploy a tool and protect it from unauthorized access. This is easy if the tool comes with built-in access control. Especially if there’s support for LDAP authentication or SAML. But sometimes, that’s just not the case. In which case you can just Apache HTTPD’s built-in mod_auth to enforce authentication with Basic Authentication. This works really well if the tool is something like a static HTML page, or even a PHP application. It gets a bit trickier if the tool requires running in its own process, like maybe a Java application running in an embedded Jetty instance. In which case you can use mod_proxy and mod_auth together. And hey presto, you’re done, right? Wrong.


On pointless Java 8 embellishments, or an exercise in simplicity

While performing a code review, I stumbled upon this little gem. Pasted verbatim, with the exception of the name of the first function.

Long num = someFunctionThatNeverReturnsNull();
if(Optional.ofNullable(num).orElse(0l) > 0) {
  // ...
}

The salient part, of course, is the if-statement. It’s rare to come across a single line with so many layers of wrongness.

  1. First, let’s talk about 0l. Depending on your font, that might look like zero one, zero el, o one or o el. By convention in Java, this should be zero capital-L: 0L. This is easier to read, and the suffix L makes it clear that we’re dealing with a long instead of an integer. That would look something like this:
if(Optional.ofNullable(num).orElse(0L) > 0) {
  // ...
}
  1. Second, what’s going on with these data types? 0L is now obviously a primitive long which will be auto-boxed to a Long object. num is a Long object. And then there’s the dangling 0. Which is a primitive integer (int). For one reason or another. Now, I admit that casting to long or int is pretty cheap. This is never going to be a performance issue. But consistency is a good thing. You probably want to compare like with like.
    There’s also a bit of weird auto-(un)boxing going on here. 0L will be auto-boxed to Long. But the result of the orElse() bit will be unboxed to a primitive long. The comparison will then be comparing a long to an int, which causes the int to be widened to a long.
    There isn’t much we can do about the auto-(un)boxing in this case, considering the comparison we want to perform. But we can at least ensure we’re using consistent data type width. So this:
if(Optional.ofNullable(num).orElse(0L) > 0L) {
  // ...
}
  1. And lastly, why the fuck are they using Optional in the first place? Never mind the fact that num can’t even be null here. The “plain”, non-embellished form of this statement would have been shorter and easier to read. That would look something like this:
if(null != num && num > 0L) {
  // ...
}

Here is the “corrected” version:

long num = someFunctionThatNeverReturnsNull();
if(num > 0L) {
  // ...
}

I can’t fathom why anyone would write garbage like this. It’s pointless. It’s hard to read. It’s ugly. And it’s bloody inefficient.


JAX-RS client filters

Life goes on with JAX-RS/Jersey. I wasted a couple of moments figuring out how to add custom headers to a Jersey generated JAX-RS client. Might as well write it down in the hope of saving someone a couple of minutes.

For starters, you’ll need a Client Filter that does the actual heavy(ish) lifting.

import javax.ws.rs.client.ClientRequestContext;
import javax.ws.rs.client.ClientRequestFilter;
 
/**
 * Add the X-GARBAGE header to all requests.
 */
public class GarbageFilter implements ClientRequestFilter {
	@Override
	public void filter(final ClientRequestContext requestContext) throws IOException {
		requestContext.getHeaders().add("X-GARBAGE", "This is added to all requests");
	}
}

And then you’ll have to register the filter with the Client(Config).

// import org.glassfish.jersey.client.ClientConfig;
 
final ClientConfig clientConfig = new ClientConfig();
clientConfig.register(new GarbageFilter()); // Yes, you could use JDK8 magic :-)
 
final Client client = ClientBuilder.newClient(clientConfig);

And that’s all. Every request you launch using the generated client will now contain your X-GARBAGE header.


JAX-RS client file upload

Another hiccup in using the wadl2java client generated from a (Jersey) JAX-RS app. This time, it concerns multipart/form-data.

The method:

@POST
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.MULTIPART_FORM_DATA)
public JsonFoo create(@FormDataParam("file") InputStream data, 
	@FormDataParam("file") FormDataContentDisposition fileDetail,
	@FormDataParam("file") FormDataBodyPart bodyPart) {
			// Implementation foo bar baz
	}

The WADL exposes something that looks like this.

<method id="create" name="POST">
	<request>
		<representation mediaType="multipart/form-data"/>
	</request>
	<!-- Response omitted for the sake of brevity -->
</method>

And the generated client has a method to go along with it. Unfortunately, it gives you no hints whatsoever as to how to actually provide a file/data.

// Long class names shortened
public static Create create(Client client, URI baseURI) {
	return new Create(client, baseURI);
}
 
// The Create object contains this little gem
public<T >T postMultipartFormDataAsJson(Object input, GenericType<T> returnType);

That’s wonderful. Unfortunately, if you pass in a java.io.File, nothing happens. The client barfs.

Many DuckDuckGo-searches, StackOverflow hunts and headscratchings later, I came up with a working solution:

// import import org.glassfish.jersey.media.multipart.FormDataMultiPart;
// import org.glassfish.jersey.media.multipart.file.FileDataBodyPart;
 
File file = new File(); // Your file!
FormDataMultiPart form = new FormDataMultiPart();
form.field("filename", file.getName());
form.bodyPart(new FileDataBodyPart("file", upload, new MediaType("image", "jpeg")));
 
Api.create(client, uri).postMultipartFormDataAsJson(form, new GenericType<CreateResponse>() {});

But wait! That won’t cut it. You also need to tell your Client that you want to use the Multipart Feature. Makes sense. If you don’t, you’ll end up with this exception.

org.glassfish.jersey.message.internal.MessageBodyProviderNotFoundException: MessageBodyWriter not found for media type=multipart/form-data, type=class org.glassfish.jersey.media.multipart.FormDataMultiPart, genericType=class org.glassfish.jersey.media.multipart.FormDataMultiPart.

// import org.glassfish.jersey.client.ClientConfig;
// import org.glassfish.jersey.media.multipart.MultiPartFeature;
 
final ClientConfig clientConfig = new ClientConfig();
clientConfig.register(MultiPartFeature.class);

And there you have it. File upload with JAX-RS and a wadl2java generated client.