Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Configuration

The server, bridge, and client configurations are written in TOML. By default, the configurations are loaded relative the the CONFIGURATION_DIRECTORY environment variable and named server.toml, bridge.toml, and client.toml respectively. Systemd sets the CONFIGURATION_DIRECTORY variable automatically in the provided systemd unit files, and if run as a system service the default directory is /etc/siguldry/.

Note

For production deployments, it is expected that the server, bridge, and any clients are separate hosts.

Systemd units

Units may need to be granted access to additional directories or devices. The defaults minimize what they have access to. Common cases are called out, but be aware that the systemd units will require overrides.

Socket units may need their MaxConnections settings raised, depending on how many concurrent requests are allowed.

systemd-creds

None of the configuration files contain any secrets directly. All secrets are provided via file paths and these files should be encrypted with systemd-creds.

systemd-creds allows you to encrypt secrets using a root-owned host key, a TPM2 device, or both. It also includes the ability to seal the secret to a set of PCR values in various ways. How you choose to set it up is left as an exercise to the reader.

Once you’ve decided on the configuration, you can encrypt any necessary secrets. For example, here’s how to generate a new key pair with OpenSSL, along with a certificate signing request for an associated X.509 certificate:

openssl req -new -nodes -sha256 \
    -subj "/CN=siguldry-server.example.com" \
    -keyout - \
    -newkey rsa:4096 \
    -out example.csr | \
    sudo systemd-creds encrypt - \
    /etc/credstore.encrypted/siguldry.server.private_key.pem

This generates the private key and pipes it directly to systemd-creds to be encrypted. The systemd units are configured to load and decrypt any credentials that begin with siguldry.

Within the configuration files, secret files should be referred to by the path relative to /etc/credstore.encrypted/ or /etc/credstore/. For the above example, you would set something like the following when referencing a secret:

private_key = "siguldry.server.private_key.pem"

X.509 Certificates

The server, bridge, and client all make use of X.509 certificates for mutual authentication when connecting over TLS.

  • Both server and client connect to the bridge. Both server and client check to ensure the certificate presented by the bridge includes a subjectAltName entry that matches the DNS name.

  • Both server and client present client certificates to the bridge for mutual TLS authenticate. These certificates MUST include the clientAuth extended key usage. The bridge only checks that the certificate presented is signed by the expected Certificate Authority.

  • The client connects to the server through the bridge and checks to ensure the certificate presented by the server matches the name in the client configuration file. The client presents its client certificate to the server. The server uses the Common Name field of the client’s certificate as the username. User setup will be covered in the administration section, but be aware that the name used when creating the user must match the value of the Common Name.

You will need to generate a set of keys and TLS certificates for the server, bridge, and one or more clients.

As an example, the following bash script creates a valid set of key pairs and X.509 certificates for a server, bridge, and single client.

Caution

This script does not encrypt any of the secret keys and should not be used for production credentials

#!/bin/bash

# If you already have a way to issue TLS certificates, it is recommended that
# you use that flow.
# 
# For reference, the following is how to generate a complete set of
# certificates.  Note that for production environments, care should be taken to
# encrypt and protect the private keys generated. The recommended approach is
# to use systemd-creds.
#
# This script accepts three or more arguments: the server commonName, the
# bridge commonName, and one or more client commonNames.
#
# For testing purposes, when all three services run on a single host,
# "siguldry-server", "localhost", and "siguldry-client" are recommended.

set -xeuo pipefail

SERVER_CN="${1}"
BRIDGE_CN="${2}"
if [[ $# -lt 3 ]]; then
    CLIENT_CNS=("")
else
    CLIENT_CNS=("${@:3}")
fi

mkdir -p creds/
pushd creds

# First, create a certificate authority which is used to sign all our certificates.
openssl req -x509 -new -nodes -sha256 \
    -days 3650 \
    -extensions v3_ca \
    -subj "/CN=Siguldry CA" \
    -newkey rsa:2048 \
    -keyout siguldry.ca.private_key.pem \
    -out siguldry.ca_certificate.pem

# Create and sign a server certificate
#
# The server uses its certificate both as a client connecting to the bridge, and
# as a server the client connects to via the bridge. The certificate for the
# server must have the `clientAuth` _and_ `serverAuth` extended key usage
# extensions.
# 
# Since the client only communicates through the bridge, and because the server
# initiates the connection to the bridge, the server's name does not need to
# resolve, but it does need to match what the client has been configured to
# accept.
openssl req -new -nodes -sha256 \
    -addext "subjectAltName = DNS:$SERVER_CN" \
    -addext "extendedKeyUsage = clientAuth,serverAuth" \
    -subj "/CN=$SERVER_CN" \
    -newkey rsa:2048 \
    -keyout siguldry.server.private_key.pem \
    -out server-cert.csr
openssl x509 -req -in server-cert.csr \
    -CAkey siguldry.ca.private_key.pem \
    -CA siguldry.ca_certificate.pem \
    -copy_extensions copyall \
    -days 3650 \
    -sha256 \
    -out siguldry.server.certificate.pem

# Create and sign a bridge certificate
#
# The bridge accepts connections from the server and the client. It needs the
# `serverAuth` extended key usage extension, and its name must resolve for both
# the client and server.
openssl req -new -nodes -sha256 \
    -addext "subjectAltName = DNS:$BRIDGE_CN" \
    -addext "extendedKeyUsage = serverAuth" \
    -subj "/CN=$BRIDGE_CN" \
    -newkey rsa:2048 \
    -keyout siguldry.bridge.private_key.pem \
    -out bridge-cert.csr
openssl x509 -req -in bridge-cert.csr \
    -CAkey siguldry.ca.private_key.pem \
    -CA siguldry.ca_certificate.pem \
    -copy_extensions copyall \
    -days 3650 \
    -sha256 \
    -out siguldry.bridge.certificate.pem

# Create and sign client certificates
#
# Each client needs a certificate to authenticate with. The common name of the
# certificate must match the username that we create on the Siguldry server
# later.
for CLIENT_CN in "${CLIENT_CNS[@]}"; do
    openssl req -new -nodes -sha256 \
        -addext "extendedKeyUsage = clientAuth" \
        -subj "/CN=$CLIENT_CN" \
        -newkey rsa:2048 \
        -keyout "siguldry.$CLIENT_CN.private_key.pem" \
        -out "siguldry.$CLIENT_CN.csr"
    openssl x509 -req -in "siguldry.$CLIENT_CN.csr" \
        -CAkey siguldry.ca.private_key.pem \
        -CA siguldry.ca_certificate.pem \
        -copy_extensions copyall \
        -days 3650 \
        -sha256 \
        -out "siguldry.$CLIENT_CN.certificate.pem"
done

rm -- siguldry.ca.private_key.pem *.csr

openssl verify -CAfile ./siguldry.ca_certificate.pem siguldry.server.certificate.pem
openssl verify -CAfile ./siguldry.ca_certificate.pem siguldry.bridge.certificate.pem
for CLIENT_CN in "${CLIENT_CNS[@]}"; do
    openssl verify -CAfile ./siguldry.ca_certificate.pem "siguldry.$CLIENT_CN.certificate.pem"
done

popd

Use the above script as an inspiration and encrypt all the private keys with systemd-creds. With your credentials in hand, you can now configure the services.

Server Configuration

An example server configuration:

# An example server configuration

# The location where the server should store its state.
#
# To back up the service, back up this directory.
state_directory = "/var/lib/siguldry/"

# The hostname of the Siguldry bridge; this is used to verify the bridge's
# TLS certificate.
bridge_hostname = "bridge.example.com"

# The port to use when connecting to the Siguldry bridge
bridge_port = 44333

# The number of ready connections to maintain with the bridge. This decreases the latency of
# responses when multiple client connections are established, at the expense of some idle
# connections. Be aware that the bridge has its own limits on the allowable number of idle
# server connections. If you use multiple servers with a single bridge, be sure that the
# bridge allows enough idle connections to cover each server's pool size. The default is 32.
connection_pool_size = 32

# The minimum length for user's access password, in *bytes*. For example, the multi-byte
# UTF-8 character "🪿" counts as 4 bytes.
user_password_length = 32

# The user ID to use when creating OpenPGP keys.
#
# This is typically an email like "Signing Key <signing@example.com>".
openpgp_user_id = "Test Signing <sign@example.com>"

# The set of certificates to encrypt passwords with.
#
# At least one entry should include a PKCS#11 URI for a private key. Signing keys are encrypted
# using each certificate, so providing more than one binding means *any* of the private keys
# associated with the certificates will allow you to access the signing key, assuming you have
# the user-set password for the key as well.
#
# When binding is used, the admin needs to unlock the token by entering the PIN using
# "siguldry-server enter-pin".
#
# If no bindings are configured, the keys are protected using only the user-provided
# password.
#
# An example binding entry:
#
# [[pkcs11_bindings]]
# certificate = "/path/to/cert.pem"
# private_key = "pkcs11:token=some-token;type=private"
#
# [[pkcs11_bindings]]
# certificate = "/path/to/a/second/cert.pem"
pkcs11_bindings = []

# The credentials to use when connecting to the bridge and when accepting client connections
# tunneled through the bridge. Note that the certificate must have both `clientAuth` and
# `serverAuth` in its extended key usage extension.
#
# It is expected that you store the private key in /etc/credstore.encrypted/ and the certificates
# in /etc/credstore/ (no encryption necessary).
[credentials]
private_key = "siguldry.server.private_key.pem"
certificate = "siguldry.server.certificate.pem"
ca_certificate = "siguldry.ca_certificate.pem"

# Certificates created by Siguldry allow the user to specify the subject's common name.
#
# The rest of the certificate's subject is specified here.
[certificate_subject]
country = "US"
state_or_province = "Massachusetts"
locality = "Cambridge"
organization = "An Example Organization"
organizational_unit = "Example Department of the Organization"

By default, this is loaded from /etc/siguldry/server.toml.

Hardware Signing Key

In the event you plan to use signing keys stored in hardware security modules, you need to configure the siguldry-signer@.service unit to have access to it. Add a systemd override file with the appropriate DeviceAllow directives.

Bridge Configuration

An example bridge configuration:

# This is an example bridge configuration.

# The socket address to listen on for incoming connections from Siguldry servers.
#
# The default is to listen on all interfaces on port 44333.
server_listening_address = "[::]:44333"

# The socket address to listen on for incoming connections from Siguldry clients.
#
# The default is to listen on all interfaces on port 44334.
client_listening_address = "[::]:44334"

# The TLS credentials for the server and client listeners.
#
# Both clients and servers connect to the above addresses and perform mutual TLS.
# Note that the certificate must have `serverAuth` in its extended key usage extension.
[credentials]
private_key = "siguldry.bridge.private_key.pem"
certificate = "siguldry.bridge.certificate.pem"
ca_certificate = "siguldry.ca_certificate.pem"

By default, this is loaded from /etc/siguldry/bridge.toml.

Client Configuration

An example client configuration:

# An example client configuration

# The Siguldry server hostname. This is used to validate the server's TLS certificate.
#
# However, since the client connects through the bridge, no DNS resolution is performed.
# This name just needs to match what is in the server's certificate.
server_hostname = "server.example.com"

# The Siguldry bridge hostname. This is used to validate the bridge's TLS certificate.
bridge_hostname = "bridge.example.com"

# The port on the Siguldry bridge to connect to; the default is 44334.
bridge_port = 44334

# A list of keys to unlock for the client.
#
# This can be set for users of the client who can't (or don't want to) call unlock or safely
# store a password. One example would be the PKCS#11 module used inside a build environment.
#
# An example entry:
#
# [[keys]]
# key_name = "signing-key"
# # Store this encrypted in /etc/credstore.encrypted/
# passphrase_path = "siguldry.signing_key.passphrase"
keys = []

# The time, in seconds, to leave an idle connection to the signing server open.
#
# Idle time is measured from the last time the client sent a request to the server,
# and clients transparently restart the connection on the next request. The server
# will also shut down idle client connections after a time (in a much less graceful
# manner) so this value should be somewhat less than the server-set timeout. It should
# also be *larger* than the `request_timeout` setting.
#
# The default idle timeout is 600 (10 minutes).
idle_timeout = 600

# The amount of time, in seconds, to wait before giving up on a request and retrying.
#
# This covers both sending requests and receiving responses. In other words, the client
# will retry the request on a new connection if it cannot write the request to the socket
# within `request_timeout`, *and* it will retry if it fails to read a response to that
# request from the socket within `request_timeout`.
request_timeout = 30

# The credentials to use when authenticating to the Siguldry bridge and server. Note that
# the certificate must have the `clientAuth` extended key usage extension.
[credentials]
private_key = "siguldry.client.private_key.pem"
certificate = "siguldry.client.certificate.pem"
ca_certificate = "siguldry.ca_certificate.pem"

By default, this is loaded from /etc/siguldry/client.toml.

siguldy-fedora-autopen

If you’re looking to automatically sign content from AMQP messages, and you happen to also be using Koji, the siguldry-fedora-autopen application is what you’re looking for. It should be configured on a host where a client configuration also exists, and any signing keys it uses must be configured to be automatically unlocked by the client.

The siguldry-fedora-autopen.service requires that the siguldry-client-proxy.socket systemd socket is active and accessible to the service. Its default configuration file location when running as a system service is /etc/siguldry/fedora-autopen.toml. Its configuration is similar, but not identical, to the robosignatory service it replaces.

An example configuration:

[amqp]
amqp_url = "amqps://fedora:@rabbitmq.fedoraproject.org/%2Fpublic_pubsub?auth_mechanism=external"

[amqp.tls]
ca_certificate = "/etc/fedora-messaging/cacert.pem"
private_key = "/etc/fedora-messaging/fedora-key.pem"
certificate = "/etc/fedora-messaging/fedora-cert.pem"

[[amqp.bindings]]
exchange = "amq.topic"
routing_keys = [
    "org.fedoraproject.*.buildsys.tag",
    "org.fedoraproject.*.coreos.build.request.artifacts-sign",
    "org.fedoraproject.*.pungi.compose.ostree",
]

[siguldry]
client_proxy_socket = "/run/siguldry-client-proxy/siguldry-client-proxy.socket"
concurrency = 128

[koji]
url = "https://koji.fedoraproject.org/kojihub"
instance = "primary"
# Enabling the readonly setting below will turn off any Koji write operations.
# This is only really useful for local testing against a public Koji instance.
#readonly = true

[koji.auth]
authmethod = "kerberos"
principal = "jcline@FEDORAPROJECT.ORG"

[[koji.tags]]
from = "f45-signing-pending"
to = "f45-updates-testing-pending"
siguldry_key = "fedora-45"
siguldry_openpgp_cert = "fedora-45-openpgp"
trusted_taggers = ["bodhi"]

[koji.tags.file_signing_key]
siguldry_key = "fedora-45-ima"
siguldry_x509_cert = "fedora-45-ima-x509"

[koji.tags.sidetags]
from_regex = "(?P<sidetag>f45-build-side-[1-9][0-9]*)-signing-pending"
to_template = "<sidetag>-testing-pending"

[rpm]
signing_tool = "gpg"
with_rpmv4 = true

[[ostree]]
reference = "fedora/rawhide/x86_64/iot"
directory = "/mnt/fedora_koji/koji/compose/iot/repo/"
siguldry_key = "fedora-45"
siguldry_openpgp_cert = "fedora-45-openpgp"

[[ostree]]
reference = "fedora/rawhide/aarch64/iot"
directory = "/mnt/fedora_koji/koji/compose/iot/repo/"
siguldry_key = "fedora-45"
siguldry_openpgp_cert = "fedora-45-openpgp"

[coreos]
aws_region = "us-east-2"
aws_bucket = "fcos-builds"
aws_access_key = "some-access-key"
aws_access_secret = "secret key"
signing_tool = "gpg"

[[coreos.keys]]
build_version = 45
siguldry_key =  "fedora-45"
siguldry_openpgp_cert = "fedora-45-openpgp"

[metrics]
http_listener = "127.0.0.1:9000"