First-time Service Setup
Many system services require some amount of initial setup before they can run properly for the first time. Common examples are the generation of private keys and certificates or a unique, system-specific identifier.
Traditionally, this was done by RPM scriptlets as part of the installation or upgrade of a package. This was sensible for a time when the majority of installations were performed by attended or unattended installers (such as anaconda and kickstart).
Today we see an increased reliance on generating virtual machine images for use in both traditional and cloud-computing environments. In those cases, having system-specific data created at package installation time is problematic. It means that the production of such images need to have significant care applied to remove any system-specific information about them and then additional tools written to apply the corrected information post-deployment. The goal of this guideline is to ensure that if a system clean-up service such as virt-sysprep is run on the system and then the machine is rebooted, any service that requires first-time configuration will re-run it. The mechanism by which we will accomplish this is to remove such first-time configuration from RPM scriptlets (e.g. %post
) and instead execute this configuration as part of service startup with systemd.
This guideline describes a mechanism that can be used for both traditional and cloud-based deployment styles.
Note: this requirement can be waived if the equivalent functionality is incorporated as part of the service’s own standard startup. These guidelines are meant to address services that require setup before the service can be started.
Defining System-Specific Setup
A particular setup task is defined thusly: "Any action that must be performed on the system where the service will be run whose output is not identical for all systems running that service."
Some non-exhaustive examples of system-specific configuration:
-
The SSH daemon generates a public/private host key
-
The mod_ssl httpd module creates a self-signed certificate for the machine’s hostname
-
A remote logging service creates a UUID to represent this machine
A few examples that should not be considered system-specific configuration:
-
Creating a service user and/or group. This is safe to copy to clones of the system.
-
Any content that is automatically re-generated by the service upon deletion.
Generating Self-Signed Certificates
The sscg (Self-Signed Certificate Generator) tool should be used to generate self-signed certificates in a secure manner. It is present in all Fedora releases; online documentation can be found at 1.
More on self-signed certificates
If your service makes use of the SSL/TLS protocol for transport security, your service will require a service certificate. Ideally the administrator deploying a new service should obtain an X.509 certificate from an appropriate Certificate Authority (CA), which should be from a globally operating CA (such as a commercial SSL certificate vendor) if your service will be available on the public Internet, or from a private CA (such as a domain controller CA) if your service will run inside an Intranet.
However, it is often desirable to start using a self-signed certificate, which can be immediately created, and allows the administrator to immediately proceed doing the installation tasks. This document will explain how to obtain a self-signed certificate, but it is recommended that it gets replaced prior to public deployment.
The disadvantage of self-signed certificates is that most client software (like web browsers) will reject them as untrusted, and if at all, will require the user to override and trust it explicitly. The way this can be done varies depending on the client software. It is easier to add a CA certificate to the system store and mark it as trusted.
Therefore, instead of creating a self-signed certificate, we will create a temporary CA certificate and use it to sign the certificate used by the service. Afterwards we will delete the private key of the CA certificate, which will remove the ability to use it to create additional certificates. Afterwards we can import the CA certificate as trusted into the local system certificate store, and consequently every local client software that respects the system CA store will accept the service certificate as trusted. The sscg tool will handle all of this for you.
Common Guidelines
For all system-specific cases, we will take advantage of systemd’s service functionality. Packagers will create a new service unit file for each service unit in their package that requires per-system configuration. This service unit will be named -init.service
and installed to /usr/lib/systemd/system
. For example, the tog-pegasus.service
configuration unit would be /usr/lib/systemd/tog-pegasus-init.service
.
The contents of this service unit will be as follows:
[Unit] Description=One-time configuration for <servicename> ConditionPathExists=|!/path/to/generated/config ConditionPathExists=|!/path/to/other/generated/config (one or more lines optional) [Service] Type=oneshot RemainAfterExit=no ExecStart=/path/to/config/script
The syntax for ConditionPathExists=
uses the ! to indicate negation (the file is not present). The | is used to create an OR logical pairing (resulting in the lack of ANY of these files causing the configuration to be run). Those are called "triggering" conditions, for full explanation see systemd.unit(5). The /path/to/config/script
can be any executable script or binary that will generate the initial configuration needed by this service. It must generate the files tested by ConditionPathExists
. If the script is a single command, it can be run directly by this service unit. If it needs to run multiple commands, it is recommended to create a script file in the package’s /usr/libexec/
directory and execute that.
To use tog-pegasus.service
as an example:
[Unit] Description=One-time configuration for tog-pegasus ConditionPathExists=|!/etc/Pegasus/server.pem ConditionPathExists=|!/etc/Pegasus/file.pem ConditionPathExists=|!/etc/Pegasus/client.pem [Service] Type=oneshot RemainAfterExit=no ExecStart=/usr/bin/sscg --package tog-pegasus --ca-file /etc/Pegasus/client.pem --cert-file /etc/Pegasus/server.pem --cert-key-file /etc/Pegasus/file.pem
The ExecStart
command may do anything, so long as it returns 0 on success. In this case, we are generating a self-signed certificate for the service to use.
Packagers will also need to update their primary service unit to require this one and run after it:
[Unit] ... Requires=<service>-init.service After=<service>-init.service
To continue the tog-pegasus.service
example:
[Unit] Description=OpenPegasus CIM Server After=slpd.service Requires=tog-pegasus-init.service After=tog-pegasus-init.service [Service] Type=forking ExecStart=/usr/sbin/cimserver PIDFile=/var/run/tog-pegasus/cimserver.pid [Install] WantedBy=multi-user.target