mithril_aggregator/services/snapshotter/ancillary_signer/
with_gcp_kms.rs

1use anyhow::Context;
2use async_trait::async_trait;
3use gcloud_kms::client::google_cloud_auth::credentials::CredentialsFile;
4use gcloud_kms::client::{Client as GcpKmsClient, ClientConfig};
5use gcloud_kms::grpc::kms::v1::AsymmetricSignRequest;
6use slog::{Logger, debug};
7
8use mithril_cardano_node_internal_database::entities::AncillaryFilesManifest;
9use mithril_common::StdResult;
10use mithril_common::crypto_helper::ManifestSignature;
11
12use crate::services::ancillary_signer::{AncillarySigner, GcpCryptoKeyVersionResourceName};
13
14/// Ancillary signer that uses a key stored in a Google Cloud Platform KMS account to sign
15/// ancillary manifests.
16pub struct AncillarySignerWithGcpKms {
17    kms_client: GcpKmsClient,
18    resource_name: GcpCryptoKeyVersionResourceName,
19    logger: Logger,
20}
21
22impl AncillarySignerWithGcpKms {
23    /// Creates a new instance of `AncillarySignerWithGcpKms`
24    pub async fn new(
25        resource_name: GcpCryptoKeyVersionResourceName,
26        credentials_json_env_var: String,
27        logger: Logger,
28    ) -> StdResult<Self> {
29        const BASE_ERROR_CONTEXT: &str =
30            "Failed to create Google Cloud KMS client for Ancillary manifest signing";
31        let unparsed_credentials_json = std::env::var(&credentials_json_env_var)
32            .with_context(|| {
33                format!("Environment variable `{credentials_json_env_var}` must be set",)
34            })
35            .with_context(|| BASE_ERROR_CONTEXT)?;
36        let credentials_file = CredentialsFile::new_from_str(&unparsed_credentials_json)
37            .await
38            .with_context(|| BASE_ERROR_CONTEXT)?;
39        let config = ClientConfig::default()
40            .with_credentials(credentials_file)
41            .await
42            .with_context(|| BASE_ERROR_CONTEXT)?;
43        let kms_client = GcpKmsClient::new(config).await.with_context(|| BASE_ERROR_CONTEXT)?;
44
45        Ok(AncillarySignerWithGcpKms {
46            kms_client,
47            resource_name,
48            logger,
49        })
50    }
51}
52
53#[async_trait]
54impl AncillarySigner for AncillarySignerWithGcpKms {
55    async fn compute_ancillary_manifest_signature(
56        &self,
57        manifest: &AncillaryFilesManifest,
58    ) -> StdResult<ManifestSignature> {
59        debug!(
60            self.logger,
61            ">> AncillarySignerWithGcpKms::compute_ancillary_manifest_signature"
62        );
63        let data = manifest.compute_hash();
64        let signature_response = self
65            .kms_client
66            .asymmetric_sign(
67                AsymmetricSignRequest {
68                    name: self.resource_name.to_string(),
69                    digest: None,
70                    digest_crc32c: None,
71                    data,
72                    data_crc32c: None,
73                },
74                None,
75            )
76            .await
77            .with_context(|| "Failed to sign the ancillary manifest with GCP KMS".to_string())?;
78
79        ManifestSignature::from_bytes(&signature_response.signature).with_context(|| {
80            "Failed to convert the signature response from GCP KMS to a ManifestSignature"
81                .to_string()
82        })
83    }
84}