Showing posts with label proxy. Show all posts
Showing posts with label proxy. Show all posts

Saturday, October 8, 2022

Running a vaadin tomcat cluster behind a reverse proxy

Vaadin is full stack java framework to write web applications.

One of the interesting parts is, that you can write the UI completly in java, so you won't have to mess with different technologies and languages.

On the other side, it's easy to include webcomponents or write your own and connect them to your java application.

Vaadin also has a powerfull integrated push system, which allows you to push UI updates/notifications to the client when they are "ready". It's based on athmosphere and can benefit from websockets, but also works/falls back to standard http long polling http requests if nothing else works.

If you are using a single tomcat / servlet container instance, then you either expose that one to your customers or put some proxy in front of it. It's pretty standard and you find a lot of examples on how to do this with nginx or apache. You can either use plain http+websocket proxy, or (in the case of apache) also the ajp connector to the backend service.

But if you have multiple backend tomcat / sevlet containers running for redundacy/scaling/... then things get more complicated.

The vaadin framework is usually statefull, you you just can't route each new request to any one of your backend servers. You will have to always route the requests to the same servlet engine.

This is called "sticky session", since a session is sticking on the same backend server.

When you use nginx as the frontend/loadbalancer service, then you will get sticky sessions too, but only based in the ip (and port) of the client. https://docs.nginx.com/nginx/admin-guide/load-balancer/http-load-balancer/

This works fine as long as you users are spread over the world (or better use different IP addresses), the load is distributed amoung the backend servers.

But if you have a company application, which is for example used at two locations in the world, with each 50 users, the distribution of the requests can occur to route all to exaclty one of the backend servers and the others waiting for work to do.

This is because nginx uses the hash of the client public ip address, so all users behind the same ip will be routed to the same backend, which isn't what we intend.

Nginx also has the ability to route the requests based on a cookie you define (on in case of servlet engines is caled JSESSIONID). But that feature is not available in the free nginx version, it's premium feature requiring a pricy subscription. https://docs.nginx.com/nginx/admin-guide/load-balancer/http-load-balancer/#enabling-session-persistence

So if you wan't to stay free of costs for the load balancer you have to choose something else.

There exist dedicated HA proxies for this, but we did choose to use the Apache webserver for this.

The basic confirguration for a apache reverse proxy is simple, a bit more complex with load balancing and even less documented for load balancing with websockets.

This is why this post exists, I did search (most of the?) internet for informations on how to achieve this, but only did find fragments of the solution and often with wrong parts in it.

So our solution does correctly proxy http/http2/websocket requests to the backend servers in a sticky way. The ssl/https configuration is not part of this post, but you can use standard ways for this.

This setup is valid for all Vaadin Flow 23.0 and 23.1 and 23.2. setups. For Vaadin 23.3 and later, the push endpoints have been simplified for a better lb configuration. https://github.com/vaadin/flow/issues/14641

So here what our config looks like, we will explaing the severals parts later:

# 1

ProxyRequests Off

#2
ProxyPass /images/ !
ProxyPass /.well-known/ !

#3

RewriteEngine On
RewriteCond %{REQUEST_URI} ^/ [NC]
RewriteCond %{QUERY_STRING} transport=websocket [NC]
RewriteRule /(.*)       balancer://backend-ws/$1 [P,L]
 

#4
ProxyPass / balancer://backend/
ProxyPassReverse / balancer://backend/

#5

<Proxy balancer://backend>
    BalancerMember http://192.168.1.50:8080 route=backend1
    BalancerMember http://192.168.1.51:8080 route=backend2
    ProxySet stickysession=JSESSIONID
</Proxy>
 

#6
<Proxy balancer://backend-ws>
    BalancerMember ws://192.168.1.50:8080 route=backend1
    BalancerMember ws://192.168.1.51:8080 route=backend2
    ProxySet stickysession=JSESSIONID
</Proxy>

And in your tomcat server.xml on the backend servers:

<Engine name="Catalina" defaultHost="backend.service.ch" jvmRoute="backend1">

...

</Engine>

So lets explain the parts:

1. General proxy config

ProxyRequests Off

Make sure to have this in your config, otherwise your webserver can be missused to proxy any request to the internet, turning your server in an open proxy.

#2
ProxyPass /images/ !
ProxyPass /.well-known/ !

With these you can serve static content direcly from you apache webserver (As long as it has access to that content)

The .well-known this is usually needed when you use letsencrypt certificates for https

#3

RewriteEngine On
RewriteCond %{REQUEST_URI} ^/ [NC]
RewriteCond %{QUERY_STRING} transport=websocket [NC]
RewriteRule /(.*)       balancer://backend-ws/$1 [P,L]

The roles above make sure to correctly handle http->websocket upgrade requests and send them to the websocket balancer.

Depending on you application/backend you will need to tune the rewrites, but these here work for a vaadin application.

It is known that a rewrite rule is not optimal from a performance point of view, but so far I know of no other solution, until Vaadin 24 will hopefully use a dedicated push/websocket endpoint.

#4
ProxyPass / balancer://backend/
ProxyPassReverse / balancer://backend/

Here we route the normal http and http2 requests to the http balancer. Please take care to include the trailing / after the backend, otherwise you will receive strange errors like "No protocol handler was valid for the URL /home (scheme 'balancer')" in your server error log

#5

<Proxy balancer://backend>
    BalancerMember http://192.168.1.50:8080 route=backend1
    BalancerMember http://192.168.1.51:8080 route=backend2
    ProxySet stickysession=JSESSIONID
</Proxy>
This is the load balancer to route the requests to the two backend servers, you can add more if you have more of them.

The stickysession indicates to use the JSESSIONID cookie to match the requests to the correct backend. The name of the route should match your jvmRoute entry in the server.xml file for each backend.

#6
<Proxy balancer://backend-ws>
    BalancerMember ws://192.168.1.50:8080 route=backend1
    BalancerMember ws://192.168.1.51:8080 route=backend2
    ProxySet stickysession=JSESSIONID
</Proxy>

Same as #5, but for the websocket requests.

As you see in the balancer definitions, the backend servers are connected via unencrypted http/ws. If you need to use https/wss toward the backend servers too, then you can just replace the backend server definitions with https/wss. But of course you will then also have to handle the certificates on tomcat side too.

Required apache modules for this to work:

Apache should use the event mpm if possible, for better handling of http2 and websokets. Other mpm might work, I have not tested them.

As for the modules themself, enable these:

http2 -> For http/2 of course

proxy -> General basic proxy funcionality

proxy_balancer -> To used balancers toward the backend

proxy_http -> For http 1.x proxy requests

proxy_http2 -> For http/2 proxy requests

proxy_wstunnel -> For websocket proxy stuff

rewrite -> To identify and redirect websocket requests to the ws balancer

lbmethod_byrequests -> type of loadbalancing to use

In debian you can just use a2enmod <module_name> to enable them, on other distributions when commands vary, but finally these modules must be active to have a full http/http2/ws load balancer for Vaadin.

This setup work for Vaadin 23.1.x, for Vaadin 24 there is a discussion going on about having dedicated endpoint for push/websockets. https://github.com/vaadin/flow/issues/14641#issuecomment-1266519119

And a documentation (still to be done) about Vaadin and reverse proxy setups

https://github.com/vaadin/docs/issues/1776#issuecomment-1272384234


Tuesday, September 1, 2020

Use perdition as ssl offload proxy for imap / pop and managesieve

 In modern setups you often have an ingress controller, which does the ssl termination of the connections and then routes the traffic to the correct backend(s)

For kubernetes and http(s), often nginx is used for that task.

When you wish to do the same for imap and/or pop, then it also possible to use nginx for this.

https://docs.nginx.com/nginx/admin-guide/mail-proxy/mail-proxy/

But the main drawback is, that you need to implement some authentication and routing based on an http request.Also the managesieve protocol isn't supported.

If you don't wish to do the hassle with this, or you haven't the required infos to do it, then perdition can handle this be used as a full imap/pop/managesieve proxy.

The basic setup is quite simple, but has a few things to be aware of, when using it with ssl and/or ipv6.

When you enable ssl, then you have to specify the certificate files.

Usually something like this:

ssl_ca_file /etc/postfix/ssl/ssl-root.ca
ssl_cert_file /etc/postfix/ssl/myserver.crt
ssl_key_file /etc/postfix/ssl/myserver.key

When you then start perdition, it will probably log some warning about not beeing able to read the DH parameters from the certificate file. 

could not read DH params from cert file

Modern OpenSSL configurations require Diffie-Hellman values to generate secure keys in the exchange.

If your certificate does not have these embedded in it, you can generate them yourself and add them to the certificate.

openssl dhparam -out dhparams.pem 4096

Then just append the content of the dhparams.pem file to your .crt file and perdition has the required DH values.

The second thing you might struggle with, when you start perdition on an IPv6 enable host, then perdition will only bind the IPv4 address and not to the IPv6 address.

Unfortunally the documentation is lacking in this area, as how to bind the IPv6 ports too.

The correct syntax is to use this in the perdition.imap4s etc. files:

bind_address 88.xx.xx.xx,"[2a01:xxx:xxx:xxx::xxx]"

Please note that you must specify both IPvç and IPv6 addresses, and also that the IPv6 "[::]" will not work.

You have to specify the IPv6 address and enclose it in "[...]", including the " characters.

Configuring perdition as managesieve proxy is also not very well documented.

Specifying the sieve capabilities is rather tricky, here a wroking example:

capability \
"\"IMPLEMENTATION\" \"Cyrus timsieved\"  "\
"\"SIEVE\" \"comparator-i;octet "\
"comparator-i;ascii-numeric "\
"fileinto "\
"reject "\
"vacation "\
"imapflags "\
"notify "\
"envelope "\
"relational "\
"regex "\
"subaddress "\
"copy\"  "\
"\"SASL\" \"PLAIN\""


 

The important things to note in this:

Use \" to delimit the capabilities, and use two spaces to delimit the capability lines .

Have a look at the base config file perdition.conf as a staring point

If you have clients using K9 mail (And probably other too), then you might remove all "AUTH=..." settings from the imap capability string.

https://lists.vergenet.net/pipermail/perdition-users/2011-August/002547.html

Please also see my upcomming post  on monitoring perdition with Zabbix


Wednesday, June 15, 2011

Webproxy auto configuration, the (hard) MS way

In windows you can use automatic proxy detection to configure all your local clients to use the proxy settings you wish.

In short you do the following:

  1. Create a file named wpad.dat on a webserver named wpad.mycompany.local (Or whatever your dns domain is)
  2. In that file you put the prxy definition, which ports, urls etc. (See here for the details)
  3. Make sure your DNS answers the query for wpad.mycompany.local with the web server delivering the wpad.dat file

So far, everything is simple and easy to understand, but... it does not work if you use microsoft products for DNS and/or Webserver.

There are two pitfalls in the MS products:

1. DNS problem

Do a query for wpad.mycompany.local (Like nslookup wpad.mycompany.local)
You would now assume your DNS server returns you the configured IP.
Nada, nothing, it just return a message telling you it's not a known name. Double check it, it's just not working.
The DNS server does not answer the question properly.

The reason for this is "security", looks like the query for wpad. is a dangerous query, so MS blocks these in the DNS servers. Don't believe it ? Look here

The simplest way is to just disable that queryblocklist on your DNS server(s)

dnscmd [dnsservername] /config /enableglobalqueryblocklist 0

2. IIS problem

When you finally got past the DNS problem and your dns servers answer queries for wpad.mycompany.local correctly, then the next problem jumps in.

Just open your web browser and enter: http://wpda.mycompany.local/wpad.dat
You would expect that you see the content of that text file, but no, IIS thinks it's not something we wish to deliver to the clients.

The reason is, that the .dat extension is not assigned to a mime type, so IIS does not deliver these files and just returns a "File not found"

As the last step you make .dat known as mime type "application/x-ns-proxy-autoconfig"