Documentation on using RedHat Package Manager (RPM) to build custom software packages. See also Maximum RPM and the Fedora Project Developer's Guide. Packages can be made available to clients via local Yellowdog Updater, Modified (YUM) servers.
Systems used to build and sign packages must not host packages. Instead, copy signed packages from build hosts to distribution servers, and require that clients verify signatures before installing. This way, an attacker who compromises a distribution host will not be able to run arbitrary software on client systems.
- Avoiding and solving RPM package conflicts.
- RPM signing problems.
- Transfer RPM packages to and from a YUM server.
- Using Epoch to solve package version number problems.
Also consider OpenPKG.
Build Setup
Each account used to build packages will need a ~/.rpmmacros configuration file and a build directory structure. The %_topdir statement in ~/.rpmmacros file must match the directory used. The example here uses ~/src/redhat as the parent of the build directory; use the following mkdir commands to create the required directories.
$ mkdir -p ~/src/redhat
$ cd ~/src/redhat
$ mkdir BUILD RPMS SOURCES SPECS SRPMS tmp
Use this Makefile to help setup the subdirectories, obtain sources from a YUM server, sign packages, and upload packages to the YUM server.
Useful settings for the ~/.rpmmacros configuration file include:
- Disable debuginfo packages.
- Build packages into RPMS instead of RPMS/%{ARCH} subdirectories.
# do not generate debugging packages by default - newer versions
# of rpmbuild may instead need:
#%define debug_package %{nil}
%debug_package %{nil}
# do not use %{ARCH} subdirs for built packages
%_build_name_fmt %%{NAME}-%%{VERSION}-%%{RELEASE}.%%{ARCH}.rpm
Public Keys
Systems used to build packages will need Pretty Good Privacy (PGP) keys imported to verify signed software. Unfortunately, not all developers sign their software, which makes it trivial for an attacker to include malicious code. If software or packages are signed with PGP, import the public keys.
$ gpg --import PGP-KEY-FILE
$ sudo rpm --import PGP-KEY-FILE
To see what keys have been registered with RPM, search for gpg-pubkey-* packages.
$ rpm -qa | grep ^gpg-pubkey
…
If software has a detatched *.sig file, verify the distribution with gpg. RPM files can be verified using rpm -K.
$ ls nmap-3.75.tar.bz2*
nmap-3.75.tar.bz2 nmap-3.75.tar.bz2.sig
$ gpg --verify nmap-3.75.tar.bz2.sig
gpg: Signature made Wed Oct 20 18:01:45 2004 PDT using RSA key ID 53587D95
gpg: Good signature from "Fyodor <fyodor@insecure.org>"
$ rpm -K nmap-3.75-1.src.rpm
nmap-3.75-1.src.rpm: (sha1) dsa sha1 md5 gpg OK
Source Packages
Source RPM files (*.src.rpm) can either be rebuilt using the rpmbuild --rebuild command, or unpacked with the rpm command.
- Package Rebuild
- Package Expansion
Source packages must be rebuilt on new platforms such as x86_64. Run rpm -K to verify the integrity of the source package prior to rebuilding. If paranoid, scan the package with anti-virus software like Clam AntiVirus.
$ rpm -K cfengine-2.1.14-1.src.rpm
cfengine-2.1.14-1.src.rpm: (sha1) dsa sha1 md5 gpg OK
$ clamscan cfengine-2.1.14-1.src.rpm
…
$ rpmbuild --rebuild cfengine-2.1.14-1.src.rpm
…
The rpm command can expand *.src.rpm into the src/redhat directory structure, as specified by the ~/.rpmmacros file. If paranoid, scan the SOURCES directory with anti-virus software.
$ rpm -K cfengine-2.1.14-1.src.rpm
cfengine-2.1.14-1.src.rpm: (sha1) dsa sha1 md5 gpg OK
$ rpm -i cfengine-2.1.14-1.src.rpm
$ clamscan -r ~/src/redhat/SOURCES
The SOURCES directory will become very messy, as packages deposit everything there. This problem can be avoided by only expanding source packages as needed and cleaning up afterward. Or, alter the RPM macros file to store sources under a subdirectory for each module.
Newly built packages must be signed before being made available for download.
Rebuilding Packages
When writing new or fixing old specification files for packages that do not take long to build, have a script check whether the specification file has changed, and if so, automatically attempt a rebuild on the package. This saves having to rerun the rpmbuild command manually after each set of changes to the specification file, and during initial development of a new *.spec file, quick feedback about problems.
Coding until tests pass is known as test driven development. In terms of building software packages, the tests are whether the package can be built, whether the built package installs properly, and ideally that the tests the software ships with pass, such as those available during make test for many perl modules. Writing and automating these tests should enhance future development and debugging, as expected software behavior and layout is codified into the specification file and other test code.
For packages modified to suit some local site need, ideally indicate this somewhere in the %{name}, %{version}, or %{release} tags to distinguish local packages from standard ones.
More information on specification files can be found in Maximum RPM. See also the test.spec file, which includes example %pre and %post script handlers.
A common source of problems is Kerberos header errors when building against OpenSSL, which leads to errors involving the kssl.h file. One workaround is to set the /usr/kerberos/include directory to be in the include path.
%build
export CPATH=/usr/kerberos/include
Signing Packages
Sign packages on a dedicated build host, then transfer the signed packages to a separate distribution system. This way, a compromise of the distribution system does not give an attacker direct access to the private signing key, disallowing the chance to brute force or keystroke log the password to the private key. Packages must be signed, as otherwise an attacker will be able to run arbitrary software should a distribution server be broken into.
- Generate Signing Keypair
- Configure ~/.rpmmacros
- Sign packages
If not done so already, create and export a PGP key. Consult a guide on gpg for more information on generating and maintaining keys. I recommend each user signing packages create their own signing key, to rotate in new keys periodically, and to resign packages with a new key when a user leaves. If signing packages for public use, upload the public key to a key server.
$ gpg --gen-key
…
$ gpg --armor --export FIXME@example.org > RPM-GPG-KEY-FIXME-2005
Client systems will need to import the signing key so packages can be verified before being installed.
Set the following values in the ~/.rpmmacros file on the build hosts, where the %_gpg_name option must be able to identify the name of the key created.
%_signature gpg
%_gpg_name FIXME@example.org
Use the rpm --addsign command to sign packages.
$ rpm --addsign *RPMS/cfengine*.rpm
To sign a large number of packages at once, the find command can help.
$ find ~/src/redhat -name "*.rpm" -print0 | xargs -0 rpm --addsign
Once signed, copy the files to the distribution server for client systems to download.
Resigning Packages
If a package signer leaves a site, their public keys should be removed from all systems, and any packages signed by them resigned with a new key. To list the signature on existing packages, use one of the following rpm commands. Older packages may have a SIGPGP value instead of SIGGPG. Unsigned packages will have neither value.
$ rpm -qip clamav-0.86.1-1.src.rpm | grep Signature
Signature : DSA/SHA1, Mon Jun 27 12:20:58 2005, Key ID 5c681717a7309fb6
$ rpm -qp --qf '%{SIGGPG}\n' clamav-0.86.1-1.src.rpm
883f03050042c0519a5c681717a7309fb611029501009d13f5147875e400ad
769e648a5e4c7af97455a900009901ec1b5a68133721be02c0035e7f80d50c227562
Search for packages with the public key to replace, then feed them to rpm --addsign to resign them.
Perl Modules
More information on installing Perl modules via RPM.
Python Libraries
To build python libraries, use the bdist_rpm argument to setup.py.
$ python setup.py bdist_rpm