From Fedora Project Wiki

Revision as of 14:17, 21 July 2015 by Sgallagh (talk | contribs) (→‎Common Guidelines: More explanation of the ExecStart line)

This page is a DRAFT and is currently under development

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.

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 <servicename>-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). 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/<packagename> directory and execute that.

To use tog-pegasus.service as an example:

[Unit]
Description=One-time configuration for <servicename>

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

Special Case: Self-signed Certificate Generation

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.

Easy implementation with sscg

There is a simple tool in Fedora called sscg (Self-Signed Certificate Generator) that will create a certificate using the above mechanism for you. See the tog-pegasus example above for a simple invocation.

usage: sscg [-h] [--debug] [--cert-format {PEM,ASN1}] [--lifetime LIFETIME]
            [--key-strength {512,1024,2048,4096}]
            [--hash-alg {md4,md5,ripemd160,sha,sha1,sha224,sha256,sha384,sha512,whirlpool}]
            --package PACKAGE [--ca-file CA_FILE] --cert-file CERT_FILE
            --cert-key-file CERT_KEY_FILE [--hostname HOSTNAME]
            [--subject-alt-names SUBJECT_ALT_NAMES [SUBJECT_ALT_NAMES ...]]
            [--country COUNTRY] [--state STATE] [--locality LOCALITY]
            [--organization ORGANIZATION]
            [--organizational-unit ORGANIZATIONAL_UNIT]

Generate a self-signed service certificate

optional arguments:
  -h, --help            show this help message and exit
  --debug               Enable logging of debug messages.

Output:
  --cert-format {PEM,ASN1}
                        Certificate file format. Default=PEM
  --lifetime LIFETIME   Certificate lifetime (days). Default=3650 (10 years)
  --key-strength {512,1024,2048,4096}
                        Strength of the certificate private keys in bits.
                        Default=2048
  --hash-alg {md4,md5,ripemd160,sha,sha1,sha224,sha256,sha384,sha512,whirlpool}
                        Hashing algorithm to use for signing. Default=sha256
  --package PACKAGE     The name of the package needing a certificate
  --ca-file CA_FILE     Path where the public CA certificate will be stored.
                        Default: ca.crt in service certificate directory
  --cert-file CERT_FILE
                        Path where the public service certificate will be
                        stored.
  --cert-key-file CERT_KEY_FILE
                        Path where the private key of the service certificate
                        will be stored

Certificate Details:
  --hostname HOSTNAME   The valid hostname of the certificate. Must be an
                        FQDN. Default: system hostname
  --subject-alt-names SUBJECT_ALT_NAMES [SUBJECT_ALT_NAMES ...]
                        One or more additional valid hostnames for the
                        certificate
  --country COUNTRY     Certificate DN: Country (C)
  --state STATE         Certificate DN: State (ST)
  --locality LOCALITY   Certificate DN: Locality (L)
  --organization ORGANIZATION
                        Certificate DN: Organization (O)
  --organizational-unit ORGANIZATIONAL_UNIT
                        Certificate DN: Organizational Unit (OU)

Do-it-yourself

Guidelines for scripting openssl or nss