Overview

Red Hat Virtualization is an open, software-defined platform that virtualizes Linux and Microsoft Windows workloads. Built on Red Hat Enterprise Linux and the Kernel-based Virtual Machine (KVM).
In some cases you would like to hide this platform behind a proxy, as the different services should not be directly reached from the outside network.

Nginx is a very lean and very fast http / https proxy, which i decide to install and configure for that use on a RHEL 7 host.

The aimed configuration should assure, that the client will not be redirected to the rhev-manager or any other service behind the proxy at all. So all host names or urls passed back to the client in the http / https answers need to be rewritten accordingly.

This is a bit more tricky than expected due to the redirect from http to https and due to the way RHV-M manages the login, there are quite a few redirects. These redirecting to new URLs is transferred via the “Location” or “Refresh” header and i’ve seen headers, in which the hostname was provided three times, all of which need to be rewritten. Also the URLs within html pages need to be adjusted.

Hint
This post works for all Web UIs of RHV except of the consoles. To reach the consoles through the same proxy machine please review the Using squid as forward proxy for vm console in front of Red Hat Virtualization 4.2 POST blog to this subject.

Installation

I’ve a RHEL 7 host with a current subscription attached to it. This subscription also includes access to Red Hat Software Collections (RHSCL), which offer additional SW with a much faster lifecycle than RHEL itself.

We need to enable the relevant repository:

subscription-manager repos --enable rhel-server-rhscl-7-rpms

and need to find the current nginx version and install it:

yum list "*nginx*"
yum install rh-nginx18

Configuration

ssl

As mentioned the proxy should also work for https. The proxy will terminate both ssl-sessions. This allows nginx to look into the communication and rewrite the hostnames as needed. It also simplifies things as certificate names, certificate residence and ssl-termination are matching the host in question.
For our session towards the rhv mangers, nginx acts as a client and no additional care needs to be taken. For the session to the client, our nginx-proxy is server and needs a ssl certificate.  If you do not have an official certificate authority (CA) you might want to create a self signed certificate.

[root@control ~]# openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout /etc/opt/rh/rh-nginx18/nginx/server.key -out /etc/opt/rh/rh-nginx18/nginx/server.crt

Generating a 2048 bit RSA private key
.......+++
................................................................+++
writing new private key to '/etc/opt/rh/rh-nginx18/nginx/server.key'
-----
You are about to be asked to enter information that will be incorporated into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [XX]:DE
State or Province Name (full name) []:Baden-Wuerttemberg
Locality Name (eg, city) [Default City]:Weinheim
Organization Name (eg, company) [Default Company Ltd]:Snake Oil
Organizational Unit Name (eg, section) []:
Common Name (eg, your name or your server's hostname) []:example.com
Email Address []:root@control.example.com

This certificate will be addressed from within the nginx configuration.

proxy config

It is good practice to leave the main configuration file mainly untouched and add the individual configuration into a separate file.
Unfortunately there is some web server – service configured in the main configuration which is not needed and may interfere with the needed proxying.

Commenting out the “main server” service in nginx.conf:

[root@control ~]# vi /etc/opt/rh/rh-nginx18/nginx/nginx.conf

    # Load modular configuration files from the /etc/nginx/conf.d directory.
    # See http://nginx.org/en/docs/ngx_core_module.html#include
    # for more information.
    include /etc/opt/rh/rh-nginx18/nginx/conf.d/*.conf;

##    server {
##        listen       80;
##        server_name  localhost;
##
##        #charset koi8-r;
##
##        #access_log  /var/opt/rh/rh-nginx18/log/nginx/host.access.log  main;
##
##        location / {
##            root   /opt/rh/rh-nginx18/root/usr/share/nginx/html;
##            index  index.html index.htm;
##        }
##
...
##    }

Then adding a separate configuration file for rhvm reverse proxy.
Please understand that the server (behind the proxy) is called engine.example.com and has the IP address 192.168.0.99.

[root@control ~]# vi /etc/opt/rh/rh-nginx18/nginx/conf.d/rhvm_proxy.conf

Within the file i create 2 server definitions. One is for http on port 80, the other for https on port 433. Within both definitions i take “/” as the location to forward to rhvm. For the client (and it’s users) this makes the URLs look and feel exactly like in real world without a proxy. Maybe this limits the forwarding to other services.
For the ssl server i also add some ssl specific configuration and make the server aware of the created certificate files.

[root@control ~]# vi /etc/opt/rh/rh-nginx18/nginx/conf.d/rhvm_proxy.conf 

# HTTP server
# Proxy with no SSL

    server {
      listen       80;
      #server_name  control.example.com;
      location / {
         ...
         ...
         ...
      }
    }

# HTTPS server
# Proxy with SSL
    server {
      listen       443;
      #server_name  control.example.com;
      location / {
         ...
         ...
         ...
      }
      ssl                  on;
      ssl_certificate      /etc/opt/rh/rh-nginx18/nginx/server.crt;
      ssl_certificate_key  /etc/opt/rh/rh-nginx18/nginx/server.key;
      ssl_session_timeout  5m;
      ssl_protocols  SSLv2 SSLv3 TLSv1;
      ssl_ciphers  HIGH:!aNULL:!MD5;
      ssl_prefer_server_ciphers   on;
      }
[root@control ~]# 

 

To forward every request to the rhvm engine behind the proxy we need the following lines inside both location definitions

         proxy_set_header Host engine.example.com;
         proxy_pass http://192.168.0.99:80$request_uri;

The first one sets the  Host header within the request sent to the server. The Host header is introduced with http Version 1.1 to allow name based virtual servers (many different domains behind the same IP address). As the rhvm should get the same requests as he would get when addressed directly, we assure that the Host header is set to the fqdn of our rhvm.

The second tells nginx, which protocol to use, which Host to connect to and which URI to request. I put in the IP-Adress, but am sure that the hostname would have worked likewise.

As rhvm sends quite a lot of answers, which inform the client which URL to connect to next, we need this to be rewritten. To change the “Location” and “Refresh” headers we need the proxy_redirect directive. This directive uses regular expressions as search string. To get the correct results, even if the fqdn of the rhvm-server was found multiple times in one header i created three search-and-replace expressions. One is catching the name 3 times, the next 2 times and the third would catch single occurrence of the fqdn.

         proxy_redirect ~^(.*)engine.example.com(.*)engine.example.com(.*)engine.example.com(.*)$ $1$http_host$2$http_host$3$http_host$4;
         proxy_redirect ~^(.*)engine.example.com(.*)engine.example.com(.*)$ $1$http_host$2$http_host$3;
         proxy_redirect ~^(.*)engine.example.com(.*)$ $1$http_host$2;

It is also necessary to replace any occurrence of the fqdn of rhvm within files sent back (like html pages). For this to happen we need the “ngx_http_sub_module” – module, which is compiled in in the RHSCL nginx, which i use. We make this submodule change the name back to the originally (from the client) requested Host name ($http_host). We make this work for any mime type, which is passed back and we allow this filter to replace any occurrence of the fqdn.

         sub_filter 'engine.example.com'  '$http_host';
         sub_filter_types *;
         sub_filter_once off;

 

The complete file looks like this:

[root@control ~]# vi /etc/opt/rh/rh-nginx18/nginx/conf.d/rhvm_proxy.conf 

# HTTP server
# Proxy with no SSL

    server {
      listen       80;
      #server_name  control.example.com;
      location / {
         # proxy_set_header Host $http_host;
         proxy_set_header Host engine.example.com;
         proxy_pass http://192.168.0.99:80$request_uri;

         proxy_redirect ~^(.*)engine.example.com(.*)engine.example.com(.*)engine.example.com(.*)$ $1$http_host$2$http_host$3$http_host$4;
         proxy_redirect ~^(.*)engine.example.com(.*)engine.example.com(.*)$ $1$http_host$2$http_host$3;
         proxy_redirect ~^(.*)engine.example.com(.*)$ $1$http_host$2;

         #proxy_redirect default;

         sub_filter 'engine.example.com'  '$http_host';
         sub_filter_types *;
         sub_filter_once off;
      }
    }

# HTTPS server
# Proxy with SSL
    server {
      listen       443;
      #server_name  control.example.com;
      location / {
         # proxy_set_header Host $http_host;
         proxy_set_header Host engine.example.com;
         proxy_pass https://192.168.0.99:443$request_uri;

         proxy_redirect ~^(.*)engine.example.com(.*)engine.example.com(.*)engine.example.com(.*)$ $1$http_host$2$http_host$3$http_host$4;
         proxy_redirect ~^(.*)engine.example.com(.*)engine.example.com(.*)$ $1$http_host$2$http_host$3;
         proxy_redirect ~^(.*)engine.example.com(.*)$ $1$http_host$2;

         #proxy_redirect default;

         sub_filter 'engine.example.com'  '$http_host';
         sub_filter_types *;

         sub_filter_once off;
      }
      ssl                  on;
      ssl_certificate      /etc/opt/rh/rh-nginx18/nginx/server.crt;
      ssl_certificate_key  /etc/opt/rh/rh-nginx18/nginx/server.key;
      ssl_session_timeout  5m;
      ssl_protocols  SSLv2 SSLv3 TLSv1;
      ssl_ciphers  HIGH:!aNULL:!MD5;
      ssl_prefer_server_ciphers   on;
      }
[root@control ~]#

 

start nginx

We also need to start and enable nginx. As it is part of RHSCL and you could also have multiple nginx in different versions installed. The unit-file filename therefore includes the version number:

systemctl list-unit-files | grep nginx
systemctl start rh-nginx18-nginx.service 
systemctl enable rh-nginx18-nginx.service

 

Conclusion

The usage of RHV-M Web-UI is fully functional through the nginx reverse proxy, no matter, if you want to manage RHV, want to access the documentation or download some client tools.

We need to assure, that the web console to some rhv-vm will also work as expected.

 

Links

I came across following links. While the first is from Red Hat, but did not give the final clues, the second helped to set up SSL correctly with Nginx.:

“How to configure nginx as a reverse proxy in RHEL?” https://access.redhat.com/solutions/1225423

“How To Configure Nginx with SSL as a Reverse Proxy for Jenkins”
https://www.digitalocean.com/community/tutorials/how-to-configure-nginx-with-ssl-as-a-reverse-proxy-for-jenkins

Using squid as forward proxy for vm console in front of Red Hat Virtualization 4.2:
https://mdschreier.com/2019/01/29/using-squid-as-forward-proxy-for-vm-console-in-front-of-red-hat-virtualization-4-2/