Setting Up SSL for Running Foreman on HTTPS

Introduction

This guide will walk you through the process of securing your Foreman instance in a development environment by setting up SSL for HTTPS access. By encrypting your communications, you can enhance the security of your Foreman development setup.

Prerequisites

  1. A working Foreman installation.
  2. Root access to your Foreman server.

Step 1: Install step and step-ca from Smallstep:

Follow the instructions from the official documentation to install step and step-ca.

Step 2: Generate CA and Certificate:

Initialize your certificate authority:

step ca init

Run your certificate authority:

step-ca $(step path)/config/ca.json

Install your root CA certificate into your system’s default trust store:

step certificate install $(step path)/certs/root_ca.crt

Get a certificate and private key from the CA for the foreman server on localhost:

step ca certificate localhost srv.crt srv.key

Optional

Please note that default maximum certificate duration is 24 hours. To adjust the global default, minimum, and maximum certificate durations for your CA, add a claims section to the $(step path)/config/ca.json configuration file, under "authority", with the following keys:

"claims": {
"minTLSCertDuration": "5m",
"maxTLSCertDuration": "24h",
"defaultTLSCertDuration": "24h"

}

This configuration allows you to modify SSL certificate lifetimes as needed. You can achieve this by using the --not-after flag in the step ca certificate command. For example, specifying --not-after=240h will extend the certificate validity to the next 10 days from the current date.

Step 3: Configure HTTPS for Foreman:

Ensure you have the mod_ssl module installed:

dnf install mod_ssl

Disable the default HTTP listener in the Apache configuration:

vim /etc/httpd/conf.d/ssl.conf

Comment out the line:
# Listen 443 https

Ensure that the SSLEngine is set to on:
SSLEngine on

Edit the httpd conf file:

vim /etc/httpd/conf/httpd.conf

Add the following line to enable listening on port 443 for HTTPS:
Listen 443

Step 4: Update Hosts File:

Update your system’s /etc/hosts file with the following entries:

127.0.0.1 localhost your.foreman.domain boot.foreman.domain loadbalancer.foreman.domain
::1 localhost your.foreman.domain boot.foreman.domain loadbalancer.foreman.domain
0.0.0.0 your.foreman.domain boot.foreman.domain

Step 5: Create a Configuration File:

Create a new configuration file for your Foreman domain:

vim /etc/httpd/conf.d/your.foreman.domain.conf

Add the following configuration (adjust paths and settings as needed):

<VirtualHost *:80>
  ServerName your.foreman.domain

  ProxyPass / http://127.0.0.1:3000/
  ProxyPassReverse / http://127.0.0.1:3000/

  RequestHeader set X-Forwarded-Proto http
</VirtualHost>

<VirtualHost *:443>
  ServerName your.foreman.domain

  SSLEngine on
  SSLCertificateFile /path/to/your/srv.crt
  SSLCertificateKeyFile /path/to/your/srv.key

  SSLOptions +ExportCertData

  RequestHeader set X-Forwarded-Proto https

  RequestHeader set SSL_CLIENT_S_DN "%{SSL_CLIENT_S_DN}s"
  RequestHeader set SSL_CLIENT_CERT "%{SSL_CLIENT_CERT}s"
  RequestHeader set SSL_CLIENT_VERIFY "%{SSL_CLIENT_VERIFY}s"

  <Location "/">
    ProxyPass http://localhost:3000/
    ProxyPassReverse http://localhost:3000/
  </Location>
</VirtualHost>

Restart the Apache service to apply the changes:

systemctl restart httpd

Step 6: Foreman Configuration:

Edit your Foreman configuration file foreman/config/settings.yaml and update the following settings:

:webpack_dev_server_https: true
:domain: 'foreman.domain'
:fqdn: 'your.foreman.domain'

Navigate to the settings page on the UI and update the following settings:

Trusted hosts: [your.foreman.domain,localhost]
SSL certificate: /path/to/your/srv.crt
SSL CA file: /path/to/your/certs/root_ca.crt
SSL private key: /path/to/your/srv.key
SSL client DN env: HTTP_SSL_CLIENT_S_DN
SSL client verify env: HTTP_SSL_CLIENT_VERIFY
SSL client cert env: HTTP_SSL_CLIENT_CERT

Step 7: Run Foreman:

Run rails process:

bundle exec rails s -b 0.0.0.0 -p 3000

Run Webpack:

env NODE_ENV=development NOTIFICATIONS_POLLING=99999999 ./node_modules/.bin/webpack-dev-server-without-h2 --config ./config/webpack.config.js --allowed-hosts your.foreman.domain --https --cert /path/to/your/srv.crt --key /path/to/your/srv.key

Troubleshooting:

To find and diagnose errors, you can use the following commands:

Check Apache error logs

tail -f /var/log/httpd/error_log

Check Apache access logs

tail -f /var/log/httpd/access_log

Check Apache configuration syntax

apachectl configtest

Check systemd journal for Apache-related messages

journalctl -xe | grep httpd

SELinux Configuration:

If you encounter SELinux-related issues, follow these commands to address them:

Check SELinux context of certificate files

ls -Z /path/to/your/srv.crt /path/to/your/srv.key

Set the appropriate SELinux context

chcon -t httpd_sys_content_t /path/to/your/srv.crt /path/to/your/srv.key

If access denied errors persist, ensure the SELinux permissions are updated accordingly.

Browser Cache Issues:

Ensure to clear browser cache and test the SSL setup on multiple browsers, as cached certificates in browsers can sometimes cause issues.

Conclusion:

By following these steps, you have successfully configured SSL for running Foreman on HTTPS. Your Foreman instance is now more secure, and communication with it is encrypted.

4 Likes

Just as a note for others stumbling over this tutorial and as it may not be clear to all: This is meant for a development setup.

Thank you for the comment, Dirk! I’ll mention it in the intro to make sure it is clear.

I’ve never tried this before, but Puma can use HTTPS: File: README — Puma master

We already have an env var to change bind:

In theory it’s making sure the localhost gem is loaded and use BIND=ssl://HOST:PORT.

I haven’t checked if the SSL client certs are properly working though, but I doubt you can also issue client certificates with the localhost gem. You can also combine the approaches and tell Puma to use existing certificates.

I’ve now tried it out. There are some considerations to take into account. I’ll list those at the bottom.

Using localhost gem without client certificates

This is the most minimal developer setup, but it doesn’t allow you to use client certificates from Smart Proxies.

First make sure the localhost gem is loaded:

cat > bundler.d/localhost.local.rb <<EOF
group :development do
  # For HTTPS certificates with bundler
  gem 'localhost'
end
EOF

Now start the process

BIND=ssl://localhost:5000 bundle exec foreman start

Verify it works:

curl --cacert ~/.localhost/localhost.crt https://localhost:5000

As you can see in the curl example, the certificates were placed in ~/.localhost. You can use these same certificates in the Smart Proxy.

Limitations

Serving both HTTP & HTTPS

Right now there is no way to bind to both HTTP and HTTPS in this setup. Puma can do this, but we don’t have config options exposed right now.

You can drop something like this in config/puma/development.rb and force it though:

port ENV.fetch('FOREMAN_PORT', '3000'), ENV['FOREMAN_BIND']

ssl_bind ENV.fetch('FOREMAN_HOST', 'localhost'), ENV.fetch('FOREMAN_SSL_PORT', '5000'), {
  cert: ENV.fetch('FOREMAN_SSL_CERT', nil),
  key: ENV.fetch('FOREMAN_SSL_KEY', nil),
  verify_mode: 'peer',
}

No client certificates

Puma will present the client certificate in the environment as puma.peercert so you can set ssl_client_cert_env to that, but Puma doesn’t send a simple status in the environment.

I think this simple patch should work:

diff --git a/app/services/foreman/client_certificate.rb b/app/services/foreman/client_certificate.rb
index d463d1cef..ac650421d 100644
--- a/app/services/foreman/client_certificate.rb
+++ b/app/services/foreman/client_certificate.rb
@@ -29,6 +29,10 @@ module Foreman
     end
 
     def verify
+      # Puma doesn't provide the verify status, but assume it passed if the
+      # cert is there
+      return 'SUCCESS' if certificate_env_key == 'puma.peercert' && raw_cert_available?
+
       request.env[verify_key]
     end
 

I then also set the following in config/settings.yaml:

:ssl_client_cert_env: puma.peercert
:ssl_certificate: /home/ekohl/.localhost/localhost.crt
:ssl_ca_file: /home/ekohl/.localhost/localhost.crt
:ssl_priv_key: /home/ekohl/.localhost/localhost.key

With this I could at least register a Smart Proxy on localhost using the same certificates, but I haven’t verified if the auth actually works.

No NodeJS

I didn’t configure NodeJS yet to serve HTTPS.