202 lines
6.2 KiB
Rust
202 lines
6.2 KiB
Rust
use crate::providers::SecretProvider;
|
|
use anyhow::{Context, Result, anyhow};
|
|
use gcloud_sdk::google::cloud::secretmanager::v1;
|
|
use gcloud_sdk::google::cloud::secretmanager::v1::replication::Automatic;
|
|
use gcloud_sdk::google::cloud::secretmanager::v1::secret_manager_service_client::SecretManagerServiceClient;
|
|
use gcloud_sdk::google::cloud::secretmanager::v1::{
|
|
AccessSecretVersionRequest, AddSecretVersionRequest, CreateSecretRequest, ListSecretsRequest,
|
|
Replication, Secret, replication,
|
|
};
|
|
use gcloud_sdk::proto_ext::secretmanager::SecretPayload;
|
|
use gcloud_sdk::tonic::Code;
|
|
use gcloud_sdk::{GoogleApi, GoogleAuthMiddleware};
|
|
use serde::{Deserialize, Serialize};
|
|
use serde_with::skip_serializing_none;
|
|
use v1::DeleteSecretRequest;
|
|
use validator::Validate;
|
|
|
|
type SecretsManagerClient = GoogleApi<SecretManagerServiceClient<GoogleAuthMiddleware>>;
|
|
|
|
#[skip_serializing_none]
|
|
/// Configuration for GCP Secret Manager provider
|
|
/// See [GCP Secret Manager](https://cloud.google.com/secret-manager)
|
|
/// for more information.
|
|
///
|
|
/// This provider stores secrets in GCP Secret Manager. It requires
|
|
/// a GCP project ID to be specified.
|
|
///
|
|
/// Example
|
|
/// ```no_run
|
|
/// use gman::providers::{SecretProvider, SupportedProvider};
|
|
/// use gman::config::{Config, ProviderConfig};
|
|
/// use gman::providers::gcp_secret_manager::GcpSecretManagerProvider;
|
|
///
|
|
/// let provider = GcpSecretManagerProvider {
|
|
/// gcp_project_id: Some("my-gcp-project".to_string()),
|
|
/// };
|
|
/// let _ = provider.set_secret("MY_SECRET", "value");
|
|
#[derive(Debug, Clone, Validate, Serialize, Deserialize, PartialEq, Eq)]
|
|
#[serde(deny_unknown_fields)]
|
|
pub struct GcpSecretManagerProvider {
|
|
#[validate(required)]
|
|
pub gcp_project_id: Option<String>,
|
|
}
|
|
|
|
#[async_trait::async_trait]
|
|
impl SecretProvider for GcpSecretManagerProvider {
|
|
fn name(&self) -> &'static str {
|
|
"GcpSecretManagerProvider"
|
|
}
|
|
|
|
async fn get_secret(&self, key: &str) -> Result<String> {
|
|
let secret_value = self
|
|
.get_client()
|
|
.await?
|
|
.get()
|
|
.access_secret_version(AccessSecretVersionRequest {
|
|
name: format!(
|
|
"projects/{}/secrets/{}/versions/latest",
|
|
self.gcp_project_id.as_ref().unwrap(),
|
|
key
|
|
),
|
|
})
|
|
.await?
|
|
.into_inner()
|
|
.payload
|
|
.ok_or_else(|| anyhow!("Secret '{}' not found", key))?
|
|
.data
|
|
.ref_sensitive_value()
|
|
.to_vec();
|
|
let secret_string = String::from_utf8(secret_value)
|
|
.with_context(|| format!("Invalid UTF-8 in secret '{})'", key))?;
|
|
|
|
Ok(secret_string)
|
|
}
|
|
|
|
async fn set_secret(&self, key: &str, value: &str) -> Result<()> {
|
|
let parent = format!("projects/{}", self.gcp_project_id.as_ref().unwrap());
|
|
let secret_name = format!("{}/secrets/{}", parent, key);
|
|
let secret = Secret {
|
|
replication: Some(Replication {
|
|
replication: Some(replication::Replication::Automatic(Automatic {
|
|
customer_managed_encryption: None,
|
|
})),
|
|
}),
|
|
..Default::default()
|
|
};
|
|
let client = self.get_client().await?;
|
|
|
|
client
|
|
.get()
|
|
.create_secret(CreateSecretRequest {
|
|
parent: parent.clone(),
|
|
secret_id: key.to_string(),
|
|
secret: Some(secret),
|
|
})
|
|
.await
|
|
.map_err(|e| {
|
|
if e.code() == Code::AlreadyExists {
|
|
anyhow!("Secret already exists")
|
|
} else {
|
|
e.into()
|
|
}
|
|
})?;
|
|
|
|
let bytes = value.as_ref();
|
|
let crc32c = crc32c::crc32c(bytes) as i64;
|
|
client
|
|
.get()
|
|
.add_secret_version(AddSecretVersionRequest {
|
|
parent: secret_name,
|
|
payload: Some(SecretPayload {
|
|
data: bytes.to_vec().into(),
|
|
data_crc32c: Some(crc32c),
|
|
}),
|
|
})
|
|
.await?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
async fn delete_secret(&self, key: &str) -> Result<()> {
|
|
let name = format!(
|
|
"projects/{}/secrets/{}",
|
|
self.gcp_project_id.as_ref().unwrap(),
|
|
key
|
|
);
|
|
self.get_client()
|
|
.await?
|
|
.get()
|
|
.delete_secret(DeleteSecretRequest {
|
|
name,
|
|
etag: "".to_string(),
|
|
})
|
|
.await?;
|
|
Ok(())
|
|
}
|
|
|
|
async fn update_secret(&self, key: &str, value: &str) -> Result<()> {
|
|
let parent = format!(
|
|
"projects/{}/secrets/{}",
|
|
self.gcp_project_id.as_ref().unwrap(),
|
|
key
|
|
);
|
|
let bytes = value.as_ref();
|
|
let crc32c = crc32c::crc32c(bytes) as i64;
|
|
|
|
self.get_client()
|
|
.await?
|
|
.get()
|
|
.add_secret_version(AddSecretVersionRequest {
|
|
parent,
|
|
payload: Some(SecretPayload {
|
|
data: bytes.to_vec().into(),
|
|
data_crc32c: Some(crc32c),
|
|
}),
|
|
})
|
|
.await?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
async fn list_secrets(&self) -> Result<Vec<String>> {
|
|
let request = ListSecretsRequest {
|
|
parent: format!("projects/{}", self.gcp_project_id.as_ref().unwrap()),
|
|
..Default::default()
|
|
};
|
|
let secrets = self
|
|
.get_client()
|
|
.await?
|
|
.get()
|
|
.list_secrets(request)
|
|
.await?
|
|
.into_inner()
|
|
.secrets
|
|
.iter()
|
|
.map(|s| {
|
|
let full_secret_name = &s.name;
|
|
|
|
if let Some(secret_name) = full_secret_name.split("/secrets/").nth(1) {
|
|
secret_name.to_string()
|
|
} else {
|
|
full_secret_name.to_string()
|
|
}
|
|
})
|
|
.collect();
|
|
Ok(secrets)
|
|
}
|
|
}
|
|
|
|
impl GcpSecretManagerProvider {
|
|
async fn get_client(&self) -> Result<SecretsManagerClient> {
|
|
let client = GoogleApi::from_function(
|
|
SecretManagerServiceClient::new,
|
|
"https://secretmanager.googleapis.com",
|
|
None,
|
|
)
|
|
.await?;
|
|
|
|
Ok(client)
|
|
}
|
|
}
|