OpenProject Apache reverse proxy with https secure connection

These are some notes on setting up OpenProject on a backend server (let’s call it backsrv.example.com), and accessing it via a front-end system (frontsrv.example.com). Normally we’d do the SSL termination at the reverse proxy, and there is some documentation on this. In this case I wanted to do things properly, and protect the login credentials all the way. This means using an https connection between the reverse proxy and the back end server.

Firstly, the reverse proxy has to trust the SSL certificate that the back end uses. There are several ways to go about this. I chose to set up a local certificate authority using the easy-rsa scripts (using another small virtual machine set up only for this purpose). For one connection this is probably overkill, but for multiple backends in the future it will make the administration a lot easier.

  • Set up CA
    • Debian 10, install easy-rsa package, do required setup.
  • Copy CA root certificate to frontsrv
    • For Debian systems, copy to /usr/local/share/ca-certificates/ and run update-ca-certificates
  • Create CSR on backsrv, copy it to CA, sign it and copy resulting certificate to backsrv. Put cert and key in sensible places (/etc/ssl/private/ and /etc/ssl/local-certs/). Make sure permissions are correct.
  • Configure Apache on backsrv and check cert works (for OpenProject edit /etc/openproject/installer.dat to put in the correct certificate paths and run openproject configure to update the config).

Set up Apache to do proxy stuff on frontsrv. Here’s the beginning fragment of default-ssl.conf that should work:

<IfModule mod_ssl.c>
        <VirtualHost _default_:443>
                ServerAdmin webmaster@localhost

                DocumentRoot /var/www/html

                RequestHeader edit Destination ^https http early

                SSLProxyEngine on
                SSLProxyCheckPeerName off

                # To openproject server on backserv
                ProxyPass /openproject https://backsrv.example.com/openproject
                ProxyPassReverse /openproject https://backsrv.example.com/openproject
                <Location /openproject>
                        ProxyPreserveHost On
                        Require all granted
                </Location>

You also need to go to the OpenProject web interface admin area, go to System Settings – General and change the Host name to the reverse proxy, and set protocol to https. It will complain if there’s a hostname mismatch (case sensitive, even!). You may also want to go to EmailEmail notifications and change the Emission email address to be consistent.

Don’t forget, need SSLProxyEngine on!

For OpenProject the subdirectory locations on the front and back ends do need to match.

The ProxyPreserveHost On is required per the OpenProject documentation. Unfortunately, that means it tries to match the name frontsrv.example.com to the back end cert, and the SSL handshake fails. This is the reason for the SSLProxyCheckPeerName off directive – it disables checking the certificate CN or Subject Alternative Names.

Apparently the SSLProxyCheckPeerName off can go in a <Proxy>...</Proxy> matching block with Apache 2.4.30 or newer, which would be nice. As it is this will turn it off for the whole vhost, which is a small lessening of security.

I suppose in principle we could create the certificate for the back end with the name of the front end, or add it to the SANs. I haven’t tried this and it seems like it could be a recipe for confusion and subtle bugs.

Secure disk wipe with Windows format command

From Windows 8 Microsoft snuck in a refinement to the format command. It is now possible to get it to do multi-pass random-number disk wipes. From the help (Win 10 20H2):

 /P:count  Zero every sector on the volume. After that, the volume
           will be overwritten "count" times using a different
           random number each time. If "count" is zero, no additional
           overwrites are made after zeroing every sector. This switch
           is ignored when /Q is specified.

So to do a single-pass random wipe:

  • Repartition disk with one partition (if desired) and give it a drive letter (let’s say F for this example). Probably a good idea to remove any OEM, EFI, recovery partitions like this. A quick way to do this is to use the clean command in diskpart.
  • Run format F: /P:1
  • If you feel like it finish up with a clean command in diskpart.

This should do a pass with all zeros, and then a random-number pass.

Note this isn’t a full ‘write random data to every block in the drive’ erase, but should still be secure enough for most purposes.

Triggering redetection of network type in Server 2012

Had an issue where a Windows Server 2012 R2 system could not be accessed by RDP or remote management, as the network type had changed to Private (and thus the firewall wasn’t letting these connections through). File sharing was still working.

Found solution via SpiceWorks forum. Restart the Network Location Awareness service (needed to log on to system locally to do this). This triggered a redetection and the type wend back to Domain. RDP etc then worked again.

Upgrading from m.2 SATA to Crucial NVMe drive on Latitude 7490

Scenario:

Dell Latitude 7490 with existing SATA m.2 SSD. We want to upgrade to larger NVMe drive (Crucial 1Tb).

First tried new drive in Startech NVMe USB enclosure (M2E1BMU31C). Downloaded Crucial cloning software (locked version of Acronis). Problem – not recognised as Crucial drive so Acronis won’t run.

Posts suggest that the new drive should be installed in the laptop first and the system booted via USB. So take current drive out and put it in a SATA USB m.2 enclosure. Attach this to USB-C port and reboot.

This doesn’t work. What does work is attaching it to a USB-A port instead. Then it boots with no intervention.

After that the disk was clones (with no reboot necessary!), the old dive disconnected and the system booted happily from the new drive.

Setting Windows 10 web proxy per-user

There are a couple of GUI routes for setting the system web proxy for Windows 10 – the old control panel page (via Network and Internet – Network Options):

Windows 7 and later Control Panel system web proxy settings panel.

And the new settings style:

Windows 10 system Proxy settings new style.

Note that the new style does not warn you that you may not be allowed to set the proxy – you can change the settings, but if you select another panel and then go back to Proxy your settings will be gone.

The reason for this is often that the system is configured to set the proxy at the machine level, not per-user. On domain systems this can be changed using Group policy. On standalone systems this can be changed using a registry key, located under

HKLM\Software\Policies\Microsoft\Windows\CurrentVersion\internet Settings

There is a DWORD key here called ProxySettingsPerUser (if not, create it). 0 means the proxy is set at machine level, 1 enables per-user settings.

If you change this to 1 then you should immediately be able to change the proxy settings.

ANSI control codes in Jekyll output breaking emails

Also see Publishing websites with Jekyll, Apache and SVN

If you send console output via email (like, say the output of jekyll build as part of a SVN post-commit hook script) if there are ANSI control characters in the string (e.g. colour codes) this can break things. In this case the mail command (Debian 9 default exim) was only sending text up to the first ANSI code, which meant that the jekyll build error messages (which are yellow and red) were missing.

To fix this pipe the text through ansi2txt (comes with the colorized-logs package in Debian and Ubuntu). This strips out all ANSI control codes making the string email safe.

(After this I pipe it through unix2dos to convert to CRLF line endings, as this appears to be the standard for email. On Debian this comes with the dos2unix package.)

The last line in the hook script then becomes

echo "$LOGVAR" | /usr/bin/ansi2txt | /usr/bin/unix2dos | mail -s "$REPOS_BASENAME build $REV" "$BUILD_EMAIL"

FDQNs in FlexNet license files

When trying to query FlexNet licenses using lmutil or similar from systems with a different DNS suffix, make sure that the license file server name contains the FDQN for the SERVER line. If not you can find that lmutils complains that the lmgrd process is not running, even though you can run the actual program with the appropriate license.

For example, with a line in the licence file like

SERVER servername 4eca3b4b8326 1055

running

lmutil lmstat -a -c 1055@servername.physics.gla.ac.uk

results in a HOST_NOT_FOUND error. Running the Client ANSLIC_ADMIN Utility gives the same error. However, ANSYS fires up as normal.

To fix this put the FDQN in the license file (refreshing the licence server afterwards!)

SERVER servername.physics.gla.ac.uk 4eca3b4b8326 1055

The lmutil queries should then work as normal.

Monitoring GPU temperatures with nvidia-smi and Check MK (OMD)

In the previous post on this subject we used code from Technische Universität Kaiserslautern to monitor our GPUs using OMD checkmk (now checkmk raw). With some new RTX2080s installed this broke, as the nvidia-smi check doesn’t report anything for ECC errors (rather than 0, as previous cards did). The solution was to remove the ECC checking completely.

The new scripts are:

On the client system in /usr/lib/check_mk_agent/local/ (or plugins/)

if which nvidia-smi >/dev/null; then
   echo '<<<nvidia_smi>>>'
   nvidia-smi -q -x > /tmp/.check_mk_nvidia_smi
   cards=$(xml_grep --text_only 'nvidia_smi_log/attached_gpus' /tmp/.check_mk_nvidia_smi | tr -d ' ')
   IFS=$'\n' names=($(xml_grep --text_only 'nvidia_smi_log/gpu/product_name' /tmp/.check_mk_nvidia_smi | tr -d ' '))
   IFS=$'\n' fan_speed=($(xml_grep --text_only 'nvidia_smi_log/gpu/fan_speed' /tmp/.check_mk_nvidia_smi | tr -d ' '))
   IFS=$'\n' gpu_utilization=($(xml_grep --text_only 'nvidia_smi_log/gpu/utilization/gpu_util' /tmp/.check_mk_nvidia_smi | tr -d ' '))
   IFS=$'\n' mem_utilization=($(xml_grep --text_only 'nvidia_smi_log/gpu/utilization/memory_util' /tmp/.check_mk_nvidia_smi | tr -d ' '))
   IFS=$'\n' temperature=($(xml_grep --text_only 'nvidia_smi_log/gpu/temperature/gpu_temp' /tmp/.check_mk_nvidia_smi | tr -d ' '))
   IFS=$'\n' power_draw=($(xml_grep --text_only 'nvidia_smi_log/gpu/power_readings/power_draw' /tmp/.check_mk_nvidia_smi | tr -d ' '))
   IFS=$'\n' power_limit=($(xml_grep --text_only 'nvidia_smi_log/gpu/power_readings/power_limit' /tmp/.check_mk_nvidia_smi | tr -d ' '))

   for i in $(seq 1 $cards) ; do
       index=$(($i - 1))
       fan_speed[$index]=${fan_speed[$index]/\%/}
       gpu_utilization[$index]=${gpu_utilization[$index]/\%/}
       mem_utilization[$index]=${mem_utilization[$index]/\%/}
       temperature[$index]=${temperature[$index]/C/}
       power_draw[$index]=${power_draw[$index]/W/}
       power_limit[$index]=${power_limit[$index]/W/}
       echo "$index ${names[$index]} ${fan_speed[$index]} ${gpu_utilization[$index]} ${mem_utilization[$index]} ${temperature[$index]} ${power_draw[$index]} ${power_limit[$index]}"
   done
fi
[/code title="nvidia_smi" lang="python"]

Don't forget to make it executable! You also need xml_grep installed.

On the OMD server at <code>/omd/sites/omd_XYZ/local/share/check_mk/checks/</code>


#!/usr/bin/python
# -*- encoding: utf-8; py-indent-offset: 4 -*-
# +------------------------------------------------------------------+
# |             ____ _               _        __  __ _  __           |
# |            / ___| |__   ___  ___| | __   |  \/  | |/ /           |
# |           | |   | '_ \ / _ \/ __| |/ /   | |\/| | ' /            |
# |           | |___| | | |  __/ (__|   <    | |  | | . \            |
# |            \____|_| |_|\___|\___|_|\_\___|_|  |_|_|\_\           |
# |                                                                  |
# | Copyright Mathias Kettner 2012             mk@mathias-kettner.de |
# +------------------------------------------------------------------+
#
# This file is part of Check_MK.
# The official homepage is at http://mathias-kettner.de/check_mk.
#
# check_mk is free software;  you can redistribute it and/or modify it
# under the  terms of the  GNU General Public License  as published by
# the Free Software Foundation in version 2.  check_mk is  distributed
# in the hope that it will be useful, but WITHOUT ANY WARRANTY;  with-
# out even the implied warranty of  MERCHANTABILITY  or  FITNESS FOR A
# PARTICULAR PURPOSE. See the  GNU General Public License for more de-
# ails.  You should have  received  a copy of the  GNU  General Public
# License along with GNU Make; see the file  COPYING.  If  not,  write
# to the Free Software Foundation, Inc., 51 Franklin St,  Fifth Floor,
# Boston, MA 02110-1301 USA.

#######################################
# Check developed by
#######################################
# Dr. Markus Hillenbrand
# University of Kaiserslautern, Germany
# hillenbr@rhrk.uni-kl.de
#######################################

# the inventory functions

def inventory_nvidia_smi_fan(info):
    inventory = []
    for line in info:
        if line[2] != 'N/A':
           inventory.append( ("GPU"+line[0], "", None) )
    return inventory
def inventory_nvidia_smi_gpuutil(info):
    inventory = []
    for line in info:
        if line[3] != 'N/A':
           inventory.append( ("GPU"+line[0], "", None) )
    return inventory
def inventory_nvidia_smi_memutil(info):
    inventory = []
    for line in info:
        if line[4] != 'N/A':
           inventory.append( ("GPU"+line[0], "", None) )
    return inventory
def inventory_nvidia_smi_temp(info):
    inventory = []
    for line in info:
        if line[5] != 'N/A':
           inventory.append( ("GPU"+line[0], "", None) )
    return inventory
def inventory_nvidia_smi_power(info):
    inventory = []
    for line in info:
        if line[6] != 'N/A' and line[7] != "N/A":
           inventory.append( ("GPU"+line[0], "", None) )
    return inventory

# the check functions

def check_nvidia_smi_fan(item, params, info):
    for line in info:
        if "GPU"+line[0] == item:
           value = int(line[2])
           perfdata = [('fan', value, 90, 95, 0, 100 )]
           if value > 95:
              return (2, "CRITICAL - %s fan speed is %d%%" % (line[1], value), perfdata)
           elif value > 90:
              return (1, "WARNING - %s fan speed is %d%%" % (line[1], value), perfdata)
           else:
              return (0, "OK - %s fan speed is %d%%" % (line[1], value), perfdata)
    return (3, "UNKNOWN - GPU %s not found in agent output" % item)

def check_nvidia_smi_gpuutil(item, params, info):
    for line in info:
        if "GPU"+line[0] == item:
           value = int(line[3])
           perfdata = [('gpuutil', value, 100, 100, 0, 100 )]
           return (0, "OK - %s utilization is %s%%" % (line[1], value), perfdata)
    return (3, "UNKNOWN - GPU %s not found in agent output" % item)

def check_nvidia_smi_memutil(item, params, info):
    for line in info:
        if "GPU"+line[0] == item:
           value = int(line[4])
           perfdata = [('memutil', value, 100, 100, 0, 100 )]
           if value > 95:
              return (2, "CRITICAL - %s memory utilization is %d%%" % (line[1], value), perfdata)
           elif value > 90:
              return (1, "WARNING - %s memory utilization is %d%%" % (line[1], value), perfdata)
           else:
              return (0, "OK - %s memory utilization is %d%%" % (line[1], value), perfdata)
    return (3, "UNKNOWN - GPU %s not found in agent output" % item)

def check_nvidia_smi_temp(item, params, info):
    for line in info:
        if "GPU"+line[0] == item:
           value = int(line[5])
           perfdata = [('temp', value, 80, 90, 0, 95 )]
           if value > 90:
              return (2, "CRITICAL - %s temperature is %dC" % (line[1], value), perfdata)
           elif value > 80:
              return (1, "WARNING - %s temperature is %dC" % (line[1], value), perfdata)
           else:
              return (0, "OK - %s temperature is %dC" % (line[1], value), perfdata)
    return (3, "UNKNOWN - GPU %s not found in agent output" % item)

def check_nvidia_smi_power(item, params, info):
    for line in info:
        if "GPU"+line[0] == item:
           draw = float(line[6])
           limit = float(line[7])
           value = draw * 100.0 / limit
           perfdata = [('power', draw, limit * 0.8, limit * 0.9, 0, limit )]
           if value > 90:
              return (2, "CRITICAL - %s power utilization is %d%% of %dW" % (line[1], value, limit), perfdata)
           elif value > 80:
              return (1, "WARNING - %s power utilization is %d%% of %dW" % (line[1], value, limit), perfdata)
           else:
              return (0, "OK - %s power utilization is %d%% of %dW" % (line[1], value, limit), perfdata)
    return (3, "UNKNOWN - GPU %s not found in agent output" % item)

# declare the check to Check_MK

check_info['nvidia_smi.fan']     = (check_nvidia_smi_fan,     "%s fan speed"      , 1, inventory_nvidia_smi_fan)
check_info['nvidia_smi.gpuutil'] = (check_nvidia_smi_gpuutil, "%s utilization"    , 1, inventory_nvidia_smi_gpuutil)
check_info['nvidia_smi.memutil'] = (check_nvidia_smi_memutil, "%s memory"         , 1, inventory_nvidia_smi_memutil)
check_info['nvidia_smi.temp']    = (check_nvidia_smi_temp,    "%s temperature"    , 1, inventory_nvidia_smi_temp)
check_info['nvidia_smi.power']   = (check_nvidia_smi_power,   "%s power"          , 1, inventory_nvidia_smi_power)

To get the pretty indicators put this in /omd/sites/omd_XYZ/share/check_mk/web/plugins/perfometer/

#!/usr/bin/python

def perfometer_nvidia_smi_fan(row, check_command, perf_data):
    varname, value, unit, warn, crit, minn, maxx = perf_data[0]
    perc_used = 100 * (float(value) / float(maxx))
    perc_free = 100 - float(perc_used)
    return str(value)+" %", '<table><tr>' \
                               + perfometer_td(perc_used, '#0f8') \
                               + perfometer_td(perc_free, '#fff') \
                               + '</tr></table>'
def perfometer_nvidia_smi_gpuutil(row, check_command, perf_data):
    varname, value, unit, warn, crit, minn, maxx = perf_data[0]
    perc_used = 100 * (float(value) / float(maxx))
    perc_free = 100 - float(perc_used)
    return str(value)+" %", '<table><tr>' \
                               + perfometer_td(perc_used, '#0f8') \
                               + perfometer_td(perc_free, '#fff') \
                               + '</tr></table>'
def perfometer_nvidia_smi_memutil(row, check_command, perf_data):
    varname, value, unit, warn, crit, minn, maxx = perf_data[0]
    perc_used = 100 * (float(value) / float(maxx))
    perc_free = 100 - float(perc_used)
    return str(value)+" %", '<table><tr>' \
                               + perfometer_td(perc_used, '#0f8') \
                               + perfometer_td(perc_free, '#fff') \
                               + '</tr></table>'
def perfometer_nvidia_smi_temp(row, check_command, perf_data):
    varname, value, unit, warn, crit, minn, maxx = perf_data[0]
    perc_used = 100 * (float(value) / float(maxx))
    perc_free = 100 - float(perc_used)
    return str(value)+" C", '<table><tr>' \
                               + perfometer_td(perc_used, '#0f8') \
                               + perfometer_td(perc_free, '#fff') \
                               + '</tr></table>'
def perfometer_nvidia_smi_power(row, check_command, perf_data):
    varname, value, unit, warn, crit, minn, maxx = perf_data[0]
    perc_used = 100 * (float(value) / float(maxx))
    perc_free = 100 - float(perc_used)
    return str(value)+" W", '<table><tr>' \
                               + perfometer_td(perc_used, '#0f8') \
                               + perfometer_td(perc_free, '#fff') \
                               + '</tr></table>'

perfometers['check_mk-nvidia_smi.fan']     = perfometer_nvidia_smi_fan
perfometers['check_mk-nvidia_smi.gpuutil'] = perfometer_nvidia_smi_gpuutil
perfometers['check_mk-nvidia_smi.memutil'] = perfometer_nvidia_smi_memutil
perfometers['check_mk-nvidia_smi.temp']    = perfometer_nvidia_smi_temp
perfometers['check_mk-nvidia_smi.power']   = perfometer_nvidia_smi_power

Notes on setting up Canon PiXMA iX6850 A3 inkjet

Windows driver – IJ Network tool does allow you to input IP address eventually (Mac version does not). Conveniently our print server was on the same subnet as the printer, so it found it straight away. Driver can be installed on Server 2012 and shared, but cannot be shared as a LPD queue (as Canon don’t use a standard IP port).

Printer does function as an IPP printer, and LPD (if enabled).

On the Mac, use the IP address of the printer – it doesn’t communicate properly with the DNS name.