Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(warg): Adds config option for signing secret #82

Merged
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,12 @@ default = "warg"
# A path to a valid warg config file. If this is not set, the `wkg` CLI (but not the libraries)
# will attempt to load the config from the default location(s).
config_file = "/a/path"
# An optional authentication token to use when authenticating with a registry.
auth_token = "an-auth-token"
# An optional key for signing the component. Ideally, you should just let warg use the keychain
# or programmatically set this key in the config without writing to disk. This offers an escape
# hatch for when you need to use a key that isn't in the keychain.
signing_key = "ecdsa-p256:2CV1EpLaSYEn4In4OAEDAj5O4Hzu8AFAxgHXuG310Ew="
[registry."acme.registry.com".oci]
# The auth field can either be a username/password pair, or a base64 encoded `username:password`
# string. If no auth is set, the `wkg` CLI (but not the libraries) will also attempt to load the
Expand Down
1 change: 1 addition & 0 deletions crates/wasm-pkg-client/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ tracing = "0.1.40"
tracing-subscriber = { workspace = true }
url = "2.5.0"
warg-client = "0.8.0"
warg-crypto = "0.8.0"
warg-protocol = "0.8.0"
wasm-pkg-common = { workspace = true, features = ["metadata-client", "tokio"] }
wit-component = { workspace = true }
Expand Down
46 changes: 44 additions & 2 deletions crates/wasm-pkg-client/src/warg/config.rs
Original file line number Diff line number Diff line change
@@ -1,29 +1,46 @@
use std::path::PathBuf;
use std::{fmt::Debug, path::PathBuf, sync::Arc};

use secrecy::{ExposeSecret, SecretString};
use serde::{Deserialize, Serialize, Serializer};
use warg_crypto::signing::PrivateKey;
use wasm_pkg_common::{config::RegistryConfig, Error};

/// Registry configuration for Warg backends.
///
/// See: [`RegistryConfig::backend_config`]
#[derive(Clone, Debug, Default, Serialize)]
#[derive(Clone, Default, Serialize)]
#[serde(into = "WargRegistryConfigToml")]
pub struct WargRegistryConfig {
/// The configuration for the Warg client.
pub client_config: warg_client::Config,
/// The authentication token for the Warg registry.
pub auth_token: Option<SecretString>,
/// A signing key to use for publishing packages.
// NOTE(thomastaylor312): This couldn't be wrapped in a secret because the outer type doesn't
// implement Zeroize. However, the inner type is zeroized.
pub signing_key: Option<Arc<PrivateKey>>,
/// The path to the Warg config file, if specified.
pub config_file: Option<PathBuf>,
}

impl Debug for WargRegistryConfig {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("WargRegistryConfig")
.field("client_config", &self.client_config)
.field("auth_token", &self.auth_token)
calvinrp marked this conversation as resolved.
Show resolved Hide resolved
.field("signing_key", &"[redacted]")
.field("config_file", &self.config_file)
.finish()
}
}

impl TryFrom<&RegistryConfig> for WargRegistryConfig {
type Error = Error;

fn try_from(registry_config: &RegistryConfig) -> Result<Self, Self::Error> {
let WargRegistryConfigToml {
auth_token,
signing_key,
config_file,
} = registry_config.backend_config("warg")?.unwrap_or_default();
let (client_config, config_file) = match config_file {
Expand All @@ -43,9 +60,16 @@ impl TryFrom<&RegistryConfig> for WargRegistryConfig {
)
}
};

Ok(Self {
client_config,
auth_token,
signing_key: signing_key
.map(|k| PrivateKey::decode(k).map(Arc::new))
.transpose()
.map_err(|e| {
Error::InvalidConfig(anyhow::anyhow!("invalid signing key in config file: {e}"))
})?,
config_file,
})
}
Expand All @@ -60,13 +84,21 @@ struct WargRegistryConfigToml {
serialize_with = "serialize_secret"
)]
auth_token: Option<SecretString>,
#[serde(
skip_serializing_if = "Option::is_none",
serialize_with = "serialize_secret"
)]
signing_key: Option<SecretString>,
}

impl From<WargRegistryConfig> for WargRegistryConfigToml {
fn from(value: WargRegistryConfig) -> Self {
WargRegistryConfigToml {
auth_token: value.auth_token,
config_file: value.config_file,
signing_key: value
.signing_key
.map(|k| SecretString::new(k.encode().to_string())),
}
}
}
Expand All @@ -90,12 +122,14 @@ mod tests {
async fn test_warg_config_roundtrip() {
let dir = tempfile::tempdir().expect("Unable to create tempdir");
let warg_config_path = dir.path().join("warg_config.json");
let (_, key) = warg_crypto::signing::generate_p256_pair();
let config = WargRegistryConfig {
client_config: warg_client::Config {
home_url: Some("https://example.com".to_owned()),
..Default::default()
},
auth_token: Some("imsecret".to_owned().into()),
signing_key: Some(Arc::new(key)),
config_file: Some(warg_config_path.clone()),
};

Expand Down Expand Up @@ -135,5 +169,13 @@ mod tests {
config.auth_token.unwrap().expose_secret(),
"Auth token should be set to the right value"
);
assert_eq!(
roundtripped
.signing_key
.expect("Should have a signing key set")
.encode(),
config.signing_key.unwrap().encode(),
"Signing key should be set to the right value"
);
}
}
16 changes: 12 additions & 4 deletions crates/wasm-pkg-client/src/warg/mod.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
//! Warg package backend.

mod config;
mod loader;
mod publisher;
use std::sync::Arc;

use serde::Deserialize;
use warg_client::{storage::PackageInfo, ClientError, FileSystemClient};
use warg_crypto::signing::PrivateKey;
use warg_protocol::registry::PackageName;
use wasm_pkg_common::{
config::RegistryConfig, metadata::RegistryMetadata, package::PackageRef, registry::Registry,
Error,
};

mod config;
mod loader;
mod publisher;

/// Re-exported for convenience.
pub use warg_client as client;

Expand All @@ -25,6 +28,7 @@ struct WargRegistryMetadata {

pub(crate) struct WargBackend {
client: FileSystemClient,
signing_key: Option<Arc<PrivateKey>>,
}

impl WargBackend {
Expand All @@ -40,6 +44,7 @@ impl WargBackend {
let WargRegistryConfig {
client_config,
auth_token,
signing_key,
..
} = registry_config.try_into()?;

Expand All @@ -57,7 +62,10 @@ impl WargBackend {
FileSystemClient::new_with_config(Some(url.as_str()), &client_config, auth_token)
.await
.map_err(warg_registry_error)?;
Ok(Self { client })
Ok(Self {
client,
signing_key,
})
}

pub(crate) async fn fetch_package_info(
Expand Down
20 changes: 11 additions & 9 deletions crates/wasm-pkg-client/src/warg/publisher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,15 +39,17 @@ impl PackagePublisher for WargBackend {

// start Warg publish, using the keyring to sign
let version = version.clone();
let record_id = self
.client
.sign_with_keyring_and_publish(Some(PublishInfo {
name: name.clone(),
head: None,
entries: vec![PublishEntry::Release { version, content }],
}))
.await
.map_err(super::warg_registry_error)?;
let info = PublishInfo {
name: name.clone(),
head: None,
entries: vec![PublishEntry::Release { version, content }],
};
let record_id = if let Some(key) = self.signing_key.as_ref() {
self.client.publish_with_info(key, info).await
} else {
self.client.sign_with_keyring_and_publish(Some(info)).await
}
.map_err(super::warg_registry_error)?;

// wait for the Warg publish to finish
self.client
Expand Down