| Crates.io | secret-store-sdk |
| lib.rs | secret-store-sdk |
| version | 0.1.3 |
| created_at | 2025-09-21 05:12:00.348633+00 |
| updated_at | 2025-12-10 13:08:50.708306+00 |
| description | Rust SDK for XJP Secret Store Service |
| homepage | |
| repository | https://github.com/rickyjim626/secret-store-sdk |
| max_upload_size | |
| id | 1848458 |
| size | 474,033 |
A comprehensive Rust SDK for interacting with the XJP Secret Store service, providing secure storage and retrieval of secrets, configuration values, and sensitive data.
Add this to your Cargo.toml:
[dependencies]
secret-store-sdk = "0.1"
rustls-tls (default): Use rustls for TLSnative-tls: Use native system TLS implementationblocking: Enable blocking/synchronous APImetrics: Enable OpenTelemetry metricswasm: WebAssembly support for browser/edge environmentsdanger-insecure-http: Allow insecure HTTP connections (development only)use xjp_secret_store::{Client, ClientBuilder, Auth};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Build client with API key authentication
let client = ClientBuilder::new("https://secret.example.com")
.auth(Auth::bearer("your-api-key"))
.build()?;
// Get a secret
let secret = client.get_secret("production", "database-url", Default::default()).await?;
println!("Secret version: {}", secret.version);
// Put a secret with TTL
let opts = PutOpts {
ttl_seconds: Some(3600), // 1 hour
metadata: Some(serde_json::json!({"env": "prod"})),
..Default::default()
};
client.put_secret("production", "api-key", "secret-value", opts).await?;
Ok(())
}
The SDK supports multiple authentication methods in priority order:
let client = ClientBuilder::new(base_url)
.auth(Auth::bearer("your-bearer-token"))
.build()?;
let client = ClientBuilder::new(base_url)
.auth(Auth::api_key("your-api-key"))
.build()?;
use xjp_secret_store::{TokenProvider, SecretString};
#[derive(Clone)]
struct MyTokenProvider {
// your implementation
}
#[async_trait]
impl TokenProvider for MyTokenProvider {
async fn get_token(&self) -> Result<SecretString, Box<dyn Error + Send + Sync>> {
// Fetch token from your auth service
Ok(SecretString::new("dynamic-token"))
}
async fn refresh_token(&self) -> Result<(), Box<dyn Error + Send + Sync>> {
// Refresh the token
Ok(())
}
fn clone_box(&self) -> Box<dyn TokenProvider> {
Box::new(self.clone())
}
}
let client = ClientBuilder::new(base_url)
.auth(Auth::token_provider(MyTokenProvider { }))
.build()?;
use xjp_secret_store::GetOpts;
// Simple get
let secret = client.get_secret("namespace", "key", GetOpts::default()).await?;
// Get with cache disabled
let opts = GetOpts {
use_cache: false,
..Default::default()
};
let secret = client.get_secret("namespace", "key", opts).await?;
// Conditional get with ETag
let opts = GetOpts {
if_none_match: Some(previous_etag),
..Default::default()
};
match client.get_secret("namespace", "key", opts).await {
Ok(secret) => println!("Secret updated: {}", secret.version),
Err(e) if e.status_code() == Some(304) => println!("Not modified"),
Err(e) => return Err(e.into()),
}
use xjp_secret_store::PutOpts;
// Simple put
client.put_secret("namespace", "key", "value", PutOpts::default()).await?;
// Put with options
let opts = PutOpts {
ttl_seconds: Some(3600), // 1 hour TTL
metadata: Some(serde_json::json!({
"owner": "team-a",
"classification": "internal"
})),
idempotency_key: Some("unique-operation-id".to_string()),
};
client.put_secret("namespace", "key", "value", opts).await?;
use xjp_secret_store::ListOpts;
// List all secrets
let list = client.list_secrets("namespace", ListOpts::default()).await?;
// List with prefix and limit
let opts = ListOpts {
prefix: Some("app-".to_string()),
limit: Some(50),
};
let list = client.list_secrets("namespace", opts).await?;
use xjp_secret_store::{BatchKeys, ExportFormat};
// Get specific keys
let keys = BatchKeys::Keys(vec!["key1".to_string(), "key2".to_string()]);
let result = client.batch_get("namespace", keys, ExportFormat::Json).await?;
// Get all keys
let result = client.batch_get("namespace", BatchKeys::All, ExportFormat::Json).await?;
// Export as dotenv format
let result = client.batch_get("namespace", BatchKeys::All, ExportFormat::Dotenv).await?;
match result {
BatchGetResult::Text(dotenv_content) => {
std::fs::write(".env", dotenv_content)?;
}
_ => {}
}
use xjp_secret_store::BatchOp;
let operations = vec![
BatchOp::put("key1", "value1").with_ttl(3600),
BatchOp::put("key2", "value2").with_metadata(json!({"env": "prod"})),
BatchOp::delete("old-key"),
];
// Execute with transaction
let result = client.batch_operate(
"namespace",
operations,
true, // transactional
Some("idempotency-key".to_string())
).await?;
println!("Succeeded: {}, Failed: {}", result.succeeded, result.failed);
use xjp_secret_store::ExportFormat;
// Export as JSON
let export = client.export_env("namespace", ExportFormat::Json).await?;
if let EnvExport::Json(json) = export {
for (key, value) in json.environment {
println!("{} = {}", key, value);
}
}
// Export as shell script
let export = client.export_env("namespace", ExportFormat::Shell).await?;
if let EnvExport::Text(shell_script) = export {
std::fs::write("env.sh", shell_script)?;
}
The SDK provides comprehensive version management capabilities for secrets:
// List all versions of a secret
let versions = client.list_versions("namespace", "key").await?;
println!("Found {} versions:", versions.total);
for version in &versions.versions {
println!("Version {}: created at {} by {}",
version.version,
version.created_at,
version.created_by
);
if version.is_current {
println!(" ^ This is the current version");
}
}
// Get a specific version of a secret
let secret_v2 = client.get_version("namespace", "key", 2).await?;
println!("Version 2 value: {}", secret_v2.value.expose_secret());
println!("Version 2 metadata: {:?}", secret_v2.metadata);
// Rollback a secret to a specific version
let rollback_result = client.rollback("namespace", "key", 2).await?;
println!("Rolled back from version {} to {}",
rollback_result.from_version,
rollback_result.to_version
);
// The rolled back version becomes the new current version
let current = client.get_secret("namespace", "key", Default::default()).await?;
println!("Current version is now: {}", current.version);
// Create multiple versions
client.put_secret("namespace", "api-key", "v1-secret", Default::default()).await?;
tokio::time::sleep(Duration::from_secs(1)).await;
let opts = PutOpts {
metadata: Some(json!({"reason": "rotation"})),
..Default::default()
};
client.put_secret("namespace", "api-key", "v2-secret", opts).await?;
// Check version history
let versions = client.list_versions("namespace", "api-key").await?;
assert_eq!(versions.total, 2);
// Rollback if needed
if need_rollback {
client.rollback("namespace", "api-key", 1).await?;
}
// List all namespaces
let namespaces = client.list_namespaces().await?;
// Get namespace details
let info = client.get_namespace("production").await?;
println!("Namespace has {} secrets", info.secret_count);
// Initialize namespace with template
use xjp_secret_store::NamespaceTemplate;
let template = NamespaceTemplate {
template: "web-app".to_string(),
params: json!({
"environment": "staging",
"region": "us-east-1"
}),
};
client.init_namespace("new-namespace", template).await?;
The SDK provides comprehensive audit log querying capabilities for compliance and debugging:
use xjp_secret_store::AuditQuery;
// Query all audit logs
let query = AuditQuery::default();
let audit_logs = client.audit(query).await?;
println!("Total audit entries: {}", audit_logs.total);
for entry in &audit_logs.entries {
println!("{}: {} by {:?} - Success: {}",
entry.timestamp,
entry.action,
entry.actor,
entry.success
);
}
// Query failed operations
let query = AuditQuery {
success: Some(false),
limit: Some(20),
..Default::default()
};
let failed_ops = client.audit(query).await?;
// Query by namespace and time range
let query = AuditQuery {
namespace: Some("production".to_string()),
from: Some("2024-01-01T00:00:00Z".to_string()),
to: Some("2024-01-31T23:59:59Z".to_string()),
..Default::default()
};
let prod_logs = client.audit(query).await?;
// Query specific actions by actor
let query = AuditQuery {
actor: Some("ci-pipeline".to_string()),
action: Some("put".to_string()),
..Default::default()
};
let ci_writes = client.audit(query).await?;
// Paginate through audit logs
let mut all_entries = Vec::new();
let mut offset = 0;
let limit = 100;
loop {
let query = AuditQuery {
limit: Some(limit),
offset: Some(offset),
..Default::default()
};
let page = client.audit(query).await?;
all_entries.extend(page.entries);
if !page.has_more {
break;
}
offset += limit;
}
println!("Retrieved {} total audit entries", all_entries.len());
Each audit entry contains:
id: Unique identifiertimestamp: When the action occurredactor: Who performed the action (optional)action: What action was performed (get, put, delete, etc.)namespace: Affected namespace (optional)key_name: Affected key (optional)success: Whether the action succeededip_address: Client IP address (optional)user_agent: Client user agent (optional)error: Error message if failed (optional)The SDK includes an intelligent caching layer:
// Configure caching
let client = ClientBuilder::new(base_url)
.auth(Auth::bearer(token))
.enable_cache(true)
.cache_max_entries(10000)
.cache_ttl_secs(300) // 5 minutes
.build()?;
// Get cache statistics
let stats = client.cache_stats();
println!("Cache hit rate: {:.2}%", stats.hit_rate());
println!("Hits: {}, Misses: {}", stats.hits(), stats.misses());
// Clear cache
client.clear_cache();
// Invalidate specific entry
client.invalidate_cache("namespace", "key").await;
The SDK provides detailed error information:
match client.get_secret("ns", "key", Default::default()).await {
Ok(secret) => println!("Got secret v{}", secret.version),
Err(Error::Http { status, category, message, request_id }) => {
eprintln!("HTTP {}: {} - {} (request: {:?})",
status, category, message, request_id);
// Handle specific errors
match status {
401 => println!("Authentication failed"),
403 => println!("Permission denied"),
404 => println!("Secret not found"),
429 => println!("Rate limited, retry later"),
_ => println!("Server error"),
}
}
Err(Error::Network(msg)) => eprintln!("Network error: {}", msg),
Err(Error::Timeout) => eprintln!("Request timed out"),
Err(e) => eprintln!("Other error: {}", e),
}
let client = ClientBuilder::new(base_url)
.auth(Auth::bearer(token))
.timeout_ms(30000) // 30 seconds
.retries(3) // up to 3 retries
.build()?;
let client = ClientBuilder::new(base_url)
.auth(Auth::bearer(token))
.user_agent_extra("my-app/1.0")
.build()?;
#[cfg(feature = "danger-insecure-http")]
let client = ClientBuilder::new("http://localhost:8080")
.auth(Auth::bearer(token))
.allow_insecure_http()
.build()?;
The SDK supports OpenTelemetry metrics when the metrics feature is enabled:
// Add to Cargo.toml
[dependencies]
secret-store-sdk = { version = "0.1", features = ["metrics"] }
use xjp_secret_store::{ClientBuilder, Auth, telemetry::TelemetryConfig};
// Configure telemetry
let telemetry_config = TelemetryConfig {
enabled: true,
service_name: "my-service".to_string(),
service_version: "1.0.0".to_string(),
};
// Create client with telemetry
let client = ClientBuilder::new(base_url)
.auth(Auth::bearer(token))
.with_telemetry(telemetry_config)
.build()?;
// Or simply enable with defaults
let client = ClientBuilder::new(base_url)
.auth(Auth::bearer(token))
.enable_telemetry()
.build()?;
The SDK exposes the following metrics:
// Initialize OpenTelemetry with Prometheus exporter
let exporter = opentelemetry_prometheus::exporter()
.init();
// Set global meter provider
opentelemetry::global::set_meter_provider(
exporter.meter_provider().unwrap()
);
// Create SDK client with telemetry
let client = ClientBuilder::new(base_url)
.auth(Auth::bearer(token))
.enable_telemetry()
.build()?;
// Use the client - metrics are automatically collected
let secret = client.get_secret("prod", "api-key", Default::default()).await?;
// Export metrics (e.g., for Prometheus scraping)
let metrics = exporter.registry().gather();
See the metrics example for a complete working implementation.
// Node.js
const client = new SecretStoreClient({
baseUrl: 'https://api.example.com',
apiKey: 'key',
});
const secret = await client.getSecret('ns', 'key');
// Rust
let client = ClientBuilder::new("https://api.example.com")
.auth(Auth::api_key("key"))
.build()?;
let secret = client.get_secret("ns", "key", Default::default()).await?;
ver (API) → version (SDK)time::OffsetDateTimeserde_json::Value for flexibilitycargo testcargo benchcargo fmtcargo clippyMIT OR Apache-2.0