Programming on the Road

I’ve been traveling for work recently and kicking myself for not bringing an RMC3 along. It’s not that I enjoy Crestron programming every waking minute of the day. A lot of times, I try to test programming out before I ship it off to someone as a “fix.” As control system programmers, we’re in a different boat from others where we can’t run our programs on our laptops to test. Specifically, if we’re writing SIMPL code, we have to have physical hardware accessible to us. It would be great to have a SIMPL Simulator for testing logic, but that’s not something Crestron provides and no way am I going to try reverse-engineering their LPZ file format (that’s specifically against the dealer agreement).

So what options do we have for a programmer on the road?

Probably the easiest way to setup VC-4 for testing is in a virtual machine. I’ve written about that before, so I wanted to try a different approach this time. I saw a discussion on the Discord server the other day about using Linode to host VC-4. I host some websites and mail server there, so I thought it would be fun to try setting up a VC-4 instance and documenting the process.

Sign Up

If you aren’t already signed up with Linode, you’ll need to create an account first. Or if you have some other cloud hosting provider you prefer, stick with that. I’m not shilling for Linode, they just have a product I really like and have used for years.

Once you’re all signed up and ready to create new virtual machines, move on to the next part.

Create a VM

At the top of the page, you’ll see a big Create button. Use that to create a new Linode:

Here is what I used for my Linode configuration:

You’ll also need to set a root password. Once you are satisfied, click the Create Linode button. It will take a seconds, but once your VM is ready, you can SSH into it.

Login and Security

Our first login will be as the root user. I’m using PuTTY to SSH into the new server. The IP address is shown in the Linode dashboard:

I don’t like logging in as root, so first thing I’ll do is create an admin user for myself. Use something like:

# useradd -s /bin/bash -m kiel
# passwd kiel
Changing password for user kiel.
New password:
Retype new password: 
passwd: all authentication tokens updated successfully.

In order to give ourselves root-like access, we need to be added to the wheel group:

# gpasswd -a kiel wheel
Addding user kiel to group wheel

Now I’m going to logout and reconnect as my non-root user. A quick test of sudo should be successful:

$ sudo su -
[sudo] password for kiel:
Last login: Sun Feb 20 15:22:40 UTC 2022 from 12.197.51.2 on pts/0
Last failed login: Sun Feb 20 15:39:42 UTC 2022 from 122.194.229.10 on ssh:notty
There were 49 failed login attempts since the last successful login.
#

See what I mean? Bots are immediately pounding on the server trying to get in. Lets take away root logins from SSH to try and limit their success. Edit the /etc/ssh/sshd_config file:

$ sudo vim /etc/ssh/sshd_config

I know vim can be frustrating, but we’ll be in and out, don’t worry! Find the setting we’re looking for by typing / and PermitRootLogin. Press Enter on the first match. Move the cursor over to yes and type dw. Press a and type no then press Escape. Type :wq to save and exit. See? Arcane, but not scary!

Now we just need to the SSH daemon to make sure it picks up the new config:

$ sudo systemctl restart sshd

Try duplicating your PuTTY session (right-click on the title bar and pick Duplicate Session) but try connecting as root. You shouldn’t get in now. Take that, bots! If you want to be more secure, disable password logins and only use key exchange. I’ll revisit this in another post since it’s a good idea, but for now, we’ll login with our unprivileged user.

Hostname

If you check our current hostname, it’s not very helpful:

$ hostname
45-33-121-20.ip.linodeusercontent.com

I have a domain I can use, so I’m going to setup up a hostname using that:

$ sudo hostnamectl set-hostname vc4.kielthecoder.com

And of course, on the DNS side, I need to point vc4.kielthecoder.com to my server. Once that’s good, I’m going to reboot the server just to make sure everything comes up with the new name:

$ sudo reboot

Give it a minute, then see if you can ping the new address from Windows:

> ping vc4.kielthecoder.com

Pinging vc4.kielthecoder.com [45.33.121.20] with 32 bytes of data:
Reply from 45.33.121.20: bytes=32 time=27ms TTL=53
Reply from 45.33.121.20: bytes=32 time=9ms TTL=53
Reply from 45.33.121.20: bytes=32 time=8ms TTL=53
Reply from 45.33.121.20: bytes=32 time=9ms TTL=53

Ping statistics for 45.33.121.20:
    Packets: Sent = 4, Received = 4, Lost = 0 (0% loss),
Approximate round trip times in milli-seconds:
    Minimum = 8ms, Maximum = 27ms, Average = 13ms

All good!

Get VC-4 Software

Next, I’m going to download the latest VC-4 software from Crestron (2.7000.00047 at the time) and copy that to my home directory on the server. I like using WinSCP to copy files from Windows to servers since you can just drag and drop them.

Unfortunately, unzip isn’t installed by default in Rocky Linux, but we can quickly add it:

$ sudo dnf install unzip

Then unzip the archive:

$ unzip vc-4_2.7000.00047.zip

Once that completes, we can run the setup script:

$ cd vc4
$ sudo ./installVC4.sh

The script will recognize that we’re installing on Rocky Linux and add the necessary package repos for us. Then it installs a hundred or so packages.

Towards the end of the script, it will start asking some questions:

Are you migrating VC4 from another build?  (Y/N) : N
Proceeding with non-migration installation

Enter virtualcontrol Home Path: ( Default: /opt/crestron/ )
Copying files to /opt/crestron/

Please provide redis port number (current redis value is 6980 )
Press Enter To Continue With Default Value (6980):

Please provide new password for the MariaDB Root user: 

Please provide a name for the database or press enter to accept the default (default is VirtualControl):

Please provide the name for the  database's user account  (default is virtualcontrol):

Please provide a password for the virtualcontrol user or press enter to accept the default (default is [RANDOM STRING]):

As you can see, I’m just accepting a lot of the defaults here (you do have to type in a database password though). After the installation finishes, try hitting the VC-4 web portal at: http://vc4.kielthecoder.com/VirtualControl/config/settings/

Voilà! It works!

HTTPS

Right now, everybody on the Internet can access our server. And trust me, everybody is because it’s wide open. Good news is that it’s not a whole lot of work to lock it down for just us.

First thing we’ll want to do is get an SSL certificate. Certbot is a great tool for easily managing SSL certificates. Unfortunately, it isn’t available in Rocky Linux until you add the EPEL repo:

$ sudo dnf install epel-release

Certbot is picky about our default Apache configuration. We need to setup a VirtualHost and serve our application from there. Edit /etc/httpd/conf.modules.d/crestron.conf and wrap the whole thing in a VirtualHost section:

<VirtualHost *:80>
  ServerName vc4.kielthecoder.com

  #Define the VC-4 home folder on the disk here.  The install script should fill this in correctly
  Define CRESTRON_VC_4_HOME /opt/crestron/virtualcontrol/

  #This variable represents the web root
  Define CRESTRON_VC_4_WEBROOT /VirtualControl

  #Turn on debugs if needed by uncommenting the line below
  #LogLevel debug rewrite:trace5

  RewriteEngine  on

  #Provide Aliases for various folders
  AliasMatch "^${CRESTRON_VC_4_WEBROOT}/Rooms/([^/]+)/Html(.*)" "${CRESTRON_VC_4_HOME}/RunningPrograms/$1/Html$2"
  AliasMatch "^${CRESTRON_VC_4_WEBROOT}/Rooms/([^/]+)/XPanel(.*)" "${CRESTRON_VC_4_HOME}/RunningPrograms/$1/XPanel$2"
  AliasMatch "^${CRESTRON_VC_4_WEBROOT}/ProgramLibrary/([^/]+)/Project(.*)" "${CRESTRON_VC_4_HOME}/ProgramLibrary/$1/Project$2"
  AliasMatch "^${CRESTRON_VC_4_WEBROOT}/ProgramLibrary/([^/]+)/Mobility(.*)" "${CRESTRON_VC_4_HOME}/ProgramLibrary/$1/Mobility$2"
  AliasMatch "^/Device/UserInterfaceConfig" "${CRESTRON_VC_4_HOME}/webui-status/dynamicElements/THOMAS_EDISON/UserInterfaceConfig.json"

  AliasMatch "^${CRESTRON_VC_4_WEBROOT}/config/docs/VirtualControlManual.pdf" "${CRESTRON_VC_4_HOME}/doc/VirtualControlManual.pdf"

  #Allow access to individual directories
  <Directory  ${CRESTRON_VC_4_HOME}/doc>
    Options Indexes FollowSymLinks MultiViews
    Require all granted
  </Directory>

  <Directory ${CRESTRON_VC_4_HOME}/>
    AllowOverride All
    Require all denied
  </Directory>

  #Allow access to individual directories
  <Directory  ${CRESTRON_VC_4_HOME}/RunningPrograms>
    Options Indexes FollowSymLinks MultiViews
    Require all granted
  </Directory>

  <Directory  ${CRESTRON_VC_4_HOME}/ProgramLibrary>
    Options Indexes FollowSymLinks MultiViews
    Require all granted
  </Directory>

  <Directory  ${CRESTRON_VC_4_HOME}/webui-status>
    Options Indexes FollowSymLinks MultiViews
    Require all granted
  </Directory>

  <Directory  ${CRESTRON_VC_4_HOME}/webui-settings>
    Options Indexes FollowSymLinks MultiViews
    Require all granted
  </Directory>

  AliasMatch "(?i)^${CRESTRON_VC_4_WEBROOT}/config/status(.*)" "${CRESTRON_VC_4_HOME}/webui-status$1"
  AliasMatch "(?i)^${CRESTRON_VC_4_WEBROOT}/config/settings(.*)" "${CRESTRON_VC_4_HOME}/webui-settings$1"

  RewriteEngine  on

  <Directory ${CRESTRON_VC_4_HOME}/webui-status>
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteCond %{REQUEST_FILENAME} !-d
    # Angular reroute - This section will be taken out
    RewriteRule ^webView(.*) "${CRESTRON_VC_4_WEBROOT}/config/status" [R]
  </Directory>

  <Directory ${CRESTRON_VC_4_HOME}/webui-settings>
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteCond %{REQUEST_FILENAME} !-d
    # Angular reroute - This section will be taken out
    RewriteRule ^webView(.*) "${CRESTRON_VC_4_WEBROOT}/config/settings" [R]
  </Directory>

  #Proxy configuration.  Redirect API calls to Web App
  <Proxy *>
    Order deny,allow
    Allow from all
  </Proxy>

  ProxyPreserveHost On

  <Proxy "fcgi://localhost/cws">
  </Proxy>

  #Adjust cws
  AliasMatch "^${CRESTRON_VC_4_WEBROOT}/Rooms/([^/]+)/cws/(.*)" "${CRESTRON_VC_4_HOME}/CrestronApps/$1/$2"
  <Directory ${CRESTRON_VC_4_HOME}/CrestronApps>
    Require all granted
    RewriteRule "^([^/]+)/(.*)" "/cws/$2" [PT,E=MATCH_ROOM_ID:$1,H=proxy:unix:${CRESTRON_VC_4_HOME}/var/run/app-$1.socket|fcgi://localhost/cws]
  </Directory>

  # Enable request processing
  SecRuleEngine On
  # enable inspection of request bodies
  SecRequestBodyAccess On
  # set actual request size limit
  SecRequestBodyLimit 134217728
  # actually generate an HTTP error, instead of truncating
  SecRequestBodyLimitAction Reject

  # Settings api redirect
  <Location "${CRESTRON_VC_4_WEBROOT}/config/settings/WebApi/">
    ProxyPass "http://127.0.0.1:5000/"
    ProxyPassReverse "http://127.0.0.1:5000/"
    Header set Cache-Control "max-age=0, no-cache, no-store, must-revalidate, private"
    Header set Pragma "no-cache"
  </Location>

  # Status api redirect
  <Location "${CRESTRON_VC_4_WEBROOT}/config/status/WebApi/">
    ProxyPass "http://127.0.0.1:5000/"
    ProxyPassReverse "http://127.0.0.1:5000/"
    Header set Cache-Control "max-age=0, no-cache, no-store, must-revalidate, private"
    Header set Pragma "no-cache"
  </Location>

  <LocationMatch "^${CRESTRON_VC_4_WEBROOT}/config/api/(.*)">
    ProxyPass "http://127.0.0.1:5000/api/$1"
    ProxyPassReverse "http://127.0.0.1:5000/api/$1"
    Header set Cache-Control "max-age=0, no-cache, no-store, must-revalidate, private"
    Header set Pragma "no-cache"
  </LocationMatch>

  SecRule REQUEST_METHOD "@rx ^(?:POST|PUT)$" \
        "phase:1 \
        id:400101 \
        chain"
        SecRule REQUEST_URI "@contains ProgramLibrary" \
                "t:none,\
                ctl:requestBodyLimit=1073741824"

  SecRule REQUEST_METHOD "@rx ^(?:POST|PUT)$" \
        "phase:1 \
        id:400102 \
        chain"
        SecRule REQUEST_URI "@contains ProgramLibrary" \
               "t:none,\
               ctl:ruleRemoveById=200003"

</VirtualHost>

Now install Certbot and Apache mod_ssl:

$ sudo dnf install certbot python3-certbot-apache mod_ssl

Next, run Certbot:

$ sudo certbot --apache -d vc4.kielthecoder.com

Certbot warns us that we should review the Rewrite rules since it adds one to redirect HTTP to HTTPS. Now try going to https://vc4.kielthecoder.com/VirtualControl/config/settings/ to make sure HTTPS is working.

Authentication and Authorization

The last bit I want to focus on for now is authentication. Setting up HTTPS means we can serve pages encrypted over the Internet, but we still allow anyone to access our site. We need to setup authentication so users have to login before they can change any settings.

Create a new username/password using the htpasswd command:

$ sudo htpasswd -c /etc/httpd/passwords crestron
New password:
Re-type new password:
Adding password for user crestron

The easiest way to add authorization to the VC-4 web UI is to modify the /opt/crestron/virtualcontrol/webui/.htaccess file:

Options -Indexes
AuthType Basic
AuthName "Password Protected"
AuthUserFile "/etc/httpd/passwords"
Require user crestron

Restart Apache:

$ sudo systemctl restart httpd

Now when you hit the VC-4 portal, you need to login first:

Conclusion

Now that VC-4 is up and running, I can load up a test program to make sure I can hit it over the Internet:

This was a quick-and-dirty setup of VC-4 on a new Linode. It needs more work to secure it better, but that’s probably best left for another time. Thanks for reading!

4 thoughts on “Programming on the Road

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s