How to build a custom Debian repository on AWS S3 with Ubuntu 20.04 and reprepo

Introduction

When it comes to installing a program on Ubuntu or other Debian-based Linux distros – what should we do normally? Most of us just run e.g. apt-get install ntp to get the package installed.

It may seem like magic, but it’s not. The package manager apt-get is responsible for searching, downloading, and installing packages for you. This process is incredibly convenient. However, what if apt-get cannot locate the program you require in its default repositories? Fortunately, apt-get permits users to define custom download sources known as repositories.

In this blog, we will find out how to set up your own secure repository and make it public for others to use. To achieve this, we’ll create the repository on Ubuntu 20.04 LTS Focal Fossa and push it to be stored on AWS S3. Then, we’ll be testing the download from another Ubuntu with the same distribution.

Prerequisites

Servers: 2 x Ubuntu 20.04 LTS Focal Fossa

Here are the 2 main parts we’ll have to go through:

Part 1 – Prepare and publish a repository signing key

First, a valid package signing key is essential. This is a critical requirement for establishing a secure repository, as it involves digitally signing all the packages. The act of signing packages instils confidence in downloaders so that they know the source can be trusted.

In this section, we will generate an encrypted master public key and a signing subkey by following these sub-steps:

  • Install GPG (default is pre-installed)
  • Generate a Master Key
  • Generate a Subkey for Package Signing
  • Detach Master Key from Subkey

1. Install GPG

Before starting gpg installation, please access your Ubuntu server and switch to “root” user

sudo su

GPG is pre-installed on most Linux distributions, though you can run the following commands to install it

apt install gpg

gpg is pre-installed with version 2.2 in Ubuntu 20.04, we can check by:

root@lab-pc:~# gpg --version
gpg (GnuPG) 2.2.19
libgcrypt 1.8.5
Copyright (C) 2019 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

Home: /root/.gnupg
Supported algorithms:
Pubkey: RSA, ELG, DSA, ECDH, ECDSA, EDDSA
Cipher: IDEA, 3DES, CAST5, BLOWFISH, AES, AES192, AES256, TWOFISH,
        CAMELLIA128, CAMELLIA192, CAMELLIA256
Hash: SHA1, RIPEMD160, SHA256, SHA384, SHA512, SHA224
Compression: Uncompressed, ZIP, ZLIB, BZIP2

2. Generate a Master Key

Invoking command gpg as below, you’ll see a prompt looks like:

gpg --full-generate-key
gpg (GnuPG) 2.2.19; Copyright (C) 2019 Free Software Foundation, Inc.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

Please select what kind of key you want:
   (1) RSA and RSA (default)
   (2) DSA and Elgamal
   (3) DSA (sign only)
   (4) RSA (sign only)
  (14) Existing key from card
Your selection? 1

Specify the first option: “(1) RSA and RSA (default)” in the prompt. Selecting this option, gpg will generate first a signing key, then a encryption subkey (both using the RSA algorithm). However, we don’t need an encryption key for this blog, but having both doesn’t take you anything, and you may use the key for encryption in the future.

Hit Enter and you’ll be prompted to enter a keysize:

RSA keys may be between 1024 and 4096 bits long.
What keysize do you want? (3072) 4096
Requested keysize is 4096 bits

The key size correlates directly to how secure you want your master key to be. The higher the bit size, the more secure key. The Debian project recommends using 4096 bits for any signing key, so we would specify 4096 here.

Hit Enter to continue.

Please specify how long the key should be valid.
         0 = key does not expire
      <n>  = key expires in n days
      <n>w = key expires in n weeks
      <n>m = key expires in n months
      <n>y = key expires in n years
Key is valid for? (0) 0

Master keys do not normally have expiration dates, but set this value as long as you expect to use this key. If you only plan to use this repository for only the next 6 months you can specify 6m0 will make it valid forever.

Hit Enter, then y to confirm

Key does not expire at all
Is this correct? (y/N)

Then, you will be prompted to generate a “user ID”. This information will be used by others and yourself to identify this key – so better you should use a real information!

GnuPG needs to construct a user ID to identify your key.

Real name: Binh Do
Email address: binh.do@example.com
Comment: A Custom APT Repository
You selected this USER-ID:
    "Binh Do (A Custom APT Repository) <binh.do@example.com>"

Change (N)ame, (C)omment, (E)mail or (O)kay/(Q)uit? o

After you fill out your information, hit o and Enter. You will be prompted again to ask for entering a password that only you have access to this key. In this case, I don’t use passphrase so have to put it empty because it will ask every time we add a package by using this master key. But if you need more secure, you can enter your password here. The prompt looks like

After hit Yes, protection is not needed, it might take a little while to generate your keys. The result should looks like

We need to generate a lot of random bytes. It is a good idea to perform
some other action (type on the keyboard, move the mouse, utilize the
disks) during the prime generation; this gives the random number
generator a better chance to gain enough entropy.
We need to generate a lot of random bytes. It is a good idea to perform
some other action (type on the keyboard, move the mouse, utilize the
disks) during the prime generation; this gives the random number
generator a better chance to gain enough entropy.
gpg: /root/.gnupg/trustdb.gpg: trustdb created
gpg: key 19FE4816082F9846 marked as ultimately trusted
gpg: directory '/root/.gnupg/openpgp-revocs.d' created
gpg: revocation certificate stored as '/root/.gnupg/openpgp-revocs.d/78E56DC93C8C74401647FBBB19FE4816082F9846.rev'
public and secret key created and signed.

pub   rsa4096 2023-10-28 [SC]
      78E56DC93C8C74401647FBBB19FE4816082F9846
uid                      Binh Do (A Custom APT Repository) <binh.do@example.com>
sub   rsa4096 2023-10-28 [E]

Now we have a master key created. The output shows that we created a master key for signing (look at the highlighted above – the example here is 19FE4816082F9846). Your key will have different IDs. Make note of your signing key’s ID. We’ll need that information in the next steps when creating another subkey for signing.

3. Generate a Subkey for Package Signing

Now we’ll create a second signing key so that we don’t need the master key on this server. Think of the master key as the root authority that gives authority to subkeys. If a user trusts the master key, trust in a subkey is implied.

Run this command to edit (Replace the example ID with your key’s ID):

gpg --edit-key 19FE4816082F9846

We’ll go into the gpg environment. Here we can edit our new key and add a subkey. The following out should look like

gpg (GnuPG) 2.2.19; Copyright (C) 2019 Free Software Foundation, Inc.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

Secret key is available.

gpg: checking the trustdb
gpg: marginals needed: 3  completes needed: 1  trust model: pgp
gpg: depth: 0  valid:   1  signed:   0  trust: 0-, 0q, 0n, 0m, 0f, 1u
sec  rsa4096/19FE4816082F9846
     created: 2023-10-28  expires: never       usage: SC  
     trust: ultimate      validity: ultimate
ssb  rsa4096/90E2B81B82545D37
     created: 2023-10-28  expires: never       usage: E   
[ultimate] (1). Binh Do (A Custom APT Repository) <binh.do@example.com>

gpg>

At the prompt, type addkey:

gpg> addkey

Hit Enter. If you have entered your passphrase at the above step, it will prompt you for entering password, just type your password. But I didn’t create password in this guide, so the following output looks like:

Please select what kind of key you want:
   (3) DSA (sign only)
   (4) RSA (sign only)
   (5) Elgamal (encrypt only)
   (6) RSA (encrypt only)
  (14) Existing key from card
Your selection? 4

We want to create a signing subkey, so select “RSA (sign only)” 4. RSA is faster for the client, while DSA is faster for the server. We’re picking RSA in this case because, for every signature that we make on a package, possibly hundreds of clients will need to verify it. The two types are equally secure.

Another prompt for a key size again.

RSA keys may be between 1024 and 4096 bits long.
What keysize do you want? (3072) 4096

Choose 4096 and hit Enter. A prompt asks for the expiration period of this signing key

Please specify how long the key should be valid.
         0 = key does not expire
      <n>  = key expires in n days
      <n>w = key expires in n weeks
      <n>m = key expires in n months
      <n>y = key expires in n years
Key is valid for? (0) 0

I enter 0 here to make it easy. As we had the master key already, so 1y expire should be a good time frame that you could choose to enter

Hit Enter, and then type y (yes) twice for the next two prompts:

Key does not expire at all
Is this correct? (y/N) y
Really create? (y/N) y

It will ask to create a password for this signing key (similar to creating the master key). Thus, I enter an empty password. Then wait for the signing key to be generated.

We need to generate a lot of random bytes. It is a good idea to perform
some other action (type on the keyboard, move the mouse, utilize the
disks) during the prime generation; this gives the random number
generator a better chance to gain enough entropy.

sec  rsa4096/19FE4816082F9846
     created: 2023-10-28  expires: never       usage: SC  
     trust: ultimate      validity: ultimate
ssb  rsa4096/90E2B81B82545D37
     created: 2023-10-28  expires: never       usage: E   
ssb  rsa4096/5A7B560EB84593AA
     created: 2023-10-28  expires: never       usage: S   
[ultimate] (1). Binh Do (A Custom APT Repository) <binh.do@example.com>

gpg> 

Type save at the prompt:

gpg> save

In the output above, we will see:

  • SC: from our master key tell us that the key is only for signing and certification
  • E: means the key we only use for encryption
  • S: is the signing key that we just created.

We have to note down our new signing key’s ID (in this example is 5A7B560EB84593AA)

4. Detach Master Key From Subkey

The main idea of creating a subkey is that we don’t need the master key on our server, which makes it more secure. Thus, we’ll need to export master and subkey (our created signing key), then deleles all keys from GPG’s storage. Finally, we re-import just the subkey only.

We use the –export-secret-key and –export commands to export the whole key (remember to use your master key’s ID which is 19FE4816082F9846 in this example)

gpg --export-secret-key 19FE4816082F9846 > private.key
gpg --export 19FE4816082F9846 >> private.key

NOTE: Make a copy of the private.key file and save it somewhere else (not on your server). This file contains your private key, your public key, your encryption subkey, and your signing subkey.

After you have backed up this file to a safe location, delete the private.key file:

Now we export your public key and your subkey. For exporting your subkey, make sure you change the key ID to your created signing key above (which is 5A7B560EB84593AA in this example)

gpg --export 19FE4816082F9846 > public.key
gpg --export-secret-subkeys 5A7B560EB84593AA > signing.key

Now that we have a backup of our private.key, we can remove our master key from our server.

gpg --delete-secret-key 19FE4816082F9846

Press y twice to confirm to delete the master key, an output as below shows up and a prompt pops up to ask you to confirm

gpg (GnuPG) 2.2.19; Copyright (C) 2019 Free Software Foundation, Inc.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.


sec  rsa4096/19FE4816082F9846 2023-10-28 Binh Do (A Custom APT Repository) <binh.do@example.com>

Delete this key from the keyring? (y/N) y
This is a secret key! - really delete? (y/N) y

Hit Enter to delete the key

Re-import the public key and signing key only

gpg --import public.key signing.key

An output shows up similar to the following:

gpg: key 19FE4816082F9846: "Binh Do (A Custom APT Repository) <binh.do@example.com>" not changed
gpg: key 19FE4816082F9846: "Binh Do (A Custom APT Repository) <binh.do@example.com>" not changed
gpg: To migrate 'secring.gpg', with each smartcard, run: gpg --card-status
gpg: key 19FE4816082F9846: secret key imported
gpg: Total number processed: 2
gpg:              unchanged: 2
gpg:       secret keys read: 1
gpg:   secret keys imported: 1

Check to verify that we no longer have our master key on our server

gpg --list-secret-keys
/root/.gnupg/pubring.kbx
------------------------
sec#  rsa4096 2023-10-28 [SC]
      78E56DC93C8C74401647FBBB19FE4816082F9846
uid           [ultimate] Binh Do (A Custom APT Repository) <binh.do@example.com>
ssb   rsa4096 2023-10-28 [E]
ssb   rsa4096 2023-10-28 [S]

Notice the # after sec. This means our master key is not installed. The server contains only our signing subkey.


If you want to publish your signing key to any remote keyserver example like keyserver.ubuntu.com, you’ll need to run this command. But if you want to deploy your own repository to AWS S3, you can skip this part.

  1. Publish your signing key
gpg --keyserver keyserver.ubuntu.com --send-key 19FE4816082F9846

Result is:

gpg: sending key 19FE4816082F9846 to hkp://keyserver.ubuntu.com

Done! Now we need to save the signing.key somewhere else again (similar to private.key). We need these keys to register on other servers. Actually, we just need to copy the signing.key file over to the other severs that you want to sign for your own repository. You’ll see the guide in the Part 2

Continue Part 2: Set up a repository by using Reprepro and make the repository public with AWS S3


Discover more from Turn DevOps Easier

Subscribe to get the latest posts sent to your email.

By Binh

Leave a Reply

Your email address will not be published. Required fields are marked *

Content on this page