===============================
Restricting use of signing keys
===============================

User story
==========

A Debusine instance should be able to hold signing keys for use by a
small number of trusted users. No other (non-sysadmin) user on the
Debusine instance should be able to make Debusine take any action with
these keys.

Requirements
============

* Access to signing keys is controlled by ``debusine:signing-key``
  assets. Assets are a new primitive similar to an artifact, but
  containing only JSON data.
* Access can be granted to tasks in a set of Workspaces.
* Access can be restricted to a Group of users.

Security Model
==============

Key Creation:

* Sysadmins can generate HSM-keys, outside Debusine.
* Sysadmins can import HSM keys into Debusine.
* Sysadmins can generate keys in Debusine.
* In the future, we expect key generation (of some sort, but it may as
  well be HSM) to be a routine part of creating a workspace that holds a
  repository. Probably delegated by a workflow, once `#634
  <https://salsa.debian.org/freexian-team/debusine/-/issues/634>`_ has
  landed.

Assets:

* While artifacts may be created by any user with contributor access to
  a workspace, assets can only be created by administrators or certain
  tasks (e.g.  key generation).
* Assets have a category (like artifacts).
* Asset data is immutable (like artifacts).
* Unlike artifacts, which have permissions applied by the containing
  workspace, Assets have permissions directly associated with them, that
  can be altered by their owner.
* Asset's permissions may be tied to workspaces. Roles are defined on an
  (asset, workspace) pair. There may be optional extra JSON restrictions
  attached to a permission for defence-in-depth.

Signing Key Assets:

* Signing key assets are used to manage the security of signing keys.
  They contain the fingerprint, purpose, and public part of the key.
* The ``GenerateKey`` task will create a signing key asset.
* The ``GenerateKey`` task can be directly executed by scope owners.
* The ``GenerateKey`` task may be incorporated in workflows.
* In the future, the execution of the ``GenerateKey`` task may be
  unrestricted.
* The extra restrictions field in a signing key asset can include
  restrictions for any/all of: ``debian:repository``, ``debian:suite``,
  and ``debian:source-package``.
* Assets may be visible in the UI. If they aren't, then the public part
  of the key still needs to be exposed. We expect repositories to have
  UI with instructions for configuring ``sources.list`` including the
  public key. This would be sufficient, no dedicated asset view is
  required.

Signing:

* Debusine's signing service has no concept of permissions, itself.
  Currently, if a user can generate a ``Sign`` task, it will be acted
  on, with the specified private key (by public key fingerprint, as a
  string).
* The ``Sign`` task may be incorporated into workflows. The workflow
  creator can then specify the signing key to be used.
* ``Sign`` tasks should be permitted to execute if the user running the
  task has the ``SIGNER`` role on the asset (via a group membership) in
  the workspace that the task is executing in. If any other
  defence-in-depth restrictions are set, they must be met.
* ``Sign`` tasks should not be dispatched unless the user is permitted
  to use the given signing key.
* The signing service can make API requests back to the Debusine
  server to make permission determinations, as a final check.
* In the future, once we have more comprehensive permissions for
  workflows, we may grant permission to execute a ``Sign`` task if the
  workflow was created and blessed by a user who has permission to use a
  given signing key. See `#634
  <https://salsa.debian.org/freexian-team/debusine/-/issues/634>`_.

Automated Signing:

* When we have APT repositories implemented, it will be necessary for
  ``Sign`` tasks to be triggered periodically, by Debusine, without any
  user linked to the request.
* These Signing Key Assets for these repositories will have a permission
  granting the ``SIGNER`` role to the workspace hosting the repository,
  without any associated group.

Work Requests:

* It's expected that core work request properties are immutable once the
  work request has been created. At least: ``workspace``,
  ``created_by``, ``created_at``, ``task_type``, ``task_name``, and
  ``task_data``.

Implementation
==============

Stage 1: Implement Assets
-------------------------

* Create the ``Asset`` DB model, with the following fields:

  * ``id`` int: unique ID (autogenerated)
  * ``category`` string: an ontology, e.g. ``debusine:signing-key``.
  * ``workspace`` string: owning workspace.
  * ``created_at`` timestamp: creation date.
  * ``data``: JSON data.

* Create the ``debusine:signing-key`` Asset Pydantic model, modelling the
  following ``data``:

  * ``purpose`` string ENUM containing: ``uefi``, or ``openpgp``.
  * ``fingerprint`` string containing the fingerprint for the key,
    calculated using the standard hash algorithm for the type of key.
  * ``public_key`` string containing the public part of the key.
  * ``description`` optional string description.

* Put a constraint on assets that ensures all ``debusine:signing-key``
  assets are unique by purpose and fingerprint.

* Create the ``AssetRole`` DB model with the following fields:

  * ``resource`` Foreign Key to ``Asset``.
  * ``role`` string ENUM containing: ``OWNER``.
  * ``group`` Foreign Key to ``Group``.

* Create the ``AssetUsage`` DB model with the following fields:

  * ``asset`` Foreign Key to ``Asset``.
  * ``workspace`` Foreign Key to ``Workspace``.
  * ``restrictions`` JSON. Optional additional defense-in-depth
    restrictions.

* Create the ``AssetUsageRole`` DB model with the following fields:

  * ``resource`` Foreign Key to ``AssetUsage``.
  * ``role`` string ENUM containing: ``SIGNER``.
  * ``group`` Foreign Key to ``Group``.

* Implement ``AssetQuerySet`` with the following permission filters:

  * ``can_manage_permissions``, checks if the user has access to the
    ``OWNER`` role.

* Implement ``AssetUsageQuerySet`` with the following permission
  filters:

  * ``can_sign_with``, checks if the user has the ability to sign with
    this key in the given workspace.

Stage 2: Migrate to Signing Key Assets
--------------------------------------

* Remove the ``debian:suite-signing-keys`` collection, it's no longer
  needed.
* Create ``debusine:signing-key`` assets in the ``GenerateKey`` task.
* Update documentation for manual key creation.
* Migrate ``debusine:signing-key`` artifacts to create
  ``debusine:signing-key`` assets. Create a group for each asset,
  granting the creator ``OWNER`` rights. Create an ``AssetUsage`` row
  for each asset, granting the OWNER group the ``SIGNER`` role in the workspace
  that used to own the artifact.
* Remove ``debusine:signing-key`` artifacts.

Stage 3: Permissions for Signing Tasks
--------------------------------------

* We implement an API for a worker to query whether it has
  the required permission:
  ``1.0/asset/<str:asset_category>/<str:asset_slug>/<str:permission-name>/``
  ex: ``1.0/asset/debusine:signing-key/uefi:FBE...64/can-sign/``

  Input Data:

    * ``artifact_id``: int (``debusine:signing-input``)
    * ``work_request_id``: int
    * ``workspace_id``: int

  Response Data:

    * ``has_permission``: boolean
    * ``username``: (optional) string (for the audit log)
    * ``resource``: (optional) JSON description of the resource being
      signed (e.g. ``repository``, ``suite``, ``package``) (for the
      audit log)

* We modify the ``Sign`` task to call this API.
* Store identifying data (``username`` and ``resource``) in ``data`` in
  the ``AuditLog`` model.

Related Improvements
--------------------

* We attempt to lock down modification of work request attributes that
  are expected to be immutable. Via the ``save()`` method, or DB
  triggers.

Future Utilization
------------------

* We define data in the ``task-configuration`` collection to instruct
  workflows to use specific keys for each task.
