Skip to content
Draft
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
5 changes: 5 additions & 0 deletions .changeset/keyset-vendor-table.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@cipherstash/auth': patch
---

Internal change to `keysetCreate` and `keysetRewrap`. One table now holds each key vendor backend: its short name, its vendor and how to build its client from the environment. There is no change to what they do.
37 changes: 22 additions & 15 deletions languages/golang/encrypt/guest/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -662,16 +662,23 @@ mod tests {
}
));

for (name, expected) in [
("gcp-kms", KeyVendor::Gcp),
("azure-key-vault", KeyVendor::Azure),
] {
let config =
vendor_config(vec![("key_vendor", s(name)), ("keyset_records", records())])
.unwrap()
.unwrap();
assert_eq!(config.settings.vendor(), expected);
}
let gcp = vendor_config(vec![
("key_vendor", s("gcp-kms")),
("keyset_records", records()),
])
.unwrap()
.unwrap();
assert!(matches!(
gcp.settings,
VendorSettings::Gcp { endpoint: None }
));
let azure = vendor_config(vec![
("key_vendor", s("azure-key-vault")),
("keyset_records", records()),
])
.unwrap()
.unwrap();
assert!(matches!(azure.settings, VendorSettings::Azure));

let static_key = vendor_config(vec![
("key_vendor", s("static")),
Expand All @@ -684,7 +691,7 @@ mod tests {
])
.unwrap()
.unwrap();
assert_eq!(static_key.settings.vendor(), KeyVendor::Static);
assert!(matches!(static_key.settings, VendorSettings::Static { .. }));
// The static key never prints.
assert!(!format!("{static_key:?}").contains('7'));
}
Expand Down Expand Up @@ -774,10 +781,10 @@ mod tests {
entries.push(("key_vendor".into(), s("gcp-kms")));
entries.push(("keyset_records".into(), records()));
let cfg = parse_config(FfiValue::Object(entries)).expect("config parses");
assert_eq!(
cfg.vendor.map(|v| v.settings.vendor()),
Some(KeyVendor::Gcp)
);
assert!(matches!(
cfg.vendor.map(|v| v.settings),
Some(VendorSettings::Gcp { .. })
));
}

#[test]
Expand Down
211 changes: 116 additions & 95 deletions languages/golang/encrypt/guest/src/vendor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,25 +76,11 @@ pub enum VendorSettings {
Static { key_ref: String, key: KeyBytes },
}

impl VendorSettings {
/// The backend these settings build.
pub fn vendor(&self) -> KeyVendor {
match self {
Self::Aws { .. } => KeyVendor::Aws,
Self::Vault { .. } => KeyVendor::Vault,
Self::Gcp { .. } => KeyVendor::Gcp,
Self::Azure => KeyVendor::Azure,
Self::Static { .. } => KeyVendor::Static,
}
}
}

impl std::fmt::Debug for VendorSettings {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
// The static key never prints.
f.debug_tuple("VendorSettings")
.field(&self.vendor().name())
.finish()
// No setting prints: the static key is one of them. The vendor
// prints once it is built ([`GuestVendor`]'s `Debug`).
f.debug_struct("VendorSettings").finish_non_exhaustive()
}
}

Expand Down Expand Up @@ -362,95 +348,109 @@ impl<C: CredentialImport> stack_kms_azure::TokenProvider for HostCredentials<C>
}
}

/// The guest's one key vendor backend: whichever the host configured.
///
/// One enum, not a type parameter, so that the instance's cipher has one
/// type whatever the host chose at run time.
pub enum GuestVendor<T, C> {
Aws(stack_kms_aws::AwsKms<T, HostCredentials<C>>),
Vault(stack_kms_vault::VaultTransit<T, HostCredentials<C>>),
Gcp(stack_kms_gcp::GcpKms<T, HostCredentials<C>>),
Azure(stack_kms_azure::AzureKeyVault<T, HostCredentials<C>>),
Static(StaticKeyVendor),
}
/// Declare [`GuestVendor`] from one list of its vendor clients, and forward
/// every [`KeyVendorBackend`] method to the configured one. The list is the
/// only place the variants are named, so a new vendor client is one line
/// here (plus its config keys in [`crate::config`] and its constructor in
/// [`build`]).
macro_rules! guest_vendors {
($($variant:ident($client:ty),)+) => {
/// The guest's one key vendor backend: whichever the host configured.
///
/// One enum, not a type parameter, so that the instance's cipher has
/// one type whatever the host chose at run time.
pub enum GuestVendor<T, C> {
$($variant($client),)+
}

impl<T, C> std::fmt::Debug for GuestVendor<T, C> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let name = match self {
Self::Aws(_) => KeyVendor::Aws.name(),
Self::Vault(_) => KeyVendor::Vault.name(),
Self::Gcp(_) => KeyVendor::Gcp.name(),
Self::Azure(_) => KeyVendor::Azure.name(),
Self::Static(_) => KeyVendor::Static.name(),
};
f.debug_tuple("GuestVendor").field(&name).finish()
}
}
impl<T, C> KeyVendorBackend for GuestVendor<T, C>
where
T: VendorTransport + Send + Sync,
C: CredentialImport,
{
fn vendor(&self) -> KeyVendor {
match self { $(Self::$variant(b) => b.vendor(),)+ }
}

/// Dispatch one `KeyVendorBackend` method to the configured client.
macro_rules! dispatch {
($self:expr, $backend:ident => $call:expr) => {
match $self {
GuestVendor::Aws($backend) => $call,
GuestVendor::Vault($backend) => $call,
GuestVendor::Gcp($backend) => $call,
GuestVendor::Azure($backend) => $call,
GuestVendor::Static($backend) => $call,
}
};
}
fn supports_mode(&self, mode: KeyMode) -> bool {
match self { $(Self::$variant(b) => b.supports_mode(mode),)+ }
}

impl<T, C> KeyVendorBackend for GuestVendor<T, C>
where
T: VendorTransport + Send + Sync,
C: CredentialImport,
{
fn vendor(&self) -> KeyVendor {
dispatch!(self, b => b.vendor())
}
fn same_key(&self, a: &str, b: &str) -> bool {
match self { $(Self::$variant(v) => v.same_key(a, b),)+ }
}

fn supports_mode(&self, mode: KeyMode) -> bool {
dispatch!(self, b => b.supports_mode(mode))
}
async fn wrap(
&self,
key_ref: &str,
key: &KeyBytes,
aad: &[u8],
) -> Result<Vec<u8>, Error> {
match self { $(Self::$variant(b) => b.wrap(key_ref, key, aad).await,)+ }
}

async fn wrap(&self, key_ref: &str, key: &KeyBytes, aad: &[u8]) -> Result<Vec<u8>, Error> {
dispatch!(self, b => b.wrap(key_ref, key, aad).await)
}
async fn unwrap(
&self,
key_ref: &str,
wrapped: &[u8],
aad: &[u8],
) -> Result<KeyBytes, Error> {
match self { $(Self::$variant(b) => b.unwrap(key_ref, wrapped, aad).await,)+ }
}

async fn unwrap(&self, key_ref: &str, wrapped: &[u8], aad: &[u8]) -> Result<KeyBytes, Error> {
dispatch!(self, b => b.unwrap(key_ref, wrapped, aad).await)
}
async fn generate_wrapped(
&self,
key_ref: &str,
aad: &[u8],
) -> Result<(KeyBytes, Vec<u8>), Error> {
match self { $(Self::$variant(b) => b.generate_wrapped(key_ref, aad).await,)+ }
}

async fn generate_wrapped(
&self,
key_ref: &str,
aad: &[u8],
) -> Result<(KeyBytes, Vec<u8>), Error> {
dispatch!(self, b => b.generate_wrapped(key_ref, aad).await)
}
fn batch_limit(&self) -> Option<NonZeroUsize> {
match self { $(Self::$variant(b) => b.batch_limit(),)+ }
}

fn batch_limit(&self) -> Option<NonZeroUsize> {
dispatch!(self, b => b.batch_limit())
}
async fn wrap_batch(
&self,
key_ref: &str,
items: &[WrapItem<'_>],
) -> Result<Vec<Vec<u8>>, Error> {
match self { $(Self::$variant(b) => b.wrap_batch(key_ref, items).await,)+ }
}

async fn wrap_batch(
&self,
key_ref: &str,
items: &[WrapItem<'_>],
) -> Result<Vec<Vec<u8>>, Error> {
dispatch!(self, b => b.wrap_batch(key_ref, items).await)
}
async fn unwrap_batch(
&self,
key_ref: &str,
items: &[UnwrapItem<'_>],
) -> Result<Vec<KeyBytes>, Error> {
match self { $(Self::$variant(b) => b.unwrap_batch(key_ref, items).await,)+ }
}

async fn unwrap_batch(
&self,
key_ref: &str,
items: &[UnwrapItem<'_>],
) -> Result<Vec<KeyBytes>, Error> {
dispatch!(self, b => b.unwrap_batch(key_ref, items).await)
}
async fn sleep(&self, duration: Duration) {
match self { $(Self::$variant(b) => b.sleep(duration).await,)+ }
}
}
};
}

guest_vendors! {
Aws(stack_kms_aws::AwsKms<T, HostCredentials<C>>),
Vault(stack_kms_vault::VaultTransit<T, HostCredentials<C>>),
Gcp(stack_kms_gcp::GcpKms<T, HostCredentials<C>>),
Azure(stack_kms_azure::AzureKeyVault<T, HostCredentials<C>>),
Static(StaticKeyVendor),
}

async fn sleep(&self, duration: Duration) {
dispatch!(self, b => b.sleep(duration).await)
/// Prints the vendor's name only: a client holds credentials or a key.
impl<T, C> std::fmt::Debug for GuestVendor<T, C>
where
T: VendorTransport + Send + Sync,
C: CredentialImport,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("GuestVendor")
.field(&self.vendor().name())
.finish()
}
}

Expand Down Expand Up @@ -846,6 +846,27 @@ mod tests {
);
}

/// Every method reaches the vendor client, `same_key` included: Google
/// Cloud KMS ignores the key version, where the trait's default would
/// compare the text.
#[test]
fn the_guest_vendor_forwards_to_its_client() {
let config = VendorConfig {
settings: VendorSettings::Gcp { endpoint: None },
records: Vec::new(),
default_keyset: None,
root_key_ttl: None,
};
let transport = Recording::default();
let source = build(config, &transport, FakeImport::unused()).unwrap();
let backend = source.backend();
assert_eq!(backend.vendor(), KeyVendor::Gcp);
assert_eq!(format!("{backend:?}"), "GuestVendor(\"gcp-kms\")");
let key = "projects/p/locations/l/keyRings/r/cryptoKeys/k";
assert!(backend.same_key(&format!("{key}/cryptoKeyVersions/1"), key));
assert!(!backend.same_key(key, "projects/p/locations/l/keyRings/r/cryptoKeys/other"));
}

#[test]
fn a_bad_setting_names_its_config_key() {
let config = VendorConfig {
Expand Down
Loading
Loading