Best practices for securely backing up user data on INFN Cloud
Important
You and the system administrator of the VM are solely responsible for the security of the data stored in the VM. The following best practices are recommendations to help you in this process.
Introduction
The backup process is a critical part of the data management process and it is important to ensure that it is secure and reliable.
While there are numerous tools and services available for backing up data, in this guide, we will focus on how to set up a custom solution for backing up any directory in your VM to the INFN Cloud object storage. To this aim, please be sure to have already created your personal bucket in the object storage, as described in the INFN Cloud object storage service user guide.
The instructions provided in this guide can be adapted to other cloud storage providers supported by the backup tools that we will use.
The custom solution we propose is composed of multiple components that work together to securely access the INFN Cloud object storage and perform the backup process:
- oidc-agent, a tool to obtain OIDC tokens from the INFN Cloud IAM;
- a helper script, provided by INFN Cloud, that uses the OIDC token to obtain temporary S3 credentials to access the object storage;
- a backup tool compatible with S3 object storage.
You can either deploy this solution directly on your VM or use the containerized version.
Docker-based Setup
To simplify the process of setting up the backup solution, we provide a
Dockerized solution that includes all the necessary dependencies to run
the backup software. An example of a docker-compose.yaml file that
sets up the necessary services to perform the backup using Restic (more
details on the tool will be provided in the manual setup section) is
provided in this
repository.
Important
Make sure to change all the placeholder values to reflect your specific setup before running the compose file.
Configuration steps
To set up the Docker solution, follow these preliminary steps:
- Create a directory to store all the necessary files for the setup,
e.g.
backup-setup, and navigate into it. - Download the
docker-compose.yamlfile from the provided repository; - Create a file named
oidc-passcontaining a secure password that will be used to protect the oidc-agent profile; - Create an empty file named
access-tokenthat will be used to share the generated access token between services; - Create a file named
restic_passwordcontaining a secure password that will be used to encrypt the Restic repository; - Create a directory named
oidc-datathat will be used to store the oidc-agent configuration and tokens; - Create a file named
aws_credthat will be used to store the AWS credentials. This file will be automatically populated by thecloud-s3-credservice; - Create a directory named
ofeliawith a sub-directorylogsto store the logs of the scheduled jobs.
mkdir backup-setup && cd backup-setup
curl -fsSL -o docker-compose.yaml \
https://baltig.infn.it/infn-cloud/user-support-scripts/-/raw/main/backup/docker-compose.yaml
# Create password files (you will be prompted to enter passwords)
read -rsp "Please enter a secure password for the OIDC agent profile: " oidc_pass && \
echo && printf '%s\n' "$oidc_pass" > oidc-pass && unset oidc_pass
read -rsp "Please enter a secure password for the Restic repository encryption: " restic_pass && \
echo && printf '%s\n' "$restic_pass" > restic_password && unset restic_pass
# Create empty files
: > access-token
: > aws_cred
# Create directories
mkdir -p oidc-data ofelia/logs
# Restrict sensitive file permissions
chmod 600 oidc-pass restic_password aws_cred access-token
chmod 700 oidc-data
The name of the directories and files can be changed as needed, but make
sure to update the docker-compose.yaml file accordingly. Make sure
to set the proper permissions to avoid other users reading its
content.
Next you should edit the docker-compose.yaml file to reflect your
specific setup. In the following we will break down each service and
explain the necessary configuration.
oidc-agent
The oidc-agent service is responsible for managing OpenID Connect
(OIDC) authentication with the INFN Cloud IAM. This service obtains and
maintains access tokens that are used by cloud-s3-cred service to
authenticate with the cloud infrastructure.
What it does:
- Handles OIDC token generation and renewal from the INFN Cloud IAM
- Stores the OIDC profile configuration securely
- Makes tokens available to other services through a shared file
Volumes:
./oidc-data/:/root/.oidc-agent- Persists OIDC agent configuration and profile data./oidc-pass:/root/.oidc-password- Contains the password to unlock the OIDC profile./access-token:/root/access-token- Shares the generated access token with other services
Environment Variables:
OIDC_PASSWORD_FILE=/root/.oidc-password- Path to the file containing the OIDC profile passwordOIDC_OUTPUT_FILE=/root/access-token- Path where the access token will be written
cloud-s3-cred
The cloud-s3-cred service exchanges OIDC tokens for temporary S3
credentials. It communicates with the INFN Cloud STS (Security Token
Service) to generate AWS-compatible credentials that allow access to the
object storage.
What it does:
- Reads the OIDC access token generated by
oidc-agent - Calls the INFN Cloud STS endpoint to exchange the token for temporary AWS credentials
- Writes the S3 credentials to a file that other services can use
Volumes:
./aws_cred:/root/.aws/credentials:rw- Stores the temporary S3 credentials./access-token:/root/access-token:ro- Reads the access token fromoidc-agent
Environment Variables:
OIDC_TOKEN_FILE=/root/access-token- Path to the access token file to read
backup-dir0
The backup-dir0 service performs the actual backup of your data using
Restic.
You can create multiple instances of this service (backup-dir1,
backup-dir2, etc...) to backup different directories.
What it does:
- Reads data from the mounted source directory
- Uses S3 credentials from
cloud-s3-credto authenticate with object storage - Creates encrypted backups in the INFN Cloud object storage
- Supports incremental backups with deduplication
Volumes:
./aws_cred:/root/.aws/credentials:ro- Reads the S3 credentials (generated bycloud-s3-cred)./restic_password:/root/restic_password:ro- Reads the Restic repository encryption password./source/to/backup:/backup0:ro- The directory you want to backup (mounted read-only inside the container)
Environment Variables:
RESTIC_REPOSITORY=s3:https://rgw.cloud.infn.it/<bucket_name>/<bucket_path>- The S3 path where backups will be stored
RESTIC_PASSWORD_FILE=/root/restic_password- Path to the file containing the Restic repository passwordRESTIC_HOST=<custom_name>- A unique identifier for this backup host (used to organize snapshots)
Parameters to Change:
./source/to/backup- Change to the actual path on your host machine that you want to backup/backup0- Can be renamed if you prefer a different mount point inside the container<bucket_name>- Your INFN AAI username (your personal bucket name)<bucket_path>- The path within your bucket where backups should be stored (e.g.,backup/vm1)<custom_name>- A descriptive name for this backup instance (e.g., the VM hostname)
ofelia
The ofelia service is a scheduler that automates the execution of
backup and maintenance tasks on a defined schedule. It monitors Docker
containers and runs commands at specified times.
What it does:
- Schedules periodic execution of backup jobs
- Refreshes S3 credentials and OIDC tokens before they expire (default expiry is 1 hour)
- Cleans up old backup snapshots to save storage space
- Maintains logs of all job executions
- Allows monitoring through webhook (INFN Cloud Healthchecks service can be used)
Initialize the setup
Once you have configured the docker-compose.yaml file and created the
necessary files and directories, you should create the oidc-agent
profile by running the following command:
docker compose run --rm oidc-agent config
This command will prompt you to authenticate with the INFN Cloud IAM, create the necessary OIDC profile and generate the initial access token.
Generate the S3 credentials by running:
docker compose run --rm cloud-s3-cred
At this point you should be ready to initialize the Restic repository. You can do this by running the following command:
docker compose run --rm backup-dir0 init
This command will create a new Restic repository in the specified S3
path, encrypted with the password you provided in the restic_password
file.
You are now ready to start the automated backup process with the command:
docker compose up -d
Restore a backup
To restore data from a Restic backup you need:
- access to the repository: the repository path
(
RESTIC_REPOSITORY) and valid S3 credentials. The credentials are refreshed automatically by the scheduled jobs; if they are not running, generate them withdocker compose run --rm cloud-s3-cred; - the repository password stored in the
restic_passwordfile. Without it the backup cannot be decrypted; - a local directory where the restored files will be written.
If the original VM is lost, set up the Docker solution on a new VM as
described above, using the same restic_password,
RESTIC_REPOSITORY and RESTIC_HOST values of the original setup. Run
the oidc-agent config and cloud-s3-cred commands, but do not run
init, since the repository already exists.
The restore is then done in four steps.
1. Find the snapshot you want to restore. Each backup run creates a snapshot, identified by an ID and the time it was taken:
docker compose run --rm backup-dir0 snapshots
Snapshots are filtered by the RESTIC_HOST value, which is why it must
match the one used to create them.
2. Look inside the snapshot (optional) to find the files you need:
docker compose run --rm backup-dir0 ls <snapshot_id>
3. Restore the snapshot into a local directory, mounted inside the
container. Use the snapshot ID, or latest for the most recent one:
mkdir -p restore
docker compose run --rm -v ./restore:/restore backup-dir0 restore <snapshot_id> --target /restore
To restore only some files or directories, add one or more --include
options with their path inside the snapshot, e.g. --include
/backup0/path/to/file.
4. Check the restored data and copy it back. The files are restored
under the same path they had inside the container, e.g.
restore/backup0/.... Once you have verified them, copy them to their
original location, for example with:
rsync -a restore/backup0/ /source/to/backup/
If you are using the manual setup, the steps are the
same: run the commands with restic instead of docker compose run --rm
backup-dir0, after exporting the RESTIC_REPOSITORY and
RESTIC_PASSWORD_FILE variables, and pass any local directory to
--target.
For all the available options, e.g. restoring single files to standard output or overwriting files in place, see the Restic documentation.
Other Restic commands
Any Restic command can be run through the backup service by appending it
to docker compose run --rm backup-dir0. For example, to run a backup
immediately or to check the integrity of the repository:
docker compose run --rm backup-dir0 backup /backup0
docker compose run --rm backup-dir0 check
Renew the OIDC refresh token
The access tokens and S3 credentials used by the backup jobs are short-lived and are refreshed automatically by the scheduled jobs. To do so, oidc-agent uses an OIDC refresh token, which expires after 30 days and must be renewed manually.
To help you remember, the check-refresh-token job scheduled by ofelia
monitors the refresh token expiration. When it is one week away, the job
reports the corresponding Healthchecks check as "down", including the
number of remaining days in the body of the request.
If the refresh token expires, all the scheduled jobs will stop working. Before that happens, renew it by running the following command:
docker compose run --rm oidc-agent reauth
Then follow the instructions on the screen to complete the authentication.
Manual setup
If you are interested in manually setting up the backup solution without using the provided Docker setup, you can follow the instructions in this section. The manual setup is more flexible and allows you to customize the solution to better suit your needs, but it requires more technical knowledge and effort to set up and maintain. You should only follow this section if you are comfortable with the command line and have experience with the tools involved in the backup process.
Warning
In this section we will assume that you are a system administrator with root access in the VM where you have to run the backup software, as some packages need to be installed system-wide. If you are not the system administrator, you can ask for help to the system administrator of the VM.
Install and configure oidc-agent
To install and configure oidc-agent you can follow the instructions in the oidc-agent documentation. If your VM is running AlmaLinux9, you can already find the updated oidc-agent in the EPEL repositories and install it with the following command:
sudo dnf install oidc-agent-cli
If you are using Debian or Ubuntu, as described in the official documentation, you need to configure an additional repository that can be found here
After installing oidc-agent, you need to export and load the environment variables by executing the following commands:
eval `oidc-agent-service use`
To make it persistent, you can add the command to your .bashrc file, running the following while in your home directory:
echo 'eval `oidc-agent-service use`' >> .bashrc
With oidc-agent version >= 5, the legacy audience mode must be
explicitly enabled by creating the file
~/.oidc-agent/issuer.config.d/infn-cloud with the following content:
{
"issuer": "https://iam.cloud.infn.it/",
"register": "https://iam.cloud.infn.it/manage/dev/dynreg",
"legacy_aud_mode": true
}
We can now configure oidc-agent to work with the INFN Cloud IAM in order to issue the necessary tokens to access the object storage. To do this, we need to create a new profile in oidc-agent by executing the following commands:
oidc-gen -w device --iss=https://iam.cloud.infn.it/ \
--scope="openid profile offline_access" --aud=object infn-cloud
Follow the instructions on the screen to complete the process.
You will be asked to open a link to the INFN Cloud IAM in your browser, login with your account (if needed) and enter the code provided (in the following is redacted with XXXXXX for security reasons) to approve the registered client. If your terminal view is too short it may be hidden by the QR code appended at the end of the output, you can simply scroll up to see the code.
Registering Client ...
Generating account configuration ...
accepted
Using a browser on any device, visit:
https://iam.cloud.infn.it/device
And enter the code: XXXXXX
Once you complete the authentication via the browser, the profile will be created and you will be asked to set a password to secure it.
If you do so, you will need to provide it each time you want to obtain a
token for this profile. The best way of doing this is by either setting
an environment variable or add the password to a text file and pass them
as an argument of the oidc command line with the --pw-env or
--pw-file option, respectively.
The configuration of oidc-agent should be now complete, you can check the profile by executing the following command:
oidc-token infn-cloud
If the command returns a token, the configuration is correct and you can proceed, if the command returns an error instead, please check the previous steps and try again.
Configure S3 credentials
To interact with the INFN Cloud object storage, we need to obtain temporary S3 credentials using the OIDC token obtained with oidc-agent. To simplify this process, INFN Cloud provides a helper script that will take care of this for you.
Make sure you have the jq and xq tools installed, as they are required
by the script for parsing JSON and XML responses. Then download the script
and save it as $HOME/.local/bin/s3-cred-oidc.sh:
mkdir -p $HOME/.local/bin
curl -fsSL -o $HOME/.local/bin/s3-cred-oidc.sh \
https://baltig.infn.it/infn-cloud/user-support-scripts/-/raw/main/obj-storage/s3-cred-oidc.sh
Finally, make the script executable:
chmod +x $HOME/.local/bin/s3-cred-oidc.sh
You can read more about the script usage and parameters running the
script with the --help flag:
$HOME/.local/bin/s3-cred-oidc.sh --help
To configure any S3 compatible tool to use the helper script to obtain
the temporary credentials, you need to create or modify
the $HOME/.aws/config file with the following content, replacing
<user> with your username (the path must be absolute, as environment
variables such as $HOME are not expanded by all tools):
[default]
credential_process = /home/<user>/.local/bin/s3-cred-oidc.sh --oidc-profile infn-cloud
endpoint_url = https://rgw.cloud.infn.it
; On some systems you may need to also specify the following:
; request_checksum_calculation = when_required
; response_checksum_validation = when_required
If you have set up a password when creating the oidc-agent profile, you have to manually load the profile after each reboot:
oidc-add infn-cloud
Since backups usually run unattended (e.g. via cron), you can instead store the password in a file readable only by you and pass it to the script, so that the profile is loaded automatically:
mkdir -p ~/.backup-secrets && chmod 700 ~/.backup-secrets
read -rsp "oidc-agent profile password: " pw && echo && \
printf '%s\n' "$pw" > ~/.backup-secrets/oidc-pass && unset pw
chmod 600 ~/.backup-secrets/oidc-pass
[default]
credential_process = /home/<user>/.local/bin/s3-cred-oidc.sh --oidc-profile infn-cloud --oidc-pw-file /home/<user>/.backup-secrets/oidc-pass
endpoint_url = https://rgw.cloud.infn.it
Configure and run Restic
Restic is a fast, secure, and efficient backup program that supports deduplication, encryption, and data integrity verification. It is designed to be easy to use and to work well with cloud storage providers.
To install Restic, we suggest you to use the package manager of your distribution of choice. For Ubuntu, you can use the following commands to download and install Restic:
sudo apt update
sudo apt install restic
For other distributions, you can find more detailed installation instructions in the official Restic documentation.
To initialize a new repository with Restic, you can use the following command:
restic -r s3://<username>/<dir-name> init
Where <username> is your INFN AAI username, which is the name of your
personal bucket in the object storage, and <dir-name> is the
name of a directory of your choice inside your bucket. This will create a new
Restic repository in the specified directory. You will be prompted to enter a
password for encrypting the repository. Make sure to remember this password,
as you will need it to access the data in the future.
To avoid being prompted for the password each time you run a Restic
command, you can store it in a file readable only by you and point the
RESTIC_PASSWORD_FILE environment variable to it. In this guide we keep
all the backup secrets in the ~/.backup-secrets directory. The
RESTIC_REPOSITORY variable defines the repository path.
mkdir -p ~/.backup-secrets && chmod 700 ~/.backup-secrets
read -rsp "Restic repository password: " pw && echo && \
printf '%s\n' "$pw" > ~/.backup-secrets/restic-pass && unset pw
chmod 600 ~/.backup-secrets/restic-pass
export RESTIC_PASSWORD_FILE="$HOME/.backup-secrets/restic-pass"
export RESTIC_REPOSITORY="s3://<username>/<dir-name>"
To create a backup with Restic, you can use the following command:
restic backup /path/to/be/backed-up
Where /path/to/be/backed-up is the local directory you want to backup.
Restic will create a backup of the specified directory in the
repository.
To list the snapshots in the repository, you can use the following command:
restic snapshots
To restore a backup, you can use the following command:
restic restore <snapshot_id> --target /path/where/to/restore
Where <snapshot_id> is the ID of the snapshot you want
to restore, and /path/where/to/restore is the directory
where you want to restore the backup. Restic will then restore the
specified snapshot to the specified directory.
See Restore a backup for a step-by-step guide on what you need and how to restore your data.
To verify the integrity of the repository, you can run:
restic check
We recommend running it periodically and, from time to time, restoring a snapshot to a temporary directory to make sure that your backups can actually be recovered.
Restic keeps every snapshot until you explicitly remove it. To limit the space used in your bucket, you can define a retention policy and delete the snapshots that don't match it, for example:
restic forget --keep-daily 7 --keep-weekly 4 --keep-monthly 6 --prune
This keeps the last 7 daily, 4 weekly and 6 monthly snapshots, removes the others and frees the space they used. Adapt the policy to your needs; see the Restic documentation for all the available options.
For more information on the available commands and options, you can consult the Restic documentation.
Configure and run Duplicity
Duplicity is a software suite that provides encrypted, digitally signed, versioned, local or remote backup of files. On the first run it performs a complete (full) backup, then any subsequent (incremental) backups only add differences from the latest version of the backup. Duplicity can use GPG to encrypt the backups and supports a wide range of backends, including an S3 object storage like the INFN Cloud one.
There are multiple ways to install Duplicity, depending on the distribution you are using. For Ubuntu, you can use the official PPA and install it with the following commands:
sudo add-apt-repository ppa:duplicity-team/duplicity-release-git
sudo apt update
sudo apt install duplicity
For other distributions, you can either download the official release from the official GitHub repository and manually install it, or use the pip package manager:
pip3 install -U duplicity
You can find more detailed installation instructions in the "Download" section of the official Duplicity documentation.
To use the INFN Cloud object storage, Duplicity needs the boto3 Python
library (version 1.28 or later). Install it with your package manager
(e.g. sudo apt install python3-boto3) or, if you installed Duplicity
with pip, with:
pip3 install -U boto3
We will now assume that you can successfully run the duplicity command
in your terminal.
To execute a backup with Duplicity, using the INFN Cloud object storage as a backend, you can simply run the following command:
duplicity /path/to/be/backed-up s3://<username>/<dir-name>
Where /path/to/be/backed-up is the path of the directory you want to
put under backup, <username> is your INFN AAI username (or the name of
the bucket in the object storage). The command will create a full backup
of the directory on the first run, and then only the differences on
subsequent runs. Duplicity can also encrypt and sign the backup with a
GPG key, you just need to provide the ID of the key. You can create a
new GPG key by running the following command:
gpg --gen-key
and follow the on screen prompts. After the key is generated, you can list the keys with the following command:
gpg --list-keys --keyid-format LONG
The output will look like this:
/home/$USER/.gnupg/pubring.kbx
----------------------------------
pub rsa3072/KEY_ID 2024-03-15 [SC] [expires: 2026-03-15]
uid [ultimate] Name <email address>
sub rsa3072/SUBKEY_ID 2024-03-15 [E] [expires: 2026-03-15]
The ID we need is the one of the primary key, on the pub line, reported
here as KEY_ID. You can now use this key to encrypt and sign the backup by
adding the proper option to the duplicity command:
read -rsp "GPG key passphrase: " PASSPHRASE && echo && export PASSPHRASE
duplicity --encrypt-sign-key=KEY_ID /path/to/be/backed-up s3://<username>/<dir-name>
Duplicity reads the passphrase of the GPG key used to encrypt and sign
the backup from the PASSPHRASE environment variable. Reading it with
read -s avoids writing it in your shell history. Remember to
unset PASSPHRASE when you are done.
If you want to restore the backup, you can use the following command:
duplicity restore s3://<username>/<dir-name> /path/where/to/restore
This will restore the latest version of the backup to
/path/where/to/restore. If you want to restore a specific version of
the backup, you can add the --time option followed by the date of the
backup you want to restore:
duplicity restore --time 1D s3://<username>/<dir-name> /path/where/to/restore
This will restore the backup as it was 1 day ago. You can also use other
time formats, such as 1W for 1 week ago, 1M for 1 month ago, etc. You
can also use the --force option to force the restore, even if the
target directory already exists, overwriting its content. For more
information on the available options, you can consult the Duplicity
documentation.
We recommend periodically restoring the backup to a temporary directory,
or running duplicity verify s3://<username>/<dir-name>
/path/to/be/backed-up, to make sure that your backups can actually be
recovered.
Bash script to automate the backup process
To automate the backup process, you can create a bash script that runs the Duplicity command and then schedule it with cron. Create a new file, we will call it backup.sh in this example, in your home directory.
The script needs the oidc-agent profile password, stored in
~/.backup-secrets/oidc-pass as described in
Configure S3 credentials, and the GPG key
passphrase. Store the passphrase in the same directory, so that it is
readable only by you and not written in the script:
read -rsp "GPG key passphrase: " pw && echo && \
printf '%s\n' "$pw" > ~/.backup-secrets/gpg-pass && unset pw
chmod 600 ~/.backup-secrets/gpg-pass
Then create backup.sh with the following content:
#!/bin/bash
eval `oidc-agent-service use`
oidc-add --pw-file="$HOME/.backup-secrets/oidc-pass" infn-cloud
DIR_TO_BACKUP="/path/to/be/backed-up"
DEST_BUCKET="s3://<username>/<dir-name>"
LOG_DIR="$HOME/.duplicity"
GPG_KEY="YOUR_KEY_ID"
export PASSPHRASE="$(cat "$HOME/.backup-secrets/gpg-pass")"
mkdir -p "$LOG_DIR"
duplicity --verbosity info --encrypt-sign-key="$GPG_KEY" \
--log-file "$LOG_DIR/info.log" \
--full-if-older-than=1M "$DIR_TO_BACKUP" "$DEST_BUCKET"
duplicity --force remove-all-but-n-full 3 "$DEST_BUCKET"
unset PASSPHRASE
Important
Please remember to edit the script, using your own values for the defined variables, before running it.
The script will run the Duplicity command to create a full backup of the directory on first run and every time the last backup is older than one month and then remove all but the last 3 full backups.
You can make the script executable by running the following command:
chmod 700 ~/backup.sh
To schedule the backup, you can use the cron daemon. You can edit the crontab file by running the following command:
crontab -e
This will open the crontab file in your default text editor. You can then add a new line with the following content:
0 4 * * * $HOME/backup.sh
This will run the backup script every day at 4:00 AM. You can customize the schedule by changing the values in the first five fields. For more information on the crontab syntax, you can consult the cron documentation.