From b39ad5d9d4820831ad333b2b2e206bc71ab8b47d Mon Sep 17 00:00:00 2001 From: Soren Bleikertz Date: Wed, 13 Nov 2024 11:49:56 +0100 Subject: [PATCH] Add KMS driver guide to 3.x docs --- docs/3.1/_toc.yml | 2 + .../usermanual/kms/kms_driver_guide.rst | 269 ++++++++++++++++++ 2 files changed, 271 insertions(+) create mode 100644 docs/3.1/docs/canton/usermanual/kms/kms_driver_guide.rst diff --git a/docs/3.1/_toc.yml b/docs/3.1/_toc.yml index 8d65eb556..799152ca8 100644 --- a/docs/3.1/_toc.yml +++ b/docs/3.1/_toc.yml @@ -1298,6 +1298,8 @@ subtrees: title: "Participant Node Migration to KMS" - file: canton/usermanual/kms/external_key_storage/manual_kms_key_rotation title: "Manual KMS key rotation" + - file: canton/usermanual/kms/kms_driver_guide + title: "Canton KMS Driver API Developer Guide" - file: canton/usermanual/performance title: "Scaling and Performance" diff --git a/docs/3.1/docs/canton/usermanual/kms/kms_driver_guide.rst b/docs/3.1/docs/canton/usermanual/kms/kms_driver_guide.rst new file mode 100644 index 000000000..d62c1336e --- /dev/null +++ b/docs/3.1/docs/canton/usermanual/kms/kms_driver_guide.rst @@ -0,0 +1,269 @@ +.. + Copyright (c) 2023 Digital Asset (Switzerland) GmbH and/or its affiliates. +.. + Proprietary code. All rights reserved. + +.. _kms_driver_guide: + +Canton KMS Driver Developer Guide +================================= + +.. enterprise-only:: + + +Introduction +------------ + +The Canton protocol relies on a number of cryptographic operations such as +asymmetric encryption and digital signatures. To maximize the +operational security of a Canton node the corresponding private keys should not +be stored or processed in cleartext. A Key Management System (KMS) or Hardware +Security Module (HSM) allows us to perform such cryptographic operations where +the private key resides securely inside the KMS/HSM. All nodes in Canton can +make use of a KMS. + +AWS KMS and Google Cloud KMS are supported as of Canton v2.7. To broaden the +support of other KMSs and HSMs, Canton v2.9 introduces a plugin approach, called +KMS Drivers, which allows the implementation of custom integrations. This +document explains the APIs that must be implemented for a KMS Driver, +provides a guide on the implementation, and describes how to configure Canton to +run with a KMS driver. An implementation needs to be developed for the JVM, +currently only Scala is supported. + +KMS Driver API +-------------- + +The two main APIs that are required for a KMS Driver are: + +1. Driver Factory: Implements how a driver is instantiated and the main entry + point for Canton to load a driver. + +2. KMS Driver: The actual KMS driver API that offers cryptographic operations + based on the KMS. + +The stable APIs are versioned with a single major version number. A breaking +change to either the factory or driver APIs will result in a new major version +of those APIs. The current and only version is **v1**, which is part of the +module path for the respective API interfaces. + +KMS Driver Factory API v1 +~~~~~~~~~~~~~~~~~~~~~~~~~ + +The factory consists of two interfaces: a generic v1 DriverFactory and a +specific v1 KmsDriverFactory. + +The ``v1.DriverFactory`` defines the following aspects for a generic driver: + +- The type of the driver +- A name that uniquely identifies the driver +- The version of the API the driver implements and optional build + information (driver version number or commit hash) +- A driver-specific configuration object with configuration parser and + writer +- A create method that instantiates a driver with that factory + +Concretely the interface is defined as the following in Scala: + +.. literalinclude:: /canton/includes/mirrored/enterprise/kms-driver-api/src/main/scala/com/digitalasset/canton/driver/api/v1/DriverFactory.scala + :language: Scala + + +A specialization of the generic DriverFactory is the ``v1.KmsDriverFactory`` which +defines the driver type to be a KmsDriver and the API version to be 1. + +.. literalinclude:: /canton/includes/mirrored/enterprise/kms-driver-api/src/main/scala/com/digitalasset/canton/crypto/kms/driver/api/v1/KmsDriverFactory.scala + :language: Scala + +Driver Configuration Reading & Parsing +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Canton uses `pureconfig `__ configuration library +to read its configuration. Given that the configuration of the driver can be +embedded inside a Canton configuration file, the factory of the driver needs to +specify the type of configuration, as well as configuration readers and writers. +A ``ConfigType`` can be a case class or as basic as a ``Map[String, String]``. +In the latter case, the config reader and writer can be defined as: + +- ``val configReader = pureconfig.ConfigReader.mapReader[String]`` +- ``val configWriter = pureconfig.ConfigWriter.mapWriter[String]`` + +KMS Driver API v1 +~~~~~~~~~~~~~~~~~ + +The main part of the API is the ``v1.KmsDriver`` API that defines the following +operations for a KMS Driver: + +- Key Generation: Asymmetric signing and encryption key pairs as well + as symmetric encryption keys +- Supported Key and Algorithm Specifications: The specs that are + supported by the driver, for example, RSA 2048 keys and RSA OAEP + SHA256 asymmetric encryption. +- Signing: Sign data with a key from the KMS and the specified + algorithm. +- Asymmetric Decryption: Decrypt a ciphertext with a private key from + the KMS and the specified algorithm. +- Symmetric Encryption and Decryption: Encrypt or decrypt with a + symmetric encryption key from the KMS. The default symmetric + encryption algorithm of the KMS is used. +- Key Management: Get the public key of a private key stored in the + KMS, check if a key exists, and delete a key. +- Health: Return the health of the KMS Driver instance. + +The API is designed as an asynchronous API using Futures. An +OpenTelemetry trace context is passed in from Canton into the KMS driver +operations to be able to link a Canton request to operations in the KMS. +The driver is responsible for propagating the trace context into the +KMS. + +Concretely the Scala interface is defined as the following: + +.. literalinclude:: /canton/includes/mirrored/enterprise/kms-driver-api/src/main/scala/com/digitalasset/canton/crypto/kms/driver/api/v1/KmsDriver.scala + :language: Scala + +Error Handling and Health +~~~~~~~~~~~~~~~~~~~~~~~~~ + +In case the driver experiences an error the ``Future`` of the operation should be +failed with a ``KmsDriverException``. When the exception's flag retryable is +true the caller side, i.e., Canton, performs a retry with exponential +backoff. This behavior is suitable for transient errors, such as network issues, +resource exhaustion etc. + +In case of permanent errors, a non-retryable exception should be thrown, which +either fails the current operation from where the cryptographic operation is +called or causes a fatal error in the Canton node. + +The driver should report its health through the health method. A Canton node +periodically queries the health of the driver and reports it as part of the +node's overall health. + +Develop and Test a KMS Driver +----------------------------- + +Set Up API Dependency +~~~~~~~~~~~~~~~~~~~~~ + +The Canton KMS Driver API is published as an artifact on Digital Asset's JFrog +Artifactory: + +https://digitalasset.jfrog.io/ui/repos/tree/General/canton-kms-driver-api + +You must have a Canton enterprise license and account to access the artifact. +You may need to configure your build system to authenticate with a personal +access token towards JFrog Artifactory. + +In your build system of choice, you need to depend on the API as a regular +Maven-style artifact with: + +- organization: com.digitalasset.canton +- artifact: kms-driver-api +- version: the Canton release version, e.g., 2.9.0 + +Implement the API and Build the Driver +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Implement the ``v1.KmsDriverFactory`` by specifying the driver's name, +configuration type with readers/writers, and the create method to instantiate a +driver by creating a new class instance of your ``KmsDriver`` implementation. +Specify the fully qualified name of the factory class in the file: + +``src/main/resources/META-INF/services/com.digitalasset.canton.crypto.kms.driver.api.v1.KmsDriverFactory`` + +and in the following file for the base driver factory (without v1): + +``src/main/resources/META-INF/services/com.digitalasset.canton.crypto.kms.driver.api.KmsDriverFactory`` + +The major part of the implementation is the ``v1.KmsDriver`` that is specific to +the KMS/HSM to be integrated with. The supported key and algorithm +specifications can be defined statically depending on the capabilities of the +underlying KMS/HSM. To ensure the best compatibility with other Canton nodes, +all currently specified key and algorithm specifications should be supported. + +Any credentials required by the underlying KMS/HSM can either be passed +through the Canton configuration file as part of the driver-specific +configuration, where secrets can be resolved from the environment, or retrieved by the driver directly from the environment or any other +driver-specific means. + +Bundle your driver into a self-contained jar, that is, with all required +libraries included in the jar. That way you only need a single driver jar when +starting Canton with your KMS Driver. + +KMS Driver Testing +~~~~~~~~~~~~~~~~~~ + +The reusable test suite for KMS Drivers is published at +`canton-kms-driver-testing +`__. +Configure your build system to depend on this maven artifact in the test scope +of your project (e.g. for sbt append % Test to limit the dependency to the test +scope). + +KmsDriverTest +^^^^^^^^^^^^^ + +The main part of the test suite is the ``KmsDriverTest`` that tests the +functionality of a driver against the ``KmsDriver`` API. + +In the simplest form the specific driver test class extends the +``KmsDriverTest`` and allows the generation of new keys as part of the test: + + +.. literalinclude:: /canton/includes/mirrored/enterprise/aws-kms-driver/src/test/scala/com/digitalasset/canton/nightly/AwsKmsDriverTest.scala + :language: Scala + :start-after: user-manual-entry-begin: AwsKmsDriverTest + :end-before: user-manual-entry-end: AwsKmsDriverTest + +Generating new keys can be expensive when running tests during +development, in particular when using cloud-based KMSs. To mitigate this, the test +suite can also be configured to use predefined keys to test most parts (except +key generation) of the KMS Driver API: + +.. literalinclude:: /canton/includes/mirrored/enterprise/aws-kms-driver/src/test/scala/com/digitalasset/canton/nightly/AwsKmsDriverTest.scala + :language: Scala + :start-after: user-manual-entry-begin: AwsKmsDriverWithPredefinedKeysTest + :end-before: user-manual-entry-end: AwsKmsDriverWithPredefinedKeysTest + + +For each supported signing/encryption key specification an existing key alias/ID +can be configured as part of the predefined keys maps. When running the test +suite the generation of new keys is not allowed. + +KmsDriverFactoryTest +^^^^^^^^^^^^^^^^^^^^ + +The test suite for the KMS driver factory is structured similarly to the above: + +.. literalinclude:: /canton/includes/mirrored/enterprise/aws-kms-driver/src/test/scala/com/digitalasset/canton/nightly/AwsKmsDriverTest.scala + :language: Scala + :start-after: user-manual-entry-begin: AwsKmsDriverFactoryTest + :end-before: user-manual-entry-end: AwsKmsDriverFactoryTest + + +The ``KmsDriverFactory`` can write the driver-specific configuration with a +confidential flag being true, which means any sensitive information in the +configuration such as credentials should be omitted from the written +configuration. A specific test case should be added if your driver-specific +configuration contains any confidential information, asserting that the +sensitive information is omitted. + +Run Canton with a KMS Driver +---------------------------- + +Configure Canton to run with a KMS driver, for example, for a +participant participant1: + +.. literalinclude:: /canton/includes/mirrored/enterprise/app/src/test/resources/aws-kms-driver.conf + :language: none + :start-after: user-manual-entry-begin: AwsKmsDriverConfig + :end-before: user-manual-entry-end: AwsKmsDriverConfig + +Run Canton with your driver jar on its class path: + +``java -cp driver.jar:canton.jar com.digitalasset.canton.CantonEnterpriseApp -c canton.conf # further canton arguments`` + +Where canton.jar depends on the Canton version, e.g., +``lib/canton-enterprise-2.9.0.jar``. The ``canton.conf`` is a configuration file +that needs to configure at least one of the nodes to use the driver KMS as +outlined above. Run a ping for example with +``participant1.health.ping(participant1)`` to validate that the participant can +use the configured KMS and driver.