SSL Certificate for server

Hello,

I have a local instance of the web app running great. My Farmbot talks to it fine, etc. However, I would like setup my wildcard SSL certificate so I can safely expose the server to the Internet. I totally get that I can and should use the public web app and there is little support available for local servers, but I’m an IT guy and running my own server is part of the fun for me in having the Farmbot.

I could just use a little direction as to which configuration file I should be looking at to point to my SSL certificate files.

Thanks!

@stre1026 Greetings!

I could just use a little direction as to which configuration file I should be looking at to point to my SSL certificate files.

It is a bit more complicated than updating a configuration file. Since this is a custom application rather than an off-the-shelf component (like WordPress, NGinx, etc…) you will need to modify the application source code. Forum user @Pitou is currently working on such a solution, but I believe he is not finished yet. If you are familiar with Ruby on Rails software development, I would be happy to help you along the way. I would also be willing to incorporate your solution in to the FarmBot Web App, given that it meets code quality standards.

I tried to add SSL support to self-hosted setups a few years ago, but had to stop in the name of time. FarmBot, Inc hosts my.farm.bot on Heroku, and Heroku manages our certificates for us. From a business perspective, I can’t spend time supporting self-hosted setups since our main focus is users of my.farm.bot. As you may have noticed, there are a number of users on the forum that wish feature development would go faster, and spending time investigating SSL configs for self-hosting would slow down our two developer team substantially. Self-hosting is one area where we are open to solutions from the community, but can’t invest any resources of our own.

I hope that helps. Please let me know if you have any questions about the Rails / RabbitMQ setup. From my initial investigation, I believe integrating RabbitMQ is the most difficult part.

@RickCarlino Thank you!

I totally understand and appreciate your position. It only makes sense to deploy your resources to things that will help everyone and not the 1% that decides to go self-hosted.

You gave the information I needed to know. I was curious how you had it implemented in the cloud. I’m not at all versed with Ruby (I thought it was dead years ago. I have since learned that is not the case – sorry! :slight_smile: )

I noticed it looked like you were using Passenger so I tried to secure that with SSL but it didn’t seem to work.

Maybe the best way to go about getting HTTPS for the web app so I can get access to it remotely is to put a reverse proxy in front of it. Since the Farmbot and the server are on the same LAN, I really only care about protecting external access to the site. Putting a reverse-proxy in front of it should take care of that problem theoretically?

Thanks for everything! I’m sure I’ll have more questions as I got along. I figured out how to make the application start automatically on server boot so that was the only other thing I wanted to do before putting this into production for myself.

Steve

@stre1026

Thank you!

Likewise! It seems that you have done your research. I’m happy to help along the way.

it looked like you were using Passenger

This is true if the RAILS_ENV environment variable is set to production. I believe that it uses Webrick in development mode.

Maybe … put a reverse proxy in front of it.

It’s important to remember that FarmBot is not just a web app, rather it is a web server and an MQTT/AMQP server bundled into one docker-compose setup. Farmbot also needs to expose AMQP and MQTT, which are not served from the same server and are not related to HTTP in any way. This means that if you go the reverse proxy route, you need a TCP reverse proxy rather than an HTTP reverse proxy. I remember investigating HAProxy as a possible solution, but that was so long ago I have forgotten most of my findings. You may find this document helpful. The docker-compose.yml file also lists TCP ports required.

I figured out how to make the application start automatically on server boot so that was the only other thing I wanted to do before putting this into production for myself.

I’m sure many users on the forum would be interested in learning how you did that if you are able to share.

@RickCarlino Good points!

Since I’m only interested in protecting the HTTP traffic for the web interface portion of the application for when I’m away from my home and I want to check on the bot, I think a reverse-proxy protecting just the HTTP would work fine since the Bot would continue to talk to the server on the local LAN and would not even know the proxy is there. Basically I would use something like Nginx which would watch for incoming traffic on port 443, then send it to port 80 and re-encypt it before it goes back out to the internet. Internal traffic meaning the bot and the server would continue to talk on port 80. This is the model that Microsoft uses for an on-premise Skype for Business environment. I will try it and report back.

As far as getting the server to start on startup, I’m using an Ubuntu Server and I added “restart: always” for each of the services in my docker-compose.yml. If you run “sudo docker-compose up -d” once, it will always start the services on startup. My docker-compose file looks like the following:

indent preformatted text by 4 spaces

version: “3”
services:
redis:
env_file: “.env”
image: redis:5
volumes:
- “./docker_volumes/redis/data:/data”
- “./docker_volumes/redis/conf:/usr/local/etc/redis”
expose: [“6379”]
restart: always

db:
env_file: “.env”
image: postgres:10
volumes: ["./docker_volumes/db:/var/lib/postgresql/data"]
# ports: [“5432:5432”]
restart: always

web:
env_file: “.env”
depends_on: [“db”, “redis”]
image: farmbot_web
volumes: [".:/farmbot", “./docker_volumes/bundle_cache:/bundle”]
stdin_open: true
tty: true
build:
context: “.”
dockerfile: docker_configs/api.Dockerfile
command: bash -c “rm -f tmp/pids/server.pid && bundle exec rails s -e development
-p ${API_PORT} -b 0.0.0.0”
ports: ["${API_PORT}:${API_PORT}"]
restart: always

mqtt:
env_file: “.env”
image: rickcarlino/experimental-docker-rmq:latest
ports: [“5672:5672”, “1883:1883”, “8883:8883”, “3002:15675”, “15672:15672”]
depends_on: [“web”]
environment: [“RABBITMQ_CONFIG_FILE=/farmbot/farmbot_rmq_config”]
volumes: ["./docker_volumes/rabbit:/farmbot"]
restart: always

parcel:
env_file: “.env”
image: farmbot_web
volumes: [".:/farmbot", “./docker_volumes/bundle_cache:/bundle”]
command: bundle exec rake api:serve_assets
ports: [“3808:3808”]
restart: always

typescript:
env_file: “.env”
image: farmbot_web
volumes: [".:/farmbot", “./docker_volumes/bundle_cache:/bundle”]
command: node_modules/typescript/bin/tsc -w --noEmit
restart: always

delayed_job:
env_file: “.env”
image: farmbot_web
volumes: [".:/farmbot", “./docker_volumes/bundle_cache:/bundle”]
depends_on: [“mqtt”]
command: bundle exec rake jobs:work
restart: always

log_digests:
env_file: “.env”
image: farmbot_web
volumes: [".:/farmbot", “./docker_volumes/bundle_cache:/bundle”]
depends_on: [“mqtt”]
command: bundle exec rake api:log_digest
restart: always

rabbit_jobs:
stdin_open: true
tty: true
env_file: “.env”
image: farmbot_web
volumes: [".:/farmbot", “./docker_volumes/bundle_cache:/bundle”]
depends_on: [“mqtt”]
command: bundle exec rails r lib/rabbit_workers.rb
restart: always
indent preformatted text by 4 spaces

1 Like

@stre1026

I think a reverse-proxy protecting just the HTTP would work

Your browser will most likely get cross-origin security errors when it tries to connect to RabbitMQ’s webssocket server. If you are connected to a site via https://, modern browsers will require all websocket connections during the same session to be over SSL also (wss:// rather than ws://). This means that all of the MQTT-based information (such as realtime logs, sync status, ability to move the device, etc…) will be unavailable. This might be acceptable for your use case, depending on which things you want to check. My guess is that the device will always appear as offline unless you find a way to put RabbitMQ behind the proxy also. I could be wrong though, please let me know how your experiment goes and we can take it from there.

1 Like

@RickCarlino

Ah, OK. I didn’t think of the web sockets. I will try it out and see what happens…

Thanks for your insights!

1 Like

@RickCarlino

So I tried the reverse-proxy and as you mentioned, I ran into issues with security so I’m trying to approach this a different way and secure each service. I’m trying to find the service that is actually the web server. I thought it was Passenger based on the gem file, but I am trying to see how it’s launched. I was messing with the Procfile in the root which has this line:

web: bundle exec passenger start -p $PORT -e $RAILS_ENV --max-pool-size 2

But I can remove this line completely and the server still runs even after reboot and rebuilding the Docker container. Can you shed some light on how this whole thing is bootstrapped? That might help me understand how this whole thing works.

Thanks!

@stre1026 That Procfile is only used by Heroku customers (such as FarmBot, Inc). Everything that a self hoster needs it outlined in the docker-compose.yml file. The web process is the most important one, along with the RabbitMQ container. No other containers need outbound port access to the public internet as far as I remember.

@RickCarlino Thanks!

So is this hosted by a NodeJS process then?

@stre1026 No problem! We only use node for live reload of Javascript / CSS assets that are used by the UI. That process does not need access to the outside world, it just builds JS / CSS assets that are served out of the public/ directory.

The web process in docker-compose.yml runs rails s -e development -p ${API_PORT} -b 0.0.0.0 which causes Rails to run an instance of WEBrick on port 3000 (or whatever port is set in the API_PORT ENV variable):

=> Booting WEBrick
=> Rails 6.0.2.2 application starting in development http://localhost:3000
=> Run `rails server --help` for more startup options
1 Like

@RickCarlino thanks for the explanation.

So one of the pieces I need to look into securing along with RabbitMQ is Webrick then? This appears to be possible based on a quick Google search. Back to the drawing board! I really appreciate your time and explanations you’ve given me today.

@stre1026 I wouldn’t base your search terms on WEBrick too much, you probably won’t get many search results that way. If your plan is to use a reverse proxy such as HAProxy (which might simplify the RabbitMQ part) it would be better to just use “Ruby on Rails” as your search term and not get too concerned about whether it is WEBRick or Passenger under the hood. Additionally, I am fairly certain an HTTP (non-TCP) reverse proxy will not work since FarmBot is not an HTTP application exclusively.

Please let me know if you have any other question.

Maybe a simple VPN back to the local network fits the bill ?

Haha now that would be too easy!

I have VPN and that is the plan if I can’t get this working.

Hello, as Rick said, I’m working on this subject too. This is a school project and we need to finish it soon :wink:

@stre1026 I dropped WebBrick for Passenger in the docker configuration.
I launch the server with:

command: bundle exec passenger start -e $RAILS_ENV --ssl --ssl-certificate certificates/fullchain1.pem --ssl-certificate-key certificates/privkey1.pem --ssl-port 3002
ports: ["80:3000", "${API_PORT}:3002"] 

FORCE_SSL is set in .env file (no specific value to put in).
API_PORT is set to 443 in .env file.
I copy-pasted my certificates in a new folder certificates/

Passenger starts and http server (only for redirection to https) on his port 3000, bind of the host 80 and starts and https server on port 3002, bind to port 443 of the host.
I will commit a script that generate the correct docker-compose.yml according to the environment variables.

It works well in http, but the websocket communication seems to break due to this changes : it doesn’t appear anymore in the requests lists and communication indicator is red in the interface, even if I forces the MQTT_WS variable to use RabbitMQ with ws://

I am still working on the SSL implementation for the RabbitMQ container, and an auto-renewal system.

Good deal @Pitou

I was looking to do what you did last night with Thin but I couldn’t figure out how to download the Gem into the container. I will change my code to what you have so I don’t reinvent the wheel!

Have you considered a reverse-proxy for the RabbitMQ side?

Thank you for this!

No I haven’t considered this. I’m trying to use ssl for the web_mqtt plugin as it’s described here https://www.rabbitmq.com/web-mqtt.html.

If you have a nice and easy setup with a reverse-proxy, I’m taking it :wink:

@RickCarlino, I got a question for you.
I tried to use the application with HTTPS and RabitMQ without SSL.

When I use regular http, I have a request to ws://my-domain.com:15675/ws with a 101 response and everything works fine.

When I am forcing https, I use the variable MQTT_WS=ws://my-domain.com:15675/ws. My problem is that I don’t have any call from my browser to get a websocket connection.
When I disable the variable, I have a browser call to wss://my-domain.com:3002/ws (but of course no answer)

Telnet is working so I think it comes from the ruby application:

$ telnet my-domain.com 15675
Trying XXX.XXX.XXX.XXX...
Connected to my-domain.com.
Escape character is '^]'.

Have you an idea why the browser is not trying to get a websocket connection? Is it possible to use the app with HTTPS and an MQTT service without SSL?

I haven’t looked into it yet, but if I find a solution, you can certainly take it! :wink:

@Pitou - I changed the code to launch passenger as you suggested but now I’m running into issues where I need to whitelist domains, etc. in the Ruby application. I did that and now I’m running into exceptions with the Action Controller. Before I go down that rabbit hole, did you run into any of these problems?