Skip to content
Open
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
107 changes: 81 additions & 26 deletions e2e/rust/tests/credential_drivers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,12 @@ fn credential_driver() -> String {
.unwrap_or_else(|_| "kubernetes-secrets".to_string())
}

#[derive(Debug)]
struct ProviderIdentity {
id: String,
workspace: String,
}

fn vault_namespace() -> String {
std::env::var("OPENSHELL_E2E_VAULT_NAMESPACE").unwrap_or_else(|_| "vault".to_string())
}
Expand All @@ -53,23 +59,28 @@ fn vault_token() -> String {
std::env::var("OPENSHELL_E2E_VAULT_TOKEN").unwrap_or_else(|_| "root".to_string())
}

fn managed_kubernetes_secret_name(provider_name: &str) -> String {
fn managed_credential_hash(identity: &ProviderIdentity, provider_name: &str) -> String {
// This test stores an ordinary provider credential, whose storage object ID is
// the provider ID, so both managed drivers use these four identity fields.
let mut hasher = Sha256::new();
hasher.update(identity.workspace.as_bytes());
hasher.update([0]);
hasher.update(identity.id.as_bytes());
hasher.update([0]);
hasher.update(provider_name.as_bytes());
hasher.update([0]);
hasher.update(CREDENTIAL_KEY.as_bytes());
let digest = hasher.finalize();
let hex = format!("{digest:x}");
format!("{digest:x}")
}

fn managed_kubernetes_secret_name(identity: &ProviderIdentity, provider_name: &str) -> String {
let hex = managed_credential_hash(identity, provider_name);
format!("openshell-cred-{}", &hex[..40])
}

fn managed_vault_path(provider_name: &str) -> String {
let mut hasher = Sha256::new();
hasher.update(provider_name.as_bytes());
hasher.update([0]);
hasher.update(CREDENTIAL_KEY.as_bytes());
let digest = hasher.finalize();
let hex = format!("{digest:x}");
fn managed_vault_path(identity: &ProviderIdentity, provider_name: &str) -> String {
let hex = managed_credential_hash(identity, provider_name);
format!("openshell/provider-credentials/{}", &hex[..40])
}

Expand Down Expand Up @@ -217,6 +228,31 @@ async fn create_provider(name: &str, secret_value: &str) -> Result<String, Strin
Ok(clean)
}

async fn provider_identity(provider_name: &str) -> Result<ProviderIdentity, String> {
let (output, code) = run_cli(&["provider", "list", "--output", "json"]).await;
let clean = strip_ansi(&output);
if code != 0 {
return Err(format!("provider list failed (exit {code}):\n{clean}"));
}
let providers: Vec<serde_json::Value> = serde_json::from_str(&clean)
.map_err(|err| format!("failed to parse provider list JSON: {err}\n{clean}"))?;
let provider = providers
.iter()
.find(|provider| provider["name"].as_str() == Some(provider_name))
.ok_or_else(|| format!("provider '{provider_name}' was not returned by provider list"))?;
let id = provider["id"]
.as_str()
.filter(|id| !id.is_empty())
.ok_or_else(|| format!("provider '{provider_name}' did not include an ID"))?;
let workspace = provider["workspace"]
.as_str()
.ok_or_else(|| format!("provider '{provider_name}' did not include a workspace"))?;
Ok(ProviderIdentity {
id: id.to_string(),
workspace: workspace.to_string(),
})
}

async fn assert_provider_get_does_not_expose_secret(
provider_name: &str,
secret_value: &str,
Expand Down Expand Up @@ -296,11 +332,12 @@ async fn configure_vault_storage() -> Result<(), String> {
}

async fn assert_kubernetes_secret_stored(
identity: &ProviderIdentity,
provider_name: &str,
secret_value: &str,
) -> Result<(), String> {
let namespace = namespace();
let secret_name = managed_kubernetes_secret_name(provider_name);
let secret_name = managed_kubernetes_secret_name(identity, provider_name);
let encoded = kubectl(&[
"-n",
&namespace,
Expand All @@ -322,9 +359,12 @@ async fn assert_kubernetes_secret_stored(
Ok(())
}

async fn assert_kubernetes_secret_deleted(provider_name: &str) -> Result<(), String> {
async fn assert_kubernetes_secret_deleted(
identity: &ProviderIdentity,
provider_name: &str,
) -> Result<(), String> {
let namespace = namespace();
let secret_name = managed_kubernetes_secret_name(provider_name);
let secret_name = managed_kubernetes_secret_name(identity, provider_name);
match kubectl(&["-n", &namespace, "get", "secret", &secret_name]).await {
Ok(output) => Err(format!(
"Kubernetes Secret '{secret_name}' still exists after provider deletion:\n{output}"
Expand All @@ -333,8 +373,12 @@ async fn assert_kubernetes_secret_deleted(provider_name: &str) -> Result<(), Str
}
}

async fn assert_vault_secret_stored(provider_name: &str, secret_value: &str) -> Result<(), String> {
let logical_path = managed_vault_path(provider_name);
async fn assert_vault_secret_stored(
identity: &ProviderIdentity,
provider_name: &str,
secret_value: &str,
) -> Result<(), String> {
let logical_path = managed_vault_path(identity, provider_name);
let output = bao(&[
"kv",
"get",
Expand All @@ -348,8 +392,11 @@ async fn assert_vault_secret_stored(provider_name: &str, secret_value: &str) ->
Ok(())
}

async fn assert_vault_secret_deleted(provider_name: &str) -> Result<(), String> {
let logical_path = managed_vault_path(provider_name);
async fn assert_vault_secret_deleted(
identity: &ProviderIdentity,
provider_name: &str,
) -> Result<(), String> {
let logical_path = managed_vault_path(identity, provider_name);
match bao(&[
"kv",
"get",
Expand All @@ -367,20 +414,27 @@ async fn assert_vault_secret_deleted(provider_name: &str) -> Result<(), String>

async fn assert_backend_stored(
driver: &str,
identity: &ProviderIdentity,
provider_name: &str,
secret_value: &str,
) -> Result<(), String> {
match driver {
"kubernetes-secrets" => assert_kubernetes_secret_stored(provider_name, secret_value).await,
"vault" => assert_vault_secret_stored(provider_name, secret_value).await,
"kubernetes-secrets" => {
assert_kubernetes_secret_stored(identity, provider_name, secret_value).await
}
"vault" => assert_vault_secret_stored(identity, provider_name, secret_value).await,
other => Err(format!("unsupported credential driver '{other}'")),
}
}

async fn assert_backend_deleted(driver: &str, provider_name: &str) -> Result<(), String> {
async fn assert_backend_deleted(
driver: &str,
identity: &ProviderIdentity,
provider_name: &str,
) -> Result<(), String> {
match driver {
"kubernetes-secrets" => assert_kubernetes_secret_deleted(provider_name).await,
"vault" => assert_vault_secret_deleted(provider_name).await,
"kubernetes-secrets" => assert_kubernetes_secret_deleted(identity, provider_name).await,
"vault" => assert_vault_secret_deleted(identity, provider_name).await,
other => Err(format!("unsupported credential driver '{other}'")),
}
}
Expand Down Expand Up @@ -409,23 +463,24 @@ async fn provider_credentials_are_stored_in_configured_backend() {
.expect("configure Vault storage fixture");
}

let result: Result<(), String> = async {
let result: Result<ProviderIdentity, String> = async {
create_provider(&provider_name, &secret_value).await?;
assert_provider_get_does_not_expose_secret(&provider_name, &secret_value).await?;
assert_backend_stored(&driver, &provider_name, &secret_value).await?;
let identity = provider_identity(&provider_name).await?;
assert_backend_stored(&driver, &identity, &provider_name, &secret_value).await?;
assert_provider_placeholder_available_in_sandbox(
&provider_name,
&sandbox_name,
&secret_value,
)
.await?;
Ok(())
Ok(identity)
}
.await;

delete_provider(&provider_name).await;
assert_backend_deleted(&driver, &provider_name)
let identity = result.expect("credential storage e2e failed");
assert_backend_deleted(&driver, &identity, &provider_name)
.await
.expect("credential backend object should be deleted with provider");
result.expect("credential storage e2e failed");
}
Loading