Foreman SSL explained
By default the foreman-installer installs using the [Puppet CA]. While options are provided to replace these, it's not obvious. In this blog we'll see how Foreman uses certificates and how to to replace them.
While the Puppet CA is very convenient for a server admin using Puppet, it's inconvenient for clients connecting to the UI because often it's not in the certificate store. For those not using Puppet, the CA service is quite heavy. Others may need to use different certificates for compliance reasons. Then there are those who do use Puppet CA but want to understand the application. Whatever the reason, we'll go step by step in building a deployment.
Generating certificates
Foreman can use certificates signed by any Certificate Authority, even self signed. For this example ownca is used, a shell script around wrapping openssl. The installation is done using wget
and extracted to /etc/ownca
.
yum -y install wget openssl
mkdir /etc/ownca
cd /etc/ownca
wget https://raw.githubusercontent.com/ekohl/ownca/master/ownca
chmod +x ownca
This set of scripts first needs to generate a Certificate Authority (CA).
# ./ownca ca
Generating a RSA private key
.........+++++
....................+++++
writing new private key to 'private/cakey.crt'
-----
A CA is allowed to sign other certificates. In this case it's a bunch of files, but sometimes it describes a company that issues certificates. Either way, there is always a public certificate; in this case /etc/ownca/cacert.crt
.
The Foreman instance running on $HOSTNAME
(foreman.example.com
here) needs a certificate:
# ./ownca cert $HOSTNAME
Generating a RSA private key
..............................................................................................................................+++++
.....+++++
writing new private key to './foreman.example.com/foreman.example.com.key'
-----
Using configuration from ./openssl.cnf
Check that the request matches the signature
Signature ok
The Subject's Distinguished Name is as follows
commonName :PRINTABLE:'foreman.example.com'
Certificate is to be certified until May 22 11:31:13 2021 GMT (365 days)
Write out database with 1 new entries
Data Base Updated
These steps created the following files that are needed later:
/etc/ownca/cacert.crt
is our Certificate Authority/etc/ownca/foreman.example.com/foreman.example.com.crt
is our Certificate/etc/ownca/foreman.example.com/foreman.example.com.key
is our Private Key
Inspecting certificates
Certificates are not just a blob of bytes, there's a lot of information in them. Extracting this information is not always obvious so this chapter explains how to read the information, starting with the CA certificate.
openssl
has various subcommands which can be listed using openssl help
. X.509 is the standard that defines the format of public key certificates. This is also the openssl subcomand to inspect them. The next command reads cacert.crt
and shows the text representation.
# openssl x509 -in cacert.crt -noout -text
Certificate:
Data:
Version: 3 (0x2)
Serial Number:
0e:30:4a:47:ba:c5:dc:3e:77:ee:43:9e:eb:fd:c0:1a:49:6a:44:83
Signature Algorithm: sha256WithRSAEncryption
Issuer: CN = OwnCA
Validity
Not Before: May 22 11:30:48 2020 GMT
Not After : May 20 11:30:48 2030 GMT
Subject: CN = OwnCA
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
RSA Public-Key: (2048 bit)
Modulus:
00:bc:3b:48:39:8c:52:3b:c2:a5:db:1f:e5:e9:26:
e0:b1:80:72:01:e4:af:41:0c:27:50:a7:21:41:5f:
13:ba:f6:ac:63:3d:aa:c1:5b:c0:bb:e2:a5:df:27:
b8:e2:1a:80:e8:12:a8:b1:12:b9:59:7d:07:0f:22:
14:c9:ea:fa:6e:ce:60:0b:cf:a6:2c:86:97:82:09:
1c:4c:1f:f8:a0:27:18:a5:26:20:59:a1:ed:69:45:
76:3d:5d:8b:b4:8b:46:78:62:a7:08:c5:c1:49:0d:
f4:c2:b6:e3:cc:e3:41:f0:2e:79:e4:04:b2:ce:8e:
7f:2d:6f:ae:84:4a:13:ad:2e:d4:47:59:3b:2a:58:
bb:21:b7:f2:dc:74:b9:d5:05:e4:7d:83:87:fc:c2:
5a:b1:bb:66:cf:f4:14:ff:a3:c0:14:5e:fc:98:11:
37:ea:75:fe:15:8b:8f:46:7f:10:28:8a:8c:e3:05:
1b:f1:68:ed:82:8d:e1:10:26:2c:dc:a7:5c:67:7b:
da:2a:70:3e:ee:42:55:ad:a8:93:35:aa:f1:7b:7f:
10:49:61:df:35:83:40:e9:8a:d3:1e:6d:7d:3c:63:
15:8b:ed:5d:a9:49:ce:8d:2f:a9:2c:04:ee:ca:c0:
3d:79:05:3a:a3:2e:82:a3:15:6a:e6:b8:eb:05:cc:
a3:ef
Exponent: 65537 (0x10001)
X509v3 extensions:
X509v3 Basic Constraints:
CA:TRUE
X509v3 Subject Key Identifier:
10:EA:F3:ED:37:90:58:CE:50:0A:A4:96:96:25:B2:80:70:EC:AD:34
X509v3 Authority Key Identifier:
keyid:10:EA:F3:ED:37:90:58:CE:50:0A:A4:96:96:25:B2:80:70:EC:AD:34
DirName:/CN=OwnCA
serial:0E:30:4A:47:BA:C5:DC:3E:77:EE:43:9E:EB:FD:C0:1A:49:6A:44:83
Signature Algorithm: sha256WithRSAEncryption
20:d8:94:4c:6e:c7:91:7f:80:7d:d4:f8:be:be:a4:6f:9f:3e:
39:2b:8d:ba:b8:2a:52:cc:fd:80:b3:a4:72:2e:c5:cc:5f:53:
43:03:29:0e:4d:b4:db:d0:12:fb:6c:0e:db:9e:0d:03:60:6c:
8f:34:bc:88:ba:ec:a4:50:a5:d4:fd:bd:70:75:ad:72:b1:af:
ba:f1:c6:cd:ea:dd:0c:a0:fa:d5:b0:9a:ad:67:61:1b:1a:f0:
8b:1b:fc:32:c2:29:e5:6a:e1:07:7b:0c:e2:52:2e:8b:91:1b:
8b:28:d5:dd:64:a5:9c:96:81:4c:0d:da:cf:a2:3f:54:a7:76:
56:cf:1c:80:0f:24:b5:61:fd:66:41:86:82:4f:5d:ba:94:0a:
2f:87:2f:be:c2:ea:a1:85:2b:00:da:a4:b3:5a:3d:64:74:6f:
33:a3:d5:08:19:64:be:07:bf:52:27:f3:43:a9:29:81:ad:de:
16:ea:aa:cc:3a:93:f0:65:09:dc:0d:67:9a:e9:7d:70:b7:ff:
6e:5e:c3:ed:43:e8:04:70:f3:a7:f3:33:ce:92:83:a4:59:c0:
9b:0c:3d:87:c3:d7:2e:a8:42:01:82:fa:e2:0f:c7:57:b5:ce:
ae:18:27:c9:9c:85:dc:cf:05:91:3f:39:84:c9:0d:15:e8:4d:
2d:09:6e:83
This output is cryptic, but there's a few things to look for. First of all, there's the following block
Issuer: CN = OwnCA
Validity
Not Before: May 22 11:30:48 2020 GMT
Not After : May 20 11:30:48 2030 GMT
Subject: CN = OwnCA
This is the most important information. It tells us who issued the certificate, when it's valid and the subject. Certificates are identified by their subject. The exact number of fields there can vary, depending on the CA. Note that Issuer
and Subject
match, indicating that the certificate signed itself (self-signed certificate). A CA certificate that signed itself is commonly known as a root certificate because computer scientists see this as a tree structure, where things start at the root.
In the extensions we also see CA:TRUE
which means it's allowed to sign certificates:
X509v3 extensions:
X509v3 Basic Constraints:
CA:TRUE
When a certificate has CA:TRUE
but is signed by another certificate, it's typically referred to as an Intermediate CA.
The host certificate can also be inspected:
# openssl x509 -in $HOSTNAME/$HOSTNAME.crt -noout -text
Certificate:
Data:
Version: 3 (0x2)
Serial Number:
06:7c:1e:d8:1c:a1:8a:b0:8b:5a:b8:63:c5:d6:57:60:97:af:57:87
Signature Algorithm: sha256WithRSAEncryption
Issuer: CN = OwnCA
Validity
Not Before: May 22 11:31:13 2020 GMT
Not After : May 22 11:31:13 2021 GMT
Subject: CN = foreman.example.com
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
RSA Public-Key: (2048 bit)
Modulus:
00:cd:42:f5:36:9a:6b:05:40:2a:4c:2f:e6:30:7a:
9d:c4:fd:3d:81:b1:fe:be:57:69:29:0f:d2:a4:7f:
af:a0:91:ee:b6:34:50:ca:35:dd:d4:27:d6:54:90:
e6:9e:51:50:ab:5d:44:f2:e8:06:f9:ee:09:5f:05:
fe:d3:c3:08:71:6f:80:01:d1:42:3d:c8:3f:36:80:
89:25:0e:c3:6f:b1:e7:35:86:d1:51:10:8c:e7:58:
de:94:4e:ee:b0:74:96:6c:43:8d:4d:29:17:99:b7:
f2:f2:17:8c:3e:35:ab:52:25:ce:bd:3c:66:29:ec:
01:e8:aa:97:07:4e:32:f7:e8:0c:6b:2d:26:f1:05:
64:a8:7a:b3:f8:9e:b6:3d:a0:d6:97:f2:84:82:ba:
88:24:3c:95:b6:e6:ef:c0:a8:3d:26:63:41:9a:a7:
cc:e1:d7:a7:cc:27:04:4a:9f:54:e3:57:2b:2e:f0:
24:03:12:63:e5:18:02:31:bf:e6:46:48:fc:5d:dd:
83:46:5a:49:86:91:83:75:39:3e:31:72:2d:26:7e:
c9:9f:b0:dc:9e:9b:20:d1:8d:ea:88:31:c2:cf:78:
a1:35:3e:b3:f4:19:fd:df:f0:11:5b:22:4f:84:d3:
f7:8e:fc:0b:9e:8a:97:40:6f:c8:8b:b0:59:44:48:
fb:83
Exponent: 65537 (0x10001)
X509v3 extensions:
X509v3 Basic Constraints:
CA:FALSE
X509v3 Subject Key Identifier:
83:0E:92:A6:95:25:1A:52:84:92:97:00:C7:CF:93:D8:40:7C:E5:71
X509v3 Key Usage:
Digital Signature, Non Repudiation, Key Encipherment
X509v3 Extended Key Usage:
TLS Web Server Authentication, TLS Web Client Authentication, Code Signing, E-mail Protection
X509v3 Subject Alternative Name:
DNS:foreman.example.com
Signature Algorithm: sha256WithRSAEncryption
a7:a1:c2:36:a1:48:ae:07:0f:d6:b7:bb:19:8a:55:c9:41:42:
c7:43:a0:2e:b9:0d:32:fa:9e:a3:6c:fd:7d:5b:46:2f:20:a0:
a0:79:9e:44:be:24:37:2e:f7:80:1a:cc:d7:ad:05:94:79:9a:
bc:4c:4d:fa:a4:73:4c:8d:48:94:4e:eb:0f:fb:7d:26:8a:ab:
0b:01:31:11:1f:53:8e:64:63:df:14:ec:aa:a7:32:79:ce:26:
39:b3:03:a3:be:2f:00:63:61:c1:11:00:90:7f:f2:d2:b3:27:
96:54:ab:3c:93:60:13:e1:a3:6d:49:06:9b:3f:a5:81:95:6b:
38:0a:f6:e5:04:59:4b:75:15:86:e3:91:36:a0:d2:a2:9a:f4:
29:a6:5a:fb:42:19:a9:fa:f6:7e:6a:91:8b:c4:64:b9:e9:d1:
20:60:42:99:0f:c3:11:d2:dc:42:c0:63:da:20:c6:e7:75:dd:
50:2f:17:2b:9f:5e:58:00:8f:ea:f9:c2:f2:23:1e:68:69:31:
26:ed:ad:9e:b6:91:f5:e9:6e:41:81:76:16:3b:7d:08:a3:ab:
c9:90:2f:d1:45:7f:10:3b:8f:41:95:0b:d8:2c:bc:3c:02:61:
97:31:b9:bd:ad:85:3c:a5:69:98:a4:9f:10:e7:7e:c4:9b:5d:
0f:45:18:c3
This is largely the same as the CA certificate, but with some differences. For starters, the certificate is not allowed to sign other certificates (CA:FALSE
).
More importantly the issuer and subject differ. The issuer is our CA and the subject is our hostname. Foreman still uses Common Name (CN) a lot, in particular around authentication. You might spot a potential problem with this. Common Name can only be a single name but various services have multiple. That's why modern tooling has started to use the Subject Alternative Name extension:
X509v3 extensions:
X509v3 Subject Alternative Name:
DNS:foreman.example.com
This is the same name as our Common Name. Ensure that the Common Name is there or things will break. Additional names can be added, but is beyond the scope of this explanation.
Installing Foreman
This guide is using CentOS 8 and the Foreman 3.1 but this is applicable to many versions and all supported operating systems.
yum -y install https://yum.theforeman.org/releases/3.1/el8/x86_64/foreman-release.rpm https://yum.puppet.com/puppet-release-el-8.noarch.rpm
yum -y install foreman-installer
This doesn't install EPEL, but you may need it for some plugins or older releases.
First the permissions will need to be fixed. For this, a dedicated group is created:
OWNCA=/etc/ownca
groupadd foreman-certs
chmod 640 $OWNCA/$HOSTNAME/$HOSTNAME.key
chgrp foreman-certs $OWNCA/$HOSTNAME/$HOSTNAME.key
Now that we've prepared the certificates, the installer will do the hard work. Just running foreman-installer
without arguments will install various components, but that's not what we want right now. It's customized to only install Foreman itself without a Foreman Proxy.
OWNCA=/etc/ownca
foreman-installer \
--no-enable-foreman-cli \
--no-enable-foreman-proxy \
--no-enable-puppet \
--foreman-user-groups foreman-certs \
--foreman-server-ssl-ca $OWNCA/cacert.crt \
--foreman-server-ssl-chain $OWNCA/cacert.crt \
--foreman-server-ssl-cert $OWNCA/$HOSTNAME/$HOSTNAME.crt \
--foreman-server-ssl-key $OWNCA/$HOSTNAME/$HOSTNAME.key \
--foreman-server-ssl-crl "" \
--foreman-client-ssl-ca $OWNCA/cacert.crt \
--foreman-client-ssl-cert $OWNCA/$HOSTNAME/$HOSTNAME.crt \
--foreman-client-ssl-key $OWNCA/$HOSTNAME/$HOSTNAME.key
Now that looks like a lot of duplication so we'll go over all options. We can recognize two groups: server and client.
The server is what will be used by Apache and the is most visible. Since Apache reads these files as root, file permissions are generally not that important. SELinux policies can limit it so be aware of that. The Certificate Revocation List (CRL) is unset. CA vs chain is explained later with examples.
However, Foreman also runs a bundled Websockify that will spawn for VNC consoles to VMs. It's serving its own certificates and these options control this. Websockify runs as the Foreman user so it needs to read these files, which is why we created the group. The server and websocket should use the same certificates.
There are also client certificates that are used to connect to the Foreman Proxy (also known as Smart Proxy). In this case, the certificate and key are used as client certificates rather than server. This means they're used for identification. Later we'll see how that's verified.
Our example uses the same certificates for both server and client, but this isn't required. Foreman Proxies can use a different CA. This can be useful when using a public commercial Certificate Authority for the Foreman UI but a custom CA (like Puppet CA) for internal communication. In that case --foreman-server-ssl-ca
must point to the public facing CA and --foreman-server-ssl-chain
the internal CA. The --foreman-client-ssl-*
options must belong to the internal CA.
After the installer has completed, we can inspect what's actually being served.
# openssl s_client -connect $HOSTNAME:443 -CAfile /etc/ownca/cacert.crt
CONNECTED(00000003)
depth=1 CN = OwnCA
verify return:1
depth=0 CN = foreman.example.com
verify return:1
---
Certificate chain
0 s:CN = foreman.example.com
i:CN = OwnCA
1 s:CN = OwnCA
i:CN = OwnCA
---
Server certificate
-----BEGIN CERTIFICATE-----
<CERTIFICATE>
-----END CERTIFICATE-----
subject=CN = foreman.example.com
issuer=CN = OwnCA
---
Acceptable client certificate CA names
CN = OwnCA
<MORE CONTENT>
This is slightly truncated for readability.
The first thing to notice, is the Certificate chain
. At the highest level we see our server certificate with its subject (s:
) and issuer (i:
). Our server also serves the CA chain below that. In this case it's only our root CA as specified in --foreman-server-ssl-chain
. If your chain includes intermediate CAs, you will see those as well.
Then the Server certificate
is shown, which matches --foreman-server-ssl-cert
. Again, the subject and issuer are shown.
Lastly there's Acceptable client certificate CA names
that matches --foreman-server-ssl-ca
. Foreman uses client certificates in various places to authenticate. Any such certificate must be signed by this CA.
Visualizing the deployment
This can be summarized in a diagram.
Services listening externally have a bold border. Red means it's unencrypted and blue is encrypted using our new CA. Concretely:
- Apache listens on
*:80
(unencrypted) - Apache listens on
*:443
(encrypted) - Websockify allocates a free port in the 5910 - 5930 (encrypted) range when a VNC console is started
Starting Foreman 2.1, Apache works as a reverse proxy to Foreman. The Foreman service runs unencrypted on 127.0.0.1:3000
and only Apache is supposed to connect to it. This is implemented using systemd socket activation. The systemd service foreman.socket
listens on a TCP socket and passes it to foreman.service
as a file descriptor. No HTTP requests are dropped this way, even if foreman.service
is restarted. In the future Apache to Foreman connections may be changed to use a unix socket for better security.
The VNC console part also can use some improvements. The RFC Consolidating The Console was started, but so far nobody has worked on implementation.
Adding a Foreman Proxy
By default, foreman-installer installs a Foreman Proxy, but in this blog it's split off to a separate host. This is done to better illustrate how things talk to eachother. Foreman can have many Foreman Proxies so this is a common scenario even when a proxy also exists on the Foreman host.
First a certificate is needed for our host foreman-proxy.example.com
. We run this on foreman.example.com
in the ownca directory.
[root@foreman ownca]# ./ownca cert foreman-proxy.example.com
Generating a RSA private key
................................+++++
...........................+++++
writing new private key to './foreman-proxy.example.com/foreman-proxy.example.com.key'
-----
Using configuration from ./openssl.cnf
Check that the request matches the signature
Signature ok
The Subject's Distinguished Name is as follows
commonName :PRINTABLE:'foreman-proxy.example.com'
Certificate is to be certified until May 26 13:07:09 2021 GMT (365 days)
Write out database with 1 new entries
Data Base Updated
Of course this can also be inspected but for brevity this is left as a user excercise. When doing so, check that the issuer is still the same CA while the subject now has the new hostname.
The installer can register a new Foreman Proxy using the REST API. Authentication is done using OAuth. By default the installer generates a random key and secret when installing Foreman but they can also be explicitly specified. To retrieve the values, you can go to the Foreman settings page. In this case we'll use the command line on foreman.example.com
:
foreman.example.com # foreman-rake -- config -k oauth_consumer_key
supersecret
foreman.example.com # foreman-rake -- config -k oauth_consumer_secret
alsosecret
Copy the files cacert.crt
, foreman-proxy.example.com.crt
and foreman-proxy.example.com.key
to the proxy host. Make sure the user foreman-proxy
can read these. It's also important that the key is not world readable.
Also follow the same installation steps to get foreman-installer
and run the installer on foreman-proxy.example.com
:
foreman-proxy.example.com # foreman-installer \
--no-enable-foreman \
--no-enable-foreman-cli \
--no-enable-puppet \
--foreman-proxy-manage-puppet-group false \
--foreman-proxy-puppet false \
--foreman-proxy-puppetca false \
--foreman-proxy-tftp false \
--foreman-proxy-foreman-base-url https://foreman.example.com \
--foreman-proxy-trusted-hosts $HOSTNAME \
--foreman-proxy-trusted-hosts foreman.example.com \
--foreman-proxy-oauth-consumer-key supersecret \
--foreman-proxy-oauth-consumer-secret alsosecret \
--foreman-proxy-ssl-ca /path/to/cacert.crt \
--foreman-proxy-ssl-cert /path/to/$HOSTNAME.crt \
--foreman-proxy-ssl-key /path/to/$HOSTNAME.key
Here we specify the server certs, but don't explicitly configure the client certificates. That's because the same certificates as the server are used automatically. When using a different CA for Foreman, --foreman-proxy-foreman-ssl-ca
must be specified as well.
There are some options to disable Puppet integration and even tftp to get a minimal installation. This only leaves the logs feature, chosen because it requires no additional dependencies and Foreman refuses to register proxies without any features.
It's also possible to specify an additional group for the foreman-proxy
user with --foreman-proxy-groups mygroup
. One example is having all files in /path/to/mycerts
and setting permissions using chown root:mygroup /path/to/mycerts && chmod 0750 /path/to/mycerts
. It is up to you to ensure mygroup
exists prior to running the installer.