.. _signing-aws-kms:
================================================
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 :asset:`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:
.. code-block:: yaml
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:
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 :collection:`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:
.. code-block:: console
$ 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.
.. code-block:: python
#! /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:
#. 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).
#. 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:
.. code-block:: console
$ sudo debusine-signing register_aws_kms_key \
openpgp-repository KEY-ID 'some description of the new key'
#. Create a :asset:`debusine:signing-key` corresponding to the new key,
replacing ``KEY-DATA`` by the data printed by the previous command:
.. code-block:: console
$ debusine asset create --workspace WORKSPACE --data - \
debusine:signing-key <` 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``".