Radicale

Radicale is a simple CalDAV and CardDAV server with extremely low administrative overhead. It does not provide the full set of features described by RFC 4791, the Calendaring extensions to WebDAV (CalDAV) nor RFC 6352, the vCard Extensions to WebDAV (CardDAV), but does offer enough functionality for many use cases to completely replace technocapitalist SaaSS products such as Google Calendar, Google Contacts, and so on. Using Python, you can also write your own Radicale plugins.

To be useful, Radicale must be paired with a CalDAV and/or CardDAV client. See the Tech Autonomy guide to using digital calendars for details on client application installation and configuration.

Installing

The recommended way to install Radicale is through Python’s own, cross-platform package manager, pip:

sudo pip3 install --upgrade radicale

On Debian-derived GNU/Linux systems, Radicale can also be easily installed from the distribution’s package repositories, although you will probably receive an outdated version:

sudo apt install radicale

Configuring

There are numerous different configurations in which Radicale can be set up. This page describes the configurations useful for and familiar to the Tech Autonomy Infrastructure committee needs.

Long-running single Radicale server process with application-level user access control

This section describes configuring Radicale with systemd to invoke a long-running single radicale server process.

  1. Set up the radicale user if installing Radicale did not already add such a user (use getent passwd radicale to see if such a user has already been created):
    sudo adduser --system --home /YOUR/CHOSEN/RADICALE/HOME/FOLDER radicale
  2. Configure the radicale daemon process itself. This is done by writing a configuration file and placing it in a conventional place, which is usually /etc/radicale/config for most GNU/Linux server systems. Paste the following configuration file, being careful to change the value of YOUR_REALM_NAME to a sensible one as you desire:
    # FILE: /etc/radicale/config
    #
    # Radicale configuration file.
    #
    # This is the configuration file for Radicale's daemon process.
    # It provides a default set of options that will take effect if
    # the equivalent command-line options are not passed during the
    # process's invocation. In some configurations, such as when run
    # from a socket-activated systemd unit, some options defined in
    # this file have no effect.
    #
    # See http://radicale.org/configuration/ for more details and
    # descriptions of additional available configuration directives.
    
    [server]
    # Bind to the local host only. This makes the Radicale server
    # unavailable to the Local Area Network.
    hosts = 127.0.0.1:5232
    
    # Run as a daemon process, in the background, when started.
    daemon = True
    
    # Write the process ID number to the file specified here.
    # This is ignored if the `--pid` command line option is passed.
    pid = /var/run/radicale/radicale.pid
    
    # Do not perform reverse DNS lookups when logging.
    # See the `[logging]` section of this file for more details.
    dns_lookup = False
    
    # The logical name of this server's WebDAV collections.
    # This is also displayed to clients when/if they are prompted
    # for access credentials (a username/password).
    realm = YOUR_REALM_NAME
    
    # Consider TLS directives carefully before activating them.
    #ssl = True
    #certificate = /etc/ssl/radicale.cert.pem
    #key = /etc/ssl/radicale.key.pem
    #certificate_authority = 
    #protocol = PROTOCOL_TLSv1_2
    #ciphers = 
    
    # Define the character encoding for both network requests
    # as well as when writing (or reading) from local storage.
    # The default is `utf-8`.
    #[encoding]
    #request = utf-8
    #stock = utf-8
    
    # Application-level user access control.
    [auth]
    type = htpasswd
    htpasswd_filename = /var/lib/radicale/users.htpasswd
    htpasswd_encryption = bcrypt
    
    # How long to wait (on average) before prompting the client for
    # another password attempt if the prior one fails. Increase this
    # to slow down brute-force password guessing attacks.
    # Defaults to `1` second.
    delay = 1
    
    [rights]
    type = from_file
    file = /var/lib/radicale/rights.conf
    
    [storage]
    # These are the default; they can be left commented.
    #type = multifilesystem
    #filesystem_folder = /var/lib/radicale/collections
    #filesystem_locking = True
    #max_sync_token_age = 2592000
    #filesystem_fsync = True
    # For an example of the `hook` directive in use, see
    # http://radicale.org/versioning/
    #hook =
    
    # The backend that provides the web interface of Radicale.
    # This can be either `none` or `internal`, or can be the name
    # given by a plugin that offers a custom Web-based interface.
    # The default is `internal`.
    [web]
    #type = internal
    
    # Additional HTTP headers that are sent to clients.
    [headers]
    #X-Extra-HTTP-Header = HelloWorld
    
    # Configure Radicale's logging behaviors.
    [logging]
    # Turn off Radicale's debug logging.
    debug = False
    
    # Never write passwords into log files, including debug logs.
    mask_passwords = True
    
    #full_environment = False
    #config = /etc/radicale/log.conf
    
  3. The above configuration uses application-level user access control, so we’ll need to define users and their passwords in the user authentication file defined in the configuration. In the example above, the location of this file is /var/lib/radicale/users.htpasswd. Moreover, the configuration tells Radicale that user passwords are hashed using the bcrypt password hashing algorithm, so we will need to use the htpasswd(1) command to generate the file itself, replacing RADICALE_USER_NAME with the username you’d like to add:
    # If this is the first time you are creating or administering user names and passwords, you must additionally
    # pass the `-c` option to `htpasswd` to create the file. BE CAREFUL, as using this option will truncate the file
    # if it already exists, wiping your user database clean and irrevocably deleting their stored passwords!
    sudo htpasswd -B /var/lib/radicale/users.htpasswd RADICALE_USER_NAME
    # Note also that you can use the -C option (that's capital "C") with a value greater than `5` to strengthen
    # the bcrypt hash with additional hashing rounds. This is "paranoid" but you are welcome to do so anyway.
    

    Repeat the above command for any additional users you want Radicale to be able to authenticate.
  4. Set up Radicale’s user rights configuration file: sudo vim /var/lib/radicale/rights.conf
    # FILE: /var/lib/radicale/rights.conf
    #
    ################################################
    # Radicale user rights configuration file.     #
    #                                              #
    # See http://radicale.org/rights/ for details. #
    ################################################
    
    ## The user "admin" can read and write any collection.
    #[admin]
    #user = admin
    #collection = .*
    #permission = rw
    
    # Authenticated users can list (discover) their own collections.
    [owner-discover]
    user = .+
    collection = ^%(login)s$
    permission = rw
    
    # Authenticated users can read and write their own collections.
    [owner-write]
    user = .+
    collection = ^%(login)s/.*
    permission = rw
    
    # Everyone can read the root collection
    [read]
    user = .*
    collection =
    permission = r
    
  5. Set up the systemd service: sudo vim /etc/systemd/system/radicale.service
    # FILE: /etc/systemd/system/radicale.service
    #
    # Radicale CalDAV and CardDAV server startup script.
    #
    # This script is a systemd service unit that ensures
    # the Radicale CalDAV/CardDav server is operational.
    
    [Unit]
    Description=A simple CalDAV (calendar) and CardDAV (contact) server
    
    [Service]
    User=radicale
    Group=radicale
    UMask=0027
    RuntimeDirectory=radicale
    ExecStart=/usr/local/bin/radicale
    Restart=on-failure
    # Extra security precautions
    LockPersonality=true
    MemoryDenyWriteExecute=true
    NoNewPrivileges=true
    PrivateDevices=true
    PrivateTmp=true
    PrivateUsers=true
    ProtectControlGroups=true
    ProtectHome=true
    ProtectKernelModules=true
    ProtectKernelTunables=true
    ProtectSystem=full
    RestrictAddressFamilies=AF_INET
    RestrictRealtime=true
    
    [Install]
    WantedBy=multi-user.target
    
  6. Tell systemd to re-read its configuration files:
    sudo systemctl daemon-reload
  7. Enable the Radicale service so that it runs atomatically during startup after the next reboot:
    sudo systemctl enable radicale.service
  8. Manually start the Radicale service:
    sudo systemctl start radicale.service

To test that this is working, check to see if Radicale is now bound to a listening network socket and accepting incoming requests:

sudo ss --listening --tcp --numeric --processes | grep radicale
LISTEN     0      5                 127.0.0.1:5232                     *:*      users:(("radicale",pid=721,fd=4))

Sharing the same collection amongst multiple users

Radicale provides no support for CalDAV’s groups, locations, resources, or principal delegation features out-of-the-box. Nevertheless, it is often important for multiple distinct users to be able to access the same data, such as when two or more users want to share the same calendar, journal, task list, or address book. If your storage backend is multifilesystem (i.e., simple files), rudimentary implementations of these features can be effectively simulated using symbolic links, as follows:

  1. Go to the root of Radicale’s collections storage directory:
    cd /var/lib/radicale/collections
  2. Make a directory to simulate the concept of CalDAV “groups,” “locations,” “resources,” or whichever type of principal you need:
    sudo -u radicale mkdir groups
  3. Use a CalDAV client or Radicale’s built-in (internal) Web interface to create a calendar, journal, task list, or address book for a given user. This will create a folder named with the UUID assigned to the collection. For example, if the user who created the calendar is exampleuser, a directory listing will show a new calendar in that user’s collections directory:
    ls collection-root/exampleuser

    You can also manually create a new empty CalDAV collection that Radicale will recognize as a calendar with the following commands:
    OUR_NEW_CALENDAR_UUID="$(python -c 'import uuid; print(uuid.uuid4());')" # Create a new unique calendar identifier string.
    # Prepare a Radicale "properties" file, which needs to be in JSON syntax and have a minimum of these three keys.
    OUR_NEW_CALENDAR_PROPS='{"C:supported-calendar-component-set": "VEVENT", "D:displayname": "Our Example New Calendar Title", "tag": "VCALENDAR"}'
    # Make a new directory for the collection based on the new calendar's UUID generated earlier.
    sudo -u radicale mkdir collection-root/exampleuser/"$OUR_NEW_CALENDAR_UUID"
    # Write the calendar's properties to the file where Radicale expects them to be:
    sudo -u radicale bash -c "echo -n '$OUR_NEW_CALENDAR_PROPS' > collection-root/exampleuser/$OUR_NEW_CALENDAR_UUID/.Radicale.props"
    

    At this point, refreshing the user’s collections (by reloading Radicale’s Web interface, for example), will display the new empty calendar.
  4. Make a note of the name of this new directory (if it’s not already saved in a shell variable as $OUR_NEW_CALENDAR_UUID), as we will need to refer to it shortly.
  5. Move the newly created calendar out of the given user’s personal collections folder and into the new “groups” folder, created earlier:
    sudo -u radicale mv collection-root/exampleuser/"$OUR_NEW_CALENDAR_UUID" groups/
  6. Create a symbolic link pointing to the new filesystem location of the shared (“group”) calendar from its old location inside the user’s own directory:
    sudo -u radicale ln -s ../../groups/$OUR_NEW_CALENDAR_UUID collection-root/exampleuser

    Repeat the above command, replacing exampleuser with each user you want to add to the (simulated) group.

Revoking a user’s access to a shared calendar

To remove the ability for a given user to access a shared calendar, delete the symbolic link pointing to the shared collection from the user’s personal collections folder.

Hardening

If you intend to run Radicale as a service for other users, and especially if you configured Radicale yourself (as opposed to implementing one of the configurations described above) you should strongly consider taking additional security precautions to help ensure that it is as difficult as possible for attackers to exploit. This section describes a non-exhaustive guide to securing your Radicale server instance.

Network security

Restrict LAN access

TK-TODO

Use application-level user access control

TK-TODO

Host-based security

Disable logging

TK-TODO

Recovering

As Radicale is a service that stores user data, it is important to account for system failures to ensure that you can continue servicing user needs in the event of a disaster.

Creating a backup

Simple tarball backup

# Log in to the server running the Radicale instance.
ssh user@the.server.local

# Create the simple tarball backup.
sudo -u radicale tar -czf /path/to/backup/folder/radicale-$(date '+%Y-%m-%d').tar.gz /var/lib/radicale
sudo chown $USER # Give yourself ownership over the created backup.

# You may wish to encrypt the backup archive at this point. For example:
#gpg --symmetric --cipher-algo AES256 \
#    --output /path/to/backup/folder/radicale-$(date '+%Y-%m-%d').tar.gz.enc \
#    /path/to/backup/folder/radicale-$(date '+%Y-%m-%d').tar.gz
#
# Alternatively, you may wish to ensure only encrypted backup data gets written to disk.
# In this case, create the simple tarball backup and immediately encrypt it through a pipe:
#sudo -u radicale tar -cz /var/lib/radicale | \
#    gpg --symmetric --cipher-algo AES256 > /path/to/backup/folder/radicale-$(date '+%Y-%m-%d').tar.gz.enc

# Then log out from the server.
exit

# Download the simple tarball backup file or the encrypted version, to your workstation.
scp user@the.server.local:/path/to/backup/folder/radicale-$(date '+%Y-%m-%d').tar.gz* /path/to/safe/backup/storage/system

# Finally, remove the backup from the server, and any encrypted backup copies.
ssh user@the.server.local "rm -f /path/to/backup/folder/radicale-$(date '+%Y-%m-%d').tar.gz*"

Encrypting your backup file

Consider encrypting your backup file with a pass phrase (preferred) or a public key (also good, but carries different risks). This can be accomplished easily with GnuPG. For example:

# Symmetrically (i.e., password) encrypt a given file.
gpg --symmetric --output encrypted_file.enc plaintext_file.txt

In many GnuPG implementations, the default symmetric cipher is CAST5 (aka, CAST-128). While decent, you should use a stronger cipher if possible. Invoke gpg --version to view the list of cipher algorithms supported by your installation. Then choose one with the --cipher-algo option. For example, to use the much more modern AES-256 cipher:

gpg --symmetric --cipher-algo AES256 --output encrypted_file.enc plaintext_file.txt

Duplicity backup

ssh user@the.server.local
sudo -u radicale duplicity /var/lib/radicale $TARGET_URL

See also duplicity.

Restoring a backup

ssh user@the.server.local
sudo -u radicale duplicity $TARGET_URL /var/lib/radicale

See also duplicity.

Provisioning

The AnarchoTech NYC collective provides an Ansible role for provisioning a Radicale server that runs on a Raspberry Pi. It can be installed in your local $ANSIBLE_ROLES_PATH (see Ansible Configuration Settings) for use with an Ansible project with:

ansible-galaxy install https://github.com/AnarchoTechNYC/ansible-role-radicale/archive/master.tar.gz