Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions UNRELEASED.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,13 @@ The Ledger API update service now exposes a `GetUpdateByHash` endpoint. Given a
- `<canton-node>.replication.connection-pool.connection.client-connection-check-interval` is introduced
that allows configuring the PostgreSQL-specific `client_connection_check_interval` parameter for DB locked connections.
This is a safety mechanism to prevent hanging connections in case of network issues. The default value is 5 seconds.
- GCP KMS configuration gains a new `key-version-overrides` map that lets operators specify a custom
cryptoKey version per cryptoKey id (e.g. `key-version-overrides = { "my-imported-key" = "3" }`).
Previously, Canton always used version `"1"`. Keys not listed in the map continue to default to
version `"1"`, so existing configurations are unchanged. This is useful for cryptoKeys whose key
material was [imported into GCP KMS](https://cloud.google.com/kms/docs/importing-a-key), where each
import creates a new cryptoKey version and multiple versions can be relevant even for asymmetric
keys.
- BREAKING: Removed the `protocolVersion` parameter from all `<node>.topology.<mapping>.list` console commands as it was not working properly.
- *BREAKING*: `kms-driver-api` and `kms-driver-testing` are now published to Maven Central, and will no longer be available in Artifactory.
- Connection pool metrics:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,14 @@ object KmsConfig {
* retry configuration
* @param endpointOverride
* the [optional] endpoint for a proxy to be used by the KMS client.
* @param keyVersionOverrides
* per-cryptoKey overrides for the cryptoKey version that Canton should use when interacting
* with that key. Each map entry is `<cryptoKey-id> -> <version>`. Keys not listed default to
* version `"1"`, which matches how Canton creates new cryptoKeys (it never adds a new
* version to an existing one). This is intended for cryptoKeys whose key material was
* [imported into GCP KMS](https://cloud.google.com/kms/docs/importing-a-key), where each
* import creates a new cryptoKey version and multiple versions can be relevant even for
* asymmetric keys.
*/
final case class Gcp(
locationId: String,
Expand All @@ -115,6 +123,7 @@ object KmsConfig {
auditLogging: Boolean = false,
override val retries: RetryConfig = RetryConfig(),
endpointOverride: Option[String] = None,
keyVersionOverrides: Map[String, String] = Map.empty,
) extends KmsConfig

object Gcp {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,10 +73,27 @@ class GcpKms(

private lazy val loggerKms = new GcpRequestResponseLogger(config.auditLogging, loggerFactory)

/* Identifies the version for all asymmetric GCP keys. Canton always opts to generate a new keys
* rather than adding a new version for that key.
*/
private val gcpKeyversion = "1"
/** Resolves the GCP cryptoKey version Canton should use for the given [[KmsKeyId]]. If the
* key id is listed in [[KmsConfig.Gcp.keyVersionOverrides]], its mapped version is returned;
* otherwise [[GcpKms.defaultGcpKeyVersion]] (`"1"`) is returned, matching Canton's behavior
* of never adding a new version to a cryptoKey it created.
*/
private def keyVersionFor(keyId: KmsKeyId): String =
config.keyVersionOverrides.getOrElse(keyId.unwrap, GcpKms.defaultGcpKeyVersion)

private def cryptoKeyVersionName(keyId: KmsKeyId): gcp.CryptoKeyVersionName =
gcp.CryptoKeyVersionName.of(
config.projectId,
config.locationId,
config.keyRingId,
keyId.unwrap,
keyVersionFor(keyId),
)

private def cryptoKeyName(keyId: KmsKeyId): gcp.CryptoKeyName =
// Symmetric keys: GCP picks the primary version on encrypt and recovers it from the
// ciphertext on decrypt, so no per-key version is needed here.
gcp.CryptoKeyName.of(config.projectId, config.locationId, config.keyRingId, keyId.unwrap)

private val errorMessagesToRetry =
Set(
Expand Down Expand Up @@ -295,14 +312,7 @@ class GcpKms(
ec: ExecutionContext,
tc: TraceContext,
): EitherT[FutureUnlessShutdown, KmsError, gcp.PublicKey] = {
val keyVersionName =
gcp.CryptoKeyVersionName.of(
config.projectId,
config.locationId,
config.keyRingId,
keyId.unwrap,
gcpKeyversion,
)
val keyVersionName = cryptoKeyVersionName(keyId)
loggerKms.withLogging[gcp.PublicKey](
loggerKms.getPublicKeyRequestMsg(keyId.unwrap),
publicKey => loggerKms.getPublicKeyResponseMsg(keyId.unwrap, publicKey.getAlgorithm.name),
Expand Down Expand Up @@ -453,13 +463,7 @@ class GcpKms(
ec: ExecutionContext,
tc: TraceContext,
): EitherT[FutureUnlessShutdown, KmsError, ByteString6144] = {
val keyName =
gcp.CryptoKeyName.of(
config.projectId,
config.locationId,
config.keyRingId,
keyId.unwrap,
)
val keyName = cryptoKeyName(keyId)
val encryptionAlgorithm = CryptoKeyVersionAlgorithm.GOOGLE_SYMMETRIC_ENCRYPTION
for {
dataEnc <- loggerKms.withLogging[ByteString](
Expand Down Expand Up @@ -496,13 +500,7 @@ class GcpKms(
ec: ExecutionContext,
tc: TraceContext,
): EitherT[FutureUnlessShutdown, KmsError, ByteString4096] = {
val keyName =
gcp.CryptoKeyName.of(
config.projectId,
config.locationId,
config.keyRingId,
keyId.unwrap,
)
val keyName = cryptoKeyName(keyId)
val encryptionAlgorithm = CryptoKeyVersionAlgorithm.GOOGLE_SYMMETRIC_ENCRYPTION
for {
dataPlain <- loggerKms.withLogging[ByteString](
Expand Down Expand Up @@ -539,14 +537,7 @@ class GcpKms(
ec: ExecutionContext,
tc: TraceContext,
): EitherT[FutureUnlessShutdown, KmsError, ByteString190] = {
val keyName =
gcp.CryptoKeyVersionName.of(
config.projectId,
config.locationId,
config.keyRingId,
keyId.unwrap,
gcpKeyversion,
)
val keyName = cryptoKeyVersionName(keyId)
for {
encryptionAlgorithm <- convertToGcpAsymmetricEncryptionSpec(encryptionAlgorithmSpec)
.leftMap(err => KmsDecryptError(keyId, err))
Expand Down Expand Up @@ -614,14 +605,7 @@ class GcpKms(
ec: ExecutionContext,
tc: TraceContext,
): EitherT[FutureUnlessShutdown, KmsError, ByteString] = {
val keyVersionName =
gcp.CryptoKeyVersionName.of(
config.projectId,
config.locationId,
config.keyRingId,
keyId.unwrap,
gcpKeyversion,
)
val keyVersionName = cryptoKeyVersionName(keyId)
signingAlgorithmSpec match {
case SigningAlgorithmSpec.EcDsaSha256 =>
signingKeySpec match {
Expand Down Expand Up @@ -677,14 +661,7 @@ class GcpKms(
ec: ExecutionContext,
tc: TraceContext,
): EitherT[FutureUnlessShutdown, KmsError, Unit] = {
val keyVersionName =
gcp.CryptoKeyVersionName.of(
config.projectId,
config.locationId,
config.keyRingId,
keyId.unwrap,
gcpKeyversion,
)
val keyVersionName = cryptoKeyVersionName(keyId)
loggerKms.withLogging[Unit](
loggerKms.deleteKeyRequestMsg(keyId.unwrap),
_ => loggerKms.deleteKeyResponseMsg(keyId.unwrap),
Expand All @@ -709,14 +686,7 @@ class GcpKms(
ec: ExecutionContext,
tc: TraceContext,
): EitherT[FutureUnlessShutdown, KmsError, gcp.CryptoKeyVersion] = {
val keyVersionName =
gcp.CryptoKeyVersionName.of(
config.projectId,
config.locationId,
config.keyRingId,
keyId.unwrap,
gcpKeyversion,
)
val keyVersionName = cryptoKeyVersionName(keyId)
loggerKms.withLogging[gcp.CryptoKeyVersion](
loggerKms.retrieveKeyMetadataRequestMsg(keyId.unwrap),
keyMetadata =>
Expand Down Expand Up @@ -746,6 +716,13 @@ class GcpKms(

object GcpKms extends Kms.SupportedSchemes {

/** Default cryptoKey version used by Canton when no override is specified for a given key
* via [[KmsConfig.Gcp.keyVersionOverrides]]. Canton creates new cryptoKeys in GCP KMS
* rather than adding a new version to an existing one, so version `"1"` is always correct
* for Canton-generated keys.
*/
private[gcp] val defaultGcpKeyVersion: String = "1"

val supportedSigningKeySpecs: NonEmpty[Set[SigningKeySpec]] =
NonEmpty.mk(
Set,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,18 @@ node's configuration file. A KMS for GCP is configured in the following way:
- ``project-id`` specifies which project are we binding to.
- ``key-ring-id`` specifies the keyring to use. Multi region keys are enabled for an entire keyring. Therefore, the KMS operator is responsible for setting the keyring correctly depending on the systems' needs.
- ``audit-logging`` flag that enables logging of every call made to the GCP KMS
- ``key-version-overrides`` (optional) maps a GCP cryptoKey id to the cryptoKey version Canton
should use for that key. Keys not listed default to version ``"1"``, which matches Canton's
behavior of creating a new cryptoKey rather than adding a version to an existing one. This
is intended for cryptoKeys whose key material was
`imported into GCP KMS <https://cloud.google.com/kms/docs/importing-a-key>`_, where each
import creates a new cryptoKey version and multiple versions can be relevant even for
asymmetric keys. Example::

key-version-overrides = {
"my-imported-signing-key" = "3"
"my-imported-encryption-key" = "2"
}

Configure GCP credentials and permissions
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,3 +76,11 @@ where `xyzKmsKeyId` is the KMS key identifier for a specific key (e.g. `KMS Key
.. note::
When using `AWS cross account keys <https://docs.aws.amazon.com/kms/latest/developerguide/key-policy-modifying-external-accounts.html>`_
the key ID can't be used, use the key `ARN` instead.

.. note::
For GCP KMS, the key identifier is the cryptoKey id (e.g. ``my-signing-key``). By
default, Canton uses version ``"1"`` of the cryptoKey. If you registered a cryptoKey
whose key material was `imported into GCP KMS <https://cloud.google.com/kms/docs/importing-a-key>`_
and want to target a specific cryptoKey version, set
:ref:`\`\`key-version-overrides\`\` <kms_gcp_config>` on the GCP KMS config, mapping the
cryptoKey id to the desired version.
Loading