RPM Packaging

This chapter deals with security-related concerns around RPM packaging. It has to be read in conjunction with distribution-specific packaging guidelines.

Generating X.509 Self-signed Certificates during Installation

Some applications need X.509 certificates for authentication purposes. For example, a single private/public key pair could be used to define cluster membership, enabling authentication and encryption of all intra-cluster communication. (Lack of certification from a CA matters less in such a context.) For such use, generating the key pair at package installation time when preparing system images for use in the cluster is reasonable. For other use cases, it is necessary to generate the key pair before the service is started for the first time, see Generating X.509 Self-signed Certificates before Service Start, and Packaging:Initial Service Setup.

The way the key is generated may not be suitable for key material of critical value. (openssl genrsa uses, but does not require, entropy from a physical source of randomness, among other things.) Such keys should be stored in a hardware security module if possible, and generated from random bits reserved for this purpose derived from a non-deterministic physical source.

In the spec file, we define two RPM variables which contain the names of the files used to store the private and public key, and the user name for the service:

# Name of the user owning the file with the private key
%define tlsuser %{name}
# Name of the directory which contains the key and certificate files
%define tlsdir %{_sysconfdir}/%{name}
%define tlskey %{tlsdir}/%{name}.key
%define tlscert %{tlsdir}/%{name}.crt

These variables likely need adjustment based on the needs of the package.

Typically, the file with the private key needs to be owned by the system user which needs to read it, %{tlsuser} (not root). In order to avoid races, if the directory %{tlsdir} is owned by the services user, you should use the code in Creating a key pair in a user-owned directory. The invocation of su with the -s /bin/bash argument is necessary in case the login shell for the user has been disabled.

Example 1. Creating a key pair in a user-owned directory
%post
if [ $1 -eq 1 ] ; then
  if ! test -e %{tlskey} ; then
    su -s /bin/bash \
      -c "umask 077 && openssl genrsa -out %{tlskey} 2048 2>/dev/null" \
      %{tlsuser}
  fi
  if ! test -e %{tlscert} ; then
    cn="Automatically generated certificate for the %{tlsuser} service"
    req_args="-key %{tlskey} -out %{tlscert} -days 7305 -subj \"/CN=$cn/\""
    su -s /bin/bash \
      -c "openssl req -new -x509 -extensions usr_cert $req_args" \
      %{tlsuser}
  fi
fi

%files
%dir %attr(0755,%{tlsuser},%{tlsuser]) %{tlsdir}
%ghost %attr(0600,%{tlsuser},%{tlsuser}) %config(noreplace) %{tlskey}
%ghost %attr(0644,%{tlsuser},%{tlsuser}) %config(noreplace) %{tlscert}

The files containing the key material are marked as ghost configuration files. This ensures that they are tracked in the RPM database as associated with the package, but RPM will not create them when the package is installed and not verify their contents (the %ghost), or delete the files when the package is uninstalled (the %config(noreplace) part).

If the directory %{tlsdir} is owned by root, use the code in Creating a key pair in a root-owned directory.

Example 2. Creating a key pair in a root-owned directory
%post
if [ $1 -eq 1 ] ; then
  if ! test -e %{tlskey} ; then
    (umask 077 && openssl genrsa -out %{tlskey} 2048 2>/dev/null)
    chown %{tlsuser} %{tlskey}
  fi
  if ! test -e %{tlscert} ; then
    cn="Automatically generated certificate for the %{tlsuser} service"
    openssl req -new -x509 -extensions usr_cert \
      -key %{tlskey} -out %{tlscert} -days 7305 -subj "/CN=$cn/"
  fi
fi

%files
%dir %attr(0755,root,root]) %{tlsdir}
%ghost %attr(0600,%{tlsuser},%{tlsuser}) %config(noreplace) %{tlskey}
%ghost %attr(0644,root,root) %config(noreplace) %{tlscert}

In order for this to work, the package which generates the keys must require the openssl package. If the user which owns the key file is generated by a different package, the package generating the certificate must specify a Requires(pre): on the package which creates the user. This ensures that the user account will exist when it is needed for the su or chmod invocation.

Generating X.509 Self-signed Certificates before Service Start

An alternative way to automatically provide an X.509 key pair is to create it just before the service is started for the first time. This ensures that installation images which are created from installed RPM packages receive different key material. Creating the key pair at package installation time (see Generating X.509 Self-signed Certificates during Installation) would put the key into the image, which may or may not make sense.

The caveats about the way the key is generated in Generating X.509 Self-signed Certificates during Installation apply to this procedure as well.

Generating key material before service start may happen very early during boot, when the kernel randomness pool has not yet been initialized. Currently, the only way to check for the initialization is to look for the kernel message random: nonblocking pool is initialized, or ensure that the application used for generating the keys is utilizing the getrandom() system call.

In theory, it is also possible to use an application which reads from /dev/random while generating the key material (instead of /dev/urandom), but this can block not just during the boot process, but also much later at run time, and generally results in a poor user experience.

The requirements for generating such keys is documented at Packaging:Initial Service Setup.