Use the AWS Key Management Service with Debusine

Debusine supports the AWS Key Management Service (KMS) for OpenPGP keys used to sign Debian repository indexes.

To set this up, you must have an IAM user in AWS for use by signing workers, with at least the following permissions:

  • kms:CreateKey

  • kms:Describe*

  • kms:Get*

  • kms:Put*

  • kms:ScheduleKeyDeletion

  • kms:Sign

  • kms:TagResource

If you only intend to use pre-generated keys and will never have Debusine generate keys in KMS by itself, then signing workers only need the kms:Sign permission.

You should then create a YAML file with cloud provider account information, stored in /etc/debusine/signing/aws-account.yaml on your signing worker(s). This file must be owned by the debusine-signing user and have mode 0600. For example:

provider_type: aws
name: debusine-kms
configuration:
  region_name: eu-west-2
  kms_endpoint_url: https://kms.eu-west-2.amazonaws.com/
credentials:
  access_key_id: <access key ID>
  secret_access_key: <secret access key>

Debusine currently leaves all keys it generates with the default key policy, allowing the AWS account that the signing worker uses to have full access to all keys it generates. If you need something finer-grained, you may override this manually for individual keys using the AWS console or the AWS CLI; see Best practices for IAM policies.

At the time of writing, AWS charges $1/month for each key pair. Since Debusine instances can have a large number of archives in ephemeral workspaces each of which has its own signing key, generating all keys in KMS could become quite expensive. To avoid this, keys are stored using software encryption by default, and an archive must have its signing_key_storage field set to aws-kms to generate a key in KMS.

For simplicity and compatibility, Debusine currently only supports Ed25519 repository keys.

Tags

Debusine sets a number of tags on the keys it generates. These are not currently used by Debusine itself, but may be useful for access control and cost tracking.

  • DebusineScope: the name of the scope for which the key was generated

  • DebusineWorkspace: the name of the workspace for which the key was generated

  • PGPName: a human-readable description of the key, used in its OpenPGP user ID

In addition, any tags in the DEBUSINE_SIGNING_AWS_KMS_TAGS Django setting are applied to all generated keys. When running from Debian packages of Debusine, this defaults to {"DebusineInstance": "production"}.

Move an existing Debusine signing key into KMS

Importing keys that were generated outside KMS may be useful for the reasons outlined in the AWS documentation on importing key material.

If the key is already stored in Debusine using software encryption, then you can use the debusine-signing move_key command to move it into KMS. You will need at least the following additional IAM permissions:

  • kms:GetParametersForImport

  • kms:ImportKeyMaterial

For example, on a signing worker:

$ sudo debusine-signing move_key openpgp-repository KEY-FINGERPRINT aws-kms

KMS does not allow exporting private key material, so this operation is irreversible. The command will prompt you for confirmation, unless you use the --yes option.

Register an existing key not already managed by Debusine

If the key was not stored in Debusine, then you can still import it manually.

As above, you will need at least the following additional IAM permissions:

  • kms:GetParametersForImport

  • kms:ImportKeyMaterial

The following Python script may be used to extract the raw key material from an OpenPGP Ed25519 private key and export it in DER-encoded format so that it can be imported into KMS. You will need to have the python3-cryptography, python3-pgpy, and sq packages installed.

#! /usr/bin/python3

import subprocess
import sys
from argparse import ArgumentParser
from pathlib import Path

from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import ed25519
from pgpy.pgp import PGPKey  # type: ignore[import-untyped]

parser = ArgumentParser()
parser.add_argument(
    "fingerprint",
    help=(
        "Export the private key of the OpenPGP certificate with this"
        " fingerprint"
    ),
)
parser.add_argument(
    "output_file",
    type=Path,
    help="Write DER-encoded PKCS#8 output to this file",
)
args = parser.parse_args()

export = subprocess.run(
    ["sq", "key", "export", "--cert", args.fingerprint],
    capture_output=True,
    check=True,
)
key, _ = PGPKey.from_blob(export.stdout)
# `sq key export` should export the key without a passphrase.
assert not key.is_protected
private_key = key.__key__.__privkey__()
if not isinstance(private_key, ed25519.Ed25519PrivateKey):
    print(
        "Debusine currently only handles Ed25519 private keys.",
        file=sys.stderr,
    )
    sys.exit(3)
args.output_file.write_bytes(
    private_key.private_bytes(
        encoding=serialization.Encoding.DER,
        format=serialization.PrivateFormat.PKCS8,
        encryption_algorithm=serialization.NoEncryption(),
    )
)

Once you have the raw private key material:

  1. Follow the instructions in Create a KMS key with imported key material to import the key in question. When reaching the corresponding steps of those instructions, you should use the --origin EXTERNAL --key-spec ECC_NIST_EDWARDS25519 --key-usage SIGN_VERIFY options to aws kms create-key, the --wrapping-algorithm RSA_AES_KEY_WRAP_SHA_256 --wrapping-key-spec RSA_4096 options to aws kms get-parameters-for-import, and the --expiration-model KEY_MATERIAL_DOES_NOT_EXPIRE option to aws kms import-key-material (since Debusine should control expiry).

  2. Register the key with debusine-signing as follows, where KEY-ID is the key ID given in the output of the aws kms create-key command:

    $ sudo debusine-signing register_aws_kms_key \
        openpgp-repository KEY-ID 'some description of the new key'
    
  3. Create a debusine:signing-key corresponding to the new key, replacing KEY-DATA by the data printed by the previous command:

    $ debusine asset create --workspace WORKSPACE --data - \
        debusine:signing-key <<END
    KEY-DATA
    END
    
  4. Set the signing_keys field of the archive that should use this key. If the output of debusine-signing register_aws_kms_key included fingerprint: KEY-FINGERPRINT, then set signing_keys: [KEY-FINGERPRINT].

Manually generate a key in KMS

You may generate a key manually in KMS if you need more control than Debusine gives you. Use aws kms create-key --key-spec ECC_NIST_EDWARDS25519 --key-usage SIGN_VERIFY together with whatever other options you need. Then follow the steps in the previous section, starting from “Register the key with debusine-signing”.