Apache NiFi Invalid SNI

The error message occurs to me when I'm trying to use Apache NiFi 2.x, either accessing with an IP address or a hostname that is not "localhost".


HTTP ERROR 400 Invalid SNI
URI:    /nifi
STATUS: 400
MESSAGE:    Invalid SNI
SERVLET:    -
CAUSED BY:  org.eclipse.jetty.http.BadMessageException: 400: Invalid SNI


org.eclipse.jetty.http.BadMessageException: 400: Invalid SNI
at org.eclipse.jetty.server.SecureRequestCustomizer.customize(SecureRequestCustomizer.java:266)
    at org.eclipse.jetty.server.SecureRequestCustomizer.customize(SecureRequestCustomizer.java:207)
    at org.eclipse.jetty.server.HttpChannel$RequestDispatchable.dispatch(HttpChannel.java:1594)
    at org.eclipse.jetty.server.HttpChannel.dispatch(HttpChannel.java:753)
    at org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:501)
    at org.eclipse.jetty.server.HttpChannel.run(HttpChannel.java:461)
    at org.eclipse.jetty.util.thread.strategy.AdaptiveExecutionStrategy.runTask(AdaptiveExecutionStrategy.java:421)
    at org.eclipse.jetty.util.thread.strategy.AdaptiveExecutionStrategy.consumeTask(AdaptiveExecutionStrategy.java:390)
    at org.eclipse.jetty.util.thread.strategy.AdaptiveExecutionStrategy.tryProduce(AdaptiveExecutionStrategy.java:277)
    at org.eclipse.jetty.util.thread.strategy.AdaptiveExecutionStrategy.produce(AdaptiveExecutionStrategy.java:193)
    at org.eclipse.jetty.http2.HTTP2Connection.produce(HTTP2Connection.java:208)
    at org.eclipse.jetty.http2.HTTP2Connection.onFillable(HTTP2Connection.java:155)
    at org.eclipse.jetty.http2.HTTP2Connection$FillableCallback.succeeded(HTTP2Connection.java:450)
    at org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:100)
    at org.eclipse.jetty.io.ssl.SslConnection$DecryptedEndPoint.onFillable(SslConnection.java:558)
    at org.eclipse.jetty.io.ssl.SslConnection.onFillable(SslConnection.java:379)
    at org.eclipse.jetty.io.ssl.SslConnection$2.succeeded(SslConnection.java:146)
    at org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:100)
    at org.eclipse.jetty.io.SelectableChannelEndPoint$1.run(SelectableChannelEndPoint.java:53)
    at org.eclipse.jetty.util.thread.strategy.AdaptiveExecutionStrategy.runTask(AdaptiveExecutionStrategy.java:421)
    at org.eclipse.jetty.util.thread.strategy.AdaptiveExecutionStrategy.consumeTask(AdaptiveExecutionStrategy.java:390)
    at org.eclipse.jetty.util.thread.strategy.AdaptiveExecutionStrategy.tryProduce(AdaptiveExecutionStrategy.java:277)
    at org.eclipse.jetty.util.thread.strategy.AdaptiveExecutionStrategy.run(AdaptiveExecutionStrategy.java:199)
    at org.eclipse.jetty.util.thread.ReservedThreadExecutor$ReservedThread.run(ReservedThreadExecutor.java:411)
    at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:969)
    at org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.doRunJob(QueuedThreadPool.java:1194)
    at org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.run(QueuedThreadPool.java:1149)
    at java.base/java.lang.Thread.run(Thread.java:1583)
According to one answer from stackoverflow, the reason behind this is that
"Jetty 10, which is included with NiFi 2.0.0-M1, incorporates updates to the Server Name Indication processing during the TLS handshake. As a result of these changes, the default behavior does not support accessing NiFi using an IP address. Using a hostname or DNS name will avoid the SNI error and allow standard TLS negotiation to work."

and the default hostname is "localhost", and it is only allowed to access via https://localhost:8443/nifi for example. This has some issues in the case of deploying NiFi in Kubernetes, which will result in the error message that we mentioned above.

Also, modifying the nifi.web.https.host field of nifi.properties does not solve the problem! 

In order to use other hostnames rather than "localhost", we need our keystore and truststore which by default only contains "localhost" to have our desired hostname as a SAN entry according to one answer from here.  And we could generate your own keystore and truststore with needed SAN entry(s).


How to generate our own keystore and trustore with needed SAN entry(s)?

We can use Apache NiFi toolkits, which are separate things from NiFi and can be downloeaded from the NiFi download page). One of these is the TLS Toolkit (I only found it in NiFi Toolkit 1.26.0 but not in Toolkit 2.x).

Then we can follow the "Securing NiFi with TLS Toolkit" section of the Apache NiFi Walkthroughs guide to generate our own keystore and trustore. For example, if we our desired hostname is "nifi.local", then we can use the command: 


./bin/tls-toolkit.sh standalone -n "nifi.local"
This will generate three files as follows in a folder called nifi.local
  • keystore.jks, 
  • truststore.jks, and 
  • nifi.properties

You can use those newly generated keystore and trustore and the corresponding nifi.properties to overwrite the previous files in our NiFi conf folder.

Those three files are the main things relevant to our error message that we want to resolve, and for the rest, we can follow the other steps mentioned in the "Securing NiFi with TLS Toolkit" section of the Apache NiFi Walkthroughs guide.

Hope it helps in solving similar problems from your end!

Connecting Apache NiFi with Jena Fuseki in Kubernetes

Background

The setup before was running both Apache NiFi and Apache Jena Fuseki using Docker in a single local machine. And I would like to deploy Fuseki instance on a Kubernetes (k8s) cluster. The NiFi processes incoming data stream and store semantified (a set of triples) into the knowledge graph stored in Fuseki, which can be queried afterwards.


Deploying Jena Fuseki using Helm 

I used Helm, a package manager for Kubernetes, just like dpkg in Debian. Helm charts simplify the deployment and management of applications on Kubernetes clusters by providing a consistent way to package, configure, and deploy applications and services. Similar to Docker hub, there are many helm charts available on https://artifacthub.io/. I tested one of them for deploying Fuseki on the k8s cluster: https://artifacthub.io/packages/helm/inseefrlab/jena.

There is no problem using a minikube in a local laptop by using the helm chart installation command for the Fuseki helm chart directly. However, it turns out that there is an issue of persistent volumes (PV) when installing the helm chart on the k8s cluster. To solve this issue, some familarity of PV, persistent volume claims (PVC), and Storage Class of k8s are required. 

Simply put, things need to consider include:
  • Create PV first for the persistent volume claims (PVC) of the deoployment of Fuseki
  • Make the volume (folder) in the (master) node has writing permission for running the Fuseki container. 
You can check PV or PVC using commands:
$ kubectl get pv
$ kubectl get pvc


401 Unauthorized Issue for Accessing Fuseki

Another issue was a "401 unauthorized" error when the Apache NiFi tries to write data via the GSP endpoint of Jena Fuseki. In this case, authentication is required with the connection to the server. Jena provides authentication guide here.

...
Authenticator authenticator = AuthLib.authenticator("username", "password");
HttpClient httpClient = HttpClient.newBuilder()
	.authenticator(authenticator)
	.build();
// Setup connection
RDFConnection connection = RDFConnectionFuseki.create()
	.destination(context.getProperty(DESTINATION).getValue())
	.gspEndpoint(context.getProperty(GSP_ENDPOINT).getValue())
	.acceptHeaderSelectQuery("application/sparql-results+json, application/sparql-results+xml;q=0.9")
	.httpClient(httpClient)
	.build();
...

The destination is set by one of the properties of the custom processor in NiFi, e.g., http://[IP address of Fuseki]:3030/ds/, and the gsp (SPARQL Graph Store Protocol) endpoint is also set by one of the properties, .e.g., data, depending on your Fuseki setup. 

Once authentication is added, the NiFi custom processor including the above code snippet is able to communicate with the Fuseki instance on k8s cluster without any problem and run the same as in the local environment.

How to check all available Java versions on Linux

In order to check the list of Java versions available on our Linux machine, we can use the following command.


$ sudo update-alternatives --config java



 Selection    Path                                            Priority   Status

------------------------------------------------------------

  0            /usr/lib/jvm/java-17-openjdk-amd64/bin/java      1711      auto mode

  1            /usr/lib/jvm/java-11-openjdk-amd64/bin/java      1111      manual mode

* 2            /usr/lib/jvm/java-17-openjdk-amd64/bin/java      1711      manual mode

  3            /usr/lib/jvm/java-8-openjdk-amd64/jre/bin/java   1081      manual mode



The one with * indicates the current version. We can check the current Java version being used directly as well.



$ java --version

openjdk 17.0.10 2024-01-16
OpenJDK Runtime Environment (build 17.0.10+7-Ubuntu-122.04.1)
OpenJDK 64-Bit Server VM (build 17.0.10+7-Ubuntu-122.04.1, mixed mode, sharing)