diff --git a/Cargo.lock b/Cargo.lock index 4e9642efe9..791b60afc6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -151,7 +151,7 @@ checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.110", ] [[package]] @@ -162,7 +162,7 @@ checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.110", ] [[package]] @@ -247,7 +247,7 @@ version = "0.5.0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.110", "tokio", "tracing", "tracing-subscriber", @@ -306,7 +306,7 @@ dependencies = [ "azure_core_test", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.110", "tokio", ] @@ -447,6 +447,7 @@ dependencies = [ "azure_security_keyvault_keys", "azure_security_keyvault_test", "futures", + "include-file", "openssl", "rand 0.9.2", "rustc_version", @@ -466,7 +467,9 @@ dependencies = [ "azure_security_keyvault_test", "criterion", "futures", + "include-file", "rand 0.9.2", + "rand_chacha 0.9.0", "reqwest", "rustc_version", "serde", @@ -485,6 +488,7 @@ dependencies = [ "azure_identity", "azure_security_keyvault_test", "futures", + "include-file", "rand 0.9.2", "rustc_version", "serde", @@ -621,7 +625,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.110", ] [[package]] @@ -710,7 +714,7 @@ dependencies = [ "quote", "serde", "serde_json", - "syn 2.0.104", + "syn 2.0.110", "tempfile", "toml", ] @@ -803,7 +807,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.110", ] [[package]] @@ -970,7 +974,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.104", + "syn 2.0.110", ] [[package]] @@ -981,7 +985,7 @@ checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ "darling_core", "quote", - "syn 2.0.104", + "syn 2.0.110", ] [[package]] @@ -1012,7 +1016,7 @@ checksum = "30542c1ad912e0e3d22a1935c290e12e8a29d704a420177a31faad4a601a0800" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.110", ] [[package]] @@ -1034,7 +1038,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.110", ] [[package]] @@ -1287,7 +1291,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.110", ] [[package]] @@ -1631,6 +1635,16 @@ dependencies = [ "icu_properties", ] +[[package]] +name = "include-file" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b82360a1cfc38a30a967d4e1b58745ff9258c9326772293402d88d6dd201229" +dependencies = [ + "proc-macro2", + "syn 2.0.110", +] + [[package]] name = "indexmap" version = "2.10.0" @@ -1929,7 +1943,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.110", ] [[package]] @@ -2089,7 +2103,7 @@ checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.110", ] [[package]] @@ -2173,9 +2187,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.97" +version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d61789d7719defeb74ea5fe81f2fdfdbd28a803847077cecce2ff14e1472f6f1" +checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" dependencies = [ "unicode-ident", ] @@ -2614,7 +2628,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.110", ] [[package]] @@ -2634,7 +2648,7 @@ checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.110", ] [[package]] @@ -2657,7 +2671,7 @@ checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.110", ] [[package]] @@ -2712,7 +2726,7 @@ checksum = "5d69265a08751de7844521fd15003ae0a888e035773ba05695c5c759a6f89eef" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.110", ] [[package]] @@ -2815,9 +2829,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.104" +version = "2.0.110" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" +checksum = "a99801b5bd34ede4cf3fc688c5919368fea4e4814a4664359503e6015b280aea" dependencies = [ "proc-macro2", "quote", @@ -2841,7 +2855,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.110", ] [[package]] @@ -2899,7 +2913,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.110", ] [[package]] @@ -2910,7 +2924,7 @@ checksum = "cc5b44b4ab9c2fdd0e0512e6bece8388e214c0749f5862b114cc5b7a25daf227" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.110", ] [[package]] @@ -3016,7 +3030,7 @@ checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.110", ] [[package]] @@ -3158,7 +3172,7 @@ checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.110", ] [[package]] @@ -3208,7 +3222,7 @@ checksum = "70977707304198400eb4835a78f6a9f928bf41bba420deb8fdb175cd965d77a7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.110", ] [[package]] @@ -3275,7 +3289,7 @@ dependencies = [ "rustc_version", "serde", "serde_json", - "syn 2.0.104", + "syn 2.0.110", "tokio", "typespec_client_core", ] @@ -3448,7 +3462,7 @@ dependencies = [ "log", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.110", "wasm-bindgen-shared", ] @@ -3483,7 +3497,7 @@ checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.110", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -3518,7 +3532,7 @@ checksum = "17d5042cc5fa009658f9a7333ef24291b1291a25b6382dd68862a7f3b969f69b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.110", ] [[package]] @@ -3791,7 +3805,7 @@ checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.110", "synstructure", ] @@ -3812,7 +3826,7 @@ checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.110", ] [[package]] @@ -3832,7 +3846,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.110", "synstructure", ] @@ -3872,7 +3886,7 @@ checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.110", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 1d8161d693..3570376fc8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -109,6 +109,7 @@ futures = "0.3" getrandom = { version = "0.3" } gloo-timers = { version = "0.3" } hmac = { version = "0.12" } +include-file = { version = "0.5.0" } litemap = "0.7.4" openssl = { version = "0.10.72" } opentelemetry = { version = "0.30", features = ["trace"] } diff --git a/sdk/keyvault/assets.json b/sdk/keyvault/assets.json index 595344126c..b64bf448a2 100644 --- a/sdk/keyvault/assets.json +++ b/sdk/keyvault/assets.json @@ -2,5 +2,5 @@ "AssetsRepo": "Azure/azure-sdk-assets", "AssetsRepoPrefixPath": "rust", "TagPrefix": "rust/keyvault", - "Tag": "rust/keyvault_5961c5368d" + "Tag": "rust/keyvault_257c804570" } diff --git a/sdk/keyvault/azure_security_keyvault_certificates/Cargo.toml b/sdk/keyvault/azure_security_keyvault_certificates/Cargo.toml index b62b33d671..50a5094256 100644 --- a/sdk/keyvault/azure_security_keyvault_certificates/Cargo.toml +++ b/sdk/keyvault/azure_security_keyvault_certificates/Cargo.toml @@ -31,6 +31,7 @@ azure_core_test = { workspace = true, features = [ azure_identity.workspace = true azure_security_keyvault_keys = { path = "../azure_security_keyvault_keys" } azure_security_keyvault_test = { path = "../azure_security_keyvault_test" } +include-file.workspace = true openssl.workspace = true rand.workspace = true tokio.workspace = true diff --git a/sdk/keyvault/azure_security_keyvault_certificates/README.md b/sdk/keyvault/azure_security_keyvault_certificates/README.md index e14dacc327..a3df456fd3 100644 --- a/sdk/keyvault/azure_security_keyvault_certificates/README.md +++ b/sdk/keyvault/azure_security_keyvault_certificates/README.md @@ -50,6 +50,37 @@ az login Instantiate a `DeveloperToolsCredential` to pass to the client. The same instance of a token credential can be used with multiple clients if they will be authenticating with the same identity. +### Instantiate a client + +```rust no_run +use azure_core::base64; +use azure_identity::DeveloperToolsCredential; +use azure_security_keyvault_certificates::CertificateClient; + +#[tokio::main] +async fn main() -> Result<(), Box> { + // Create a new certificate client + let credential = DeveloperToolsCredential::new(None)?; + let client = CertificateClient::new( + "https://your-key-vault-name.vault.azure.net/", + credential.clone(), + None, + )?; + + // Get a certificate using the certificate client. + let certificate = client + .get_certificate("certificate-name", None) + .await? + .into_model()?; + println!( + "Thumbprint: {:?}", + certificate.x509_thumbprint.map(base64::encode_url_safe) + ); + + Ok(()) +} +``` + ## Key concepts ### Certificate @@ -66,7 +97,7 @@ We guarantee that all client instance methods are thread-safe and independent of ## Examples -The following section provides several code snippets using the `CertificateClient`, covering some of the most common Azure Key Vault certificates service related tasks: +The following section provides several code snippets using a `CertificateClient` like we [instantiated above](#instantiate-a-client): * [Create a certificate](#create-a-certificate) * [Retrieve a certificate](#retrieve-a-certificate) @@ -83,48 +114,34 @@ Before we can create a new certificate, though, we need to define a certificate `create_certificate` returns a `Poller`, which implements both `std::future::IntoFuture` and `futures::Stream`. You can `await` the `Poller` to get the final result - a `Certificate` - or asynchronously iterate over each status update. -```rust no_run -use azure_identity::DeveloperToolsCredential; -use azure_security_keyvault_certificates::{ - CertificateClient, - models::{CreateCertificateParameters, CertificatePolicy, X509CertificateProperties, IssuerParameters}, +```rust ignore create_certificate +use azure_security_keyvault_certificates::models::{ + CertificatePolicy, CreateCertificateParameters, IssuerParameters, X509CertificateProperties, }; -#[tokio::main] -async fn main() -> Result<(), Box> { - let credential = DeveloperToolsCredential::new(None)?; - let client = CertificateClient::new( - "https://your-key-vault-name.vault.azure.net/", - credential.clone(), - None, - )?; - - // Create a self-signed certificate. - let policy = CertificatePolicy { - x509_certificate_properties: Some(X509CertificateProperties { - subject: Some("CN=DefaultPolicy".into()), - ..Default::default() - }), - issuer_parameters: Some(IssuerParameters { - name: Some("Self".into()), - ..Default::default() - }), +// Create a self-signed certificate. +let policy = CertificatePolicy { + x509_certificate_properties: Some(X509CertificateProperties { + subject: Some("CN=DefaultPolicy".into()), ..Default::default() - }; - let body = CreateCertificateParameters { - certificate_policy: Some(policy), + }), + issuer_parameters: Some(IssuerParameters { + name: Some("Self".into()), ..Default::default() - }; - - // Wait for the certificate operation to complete. - // The Poller implements futures::Stream and automatically waits between polls. - let certificate = client - .create_certificate("certificate-name", body.try_into()?, None)? - .await? - .into_model()?; + }), + ..Default::default() +}; +let body = CreateCertificateParameters { + certificate_policy: Some(policy), + ..Default::default() +}; - Ok(()) -} +// Wait for the certificate operation to complete. +// The Poller implements futures::Stream and automatically waits between polls. +let certificate = client + .create_certificate("certificate-name", body.try_into()?, None)? + .await? + .into_model()?; ``` ### Retrieve a certificate @@ -132,36 +149,23 @@ async fn main() -> Result<(), Box> { `get_certificate` retrieves a certificate that was created or even still in progress in Key Vault. Setting the `certificate-version` to an empty string will return the latest version. -```rust no_run +```rust ignore get_certificate use azure_core::base64; -use azure_identity::DeveloperToolsCredential; -use azure_security_keyvault_certificates::{CertificateClient, models::CertificateClientGetCertificateOptions}; - -#[tokio::main] -async fn main() -> Result<(), Box> { - let credential = DeveloperToolsCredential::new(None)?; - let client = CertificateClient::new( - "https://.vault.azure.net/", - credential.clone(), - None, - )?; - - let get_options = CertificateClientGetCertificateOptions{ - certificate_version: Some("certificate-version".to_string()), - ..Default::default() - }; - let certificate = client - .get_certificate("certificate-name", Some(get_options)) - .await? - .into_model()?; - - println!( - "Certificate thumbprint: {:?}", - certificate.x509_thumbprint.map(base64::encode) - ); +use azure_security_keyvault_certificates::models::CertificateClientGetCertificateOptions; - Ok(()) -} +let get_options = CertificateClientGetCertificateOptions{ + certificate_version: Some("certificate-version".to_string()), + ..Default::default() +}; +let certificate = client + .get_certificate("certificate-name", Some(get_options)) + .await? + .into_model()?; + +println!( + "Certificate thumbprint: {:?}", + certificate.x509_thumbprint.map(base64::encode) +); ``` ### Update an existing certificate @@ -169,39 +173,24 @@ async fn main() -> Result<(), Box> { `update_certificate_properties` updates a certificate previously stored in the Azure Key Vault. Only the attributes of the certificate are updated. To regenerate the certificate, call `CertificateClient::create_certificate` on a certificate with the same name. -```rust no_run -use azure_identity::DeveloperToolsCredential; -use azure_security_keyvault_certificates::{ - models::UpdateCertificatePropertiesParameters, CertificateClient, -}; +```rust ignore update_certificate +use azure_security_keyvault_certificates::models::UpdateCertificatePropertiesParameters; use std::collections::HashMap; -#[tokio::main] -async fn main() -> Result<(), Box> { - let credential = DeveloperToolsCredential::new(None)?; - let client = CertificateClient::new( - "https://.vault.azure.net/", - credential.clone(), - None, - )?; - - // Update a certificate using the certificate client. - let certificate_update_parameters = UpdateCertificatePropertiesParameters { - tags: Some(HashMap::from_iter(vec![("tag-name".into(), "tag-value".into())])), - ..Default::default() - }; - - client - .update_certificate_properties( - "certificate-name", - certificate_update_parameters.try_into()?, - None, - ) - .await? - .into_model()?; +// Update a certificate using the certificate client. +let certificate_update_parameters = UpdateCertificatePropertiesParameters { + tags: Some(HashMap::from_iter(vec![("tag-name".into(), "tag-value".into())])), + ..Default::default() +}; - Ok(()) -} +client + .update_certificate_properties( + "certificate-name", + certificate_update_parameters.try_into()?, + None, + ) + .await? + .into_model()?; ``` ### Delete a certificate @@ -209,53 +198,24 @@ async fn main() -> Result<(), Box> { `delete_certificate` will tell Key Vault to delete a certificate but it is not deleted immediately. It will not be deleted until the service-configured data retention period - the default is 90 days - or until you call `purge_certificate` on the returned `DeletedCertificate.id`. -```rust no_run -use azure_identity::DeveloperToolsCredential; -use azure_security_keyvault_certificates::CertificateClient; - -#[tokio::main] -async fn main() -> Result<(), Box> { - let credential = DeveloperToolsCredential::new(None)?; - let client = CertificateClient::new( - "https://.vault.azure.net/", - credential.clone(), - None, - )?; - - // Delete a certificate using the certificate client. - client.delete_certificate("certificate-name", None).await?; - - Ok(()) -} +```rust ignore delete_certificate +// Delete a certificate using the certificate client. +client.delete_certificate("certificate-name", None).await?; ``` ### List certificates This example lists all the certificates in the specified Azure Key Vault. -```rust no_run -use azure_identity::DeveloperToolsCredential; -use azure_security_keyvault_certificates::{CertificateClient, ResourceExt}; +```rust ignore list_certificates +use azure_security_keyvault_certificates::ResourceExt; use futures::TryStreamExt; -#[tokio::main] -async fn main() -> Result<(), Box> { - // Create a new certificate client - let credential = DeveloperToolsCredential::new(None)?; - let client = CertificateClient::new( - "https://.vault.azure.net/", - credential.clone(), - None, - )?; - - let mut pager = client.list_certificate_properties(None)?.into_stream(); - while let Some(certificate) = pager.try_next().await? { - // Get the certificate name from the ID. - let name = certificate.resource_id()?.name; - println!("Found Certificate with Name: {}", name); - } - - Ok(()) +let mut pager = client.list_certificate_properties(None)?.into_stream(); +while let Some(certificate) = pager.try_next().await? { + // Get the certificate name from the ID. + let name = certificate.resource_id()?.name; + println!("Found Certificate with Name: {}", name); } ``` @@ -264,89 +224,68 @@ async fn main() -> Result<(), Box> { You can use a `KeyClient` to perform key operations on a certificate created with a `CertificateClient`. The following example shows how to sign data using an EC certificate key. -```rust no_run +```rust ignore key_operations use azure_core::base64; -use azure_identity::DeveloperToolsCredential; use azure_security_keyvault_certificates::{ models::{ - CertificatePolicy, CreateCertificateParameters, CurveName, IssuerParameters, KeyProperties, - KeyType, KeyUsageType, X509CertificateProperties, + CertificatePolicy, CreateCertificateParameters, CurveName, IssuerParameters, + KeyProperties, KeyType, KeyUsageType, X509CertificateProperties, }, - CertificateClient, ResourceExt, ResourceId, + ResourceExt, ResourceId, }; use azure_security_keyvault_keys::{ models::{SignParameters, SignatureAlgorithm}, - KeyClient, }; use openssl::sha::sha256; -use std::{env, time::Duration}; -use tokio::time::sleep; - -#[tokio::main] -async fn main() -> Result<(), Box> { - // Pass data to sign as the first argument. - let plaintext = env::args().nth(1).ok_or("plaintext required")?; - let certificate_client = CertificateClient::new( - "https://.vault.azure.net/", - DeveloperToolsCredential::new(None)?, - None, - )?; +let plaintext = "plaintext"; - // Create an EC certificate policy for signing. - let policy = CertificatePolicy { - x509_certificate_properties: Some(X509CertificateProperties { - subject: Some("CN=DefaultPolicy".into()), - key_usage: Some(vec![KeyUsageType::DigitalSignature]), - ..Default::default() - }), - issuer_parameters: Some(IssuerParameters { - name: Some("Self".into()), - ..Default::default() - }), - key_properties: Some(KeyProperties { - key_type: Some(KeyType::Ec), - curve: Some(CurveName::P256), - ..Default::default() - }), +// Create an EC certificate policy for signing. +let policy = CertificatePolicy { + x509_certificate_properties: Some(X509CertificateProperties { + subject: Some("CN=DefaultPolicy".into()), + key_usage: Some(vec![KeyUsageType::DigitalSignature]), ..Default::default() - }; - - // Create a self-signed certificate. - let body = CreateCertificateParameters { - certificate_policy: Some(policy), + }), + issuer_parameters: Some(IssuerParameters { + name: Some("Self".into()), + ..Default::default() + }), + key_properties: Some(KeyProperties { + key_type: Some(KeyType::Ec), + curve: Some(CurveName::P256), ..Default::default() - }; + }), + ..Default::default() +}; - // Wait for the certificate operation to complete. - certificate_client - .create_certificate("ec-signing-certificate", body.try_into()?, None)? - .await?; +// Create a self-signed certificate. +let body = CreateCertificateParameters { + certificate_policy: Some(policy), + ..Default::default() +}; - // Hash the plaintext to be signed. - let digest = sha256(plaintext.as_bytes()).to_vec(); +// Wait for the certificate operation to complete. +client + .create_certificate("ec-signing-certificate", body.try_into()?, None)? + .await?; - // Create a KeyClient using the certificate to sign the digest. - let key_client = KeyClient::new( - certificate_client.endpoint().as_str(), - DeveloperToolsCredential::new(None)?, - None, - )?; - let body = SignParameters { - algorithm: Some(SignatureAlgorithm::Es256), - value: Some(digest), - }; +// Hash the plaintext to be signed. +let digest = sha256(plaintext.as_bytes()).to_vec(); - let signature = key_client - .sign("ec-signing-certificate", body.try_into()?, None) - .await? - .into_model()?; +// Use a KeyClient using the certificate to sign the digest. +let body = SignParameters { + algorithm: Some(SignatureAlgorithm::Es256), + value: Some(digest), +}; - if let Some(signature) = signature.result.map(base64::encode_url_safe) { - println!("Signature: {}", signature); - } +let signature = key_client + .sign("ec-signing-certificate", body.try_into()?, None) + .await? + .into_model()?; - Ok(()) +if let Some(signature) = signature.result.map(base64::encode_url_safe) { + println!("Signature: {}", signature); } ``` @@ -358,25 +297,10 @@ When you interact with the Azure Key Vault certificates client library using the For example, if you try to retrieve a key that doesn't exist in your Azure Key Vault, a `404` error is returned, indicating `Not Found`. -```rust no_run -use azure_identity::DeveloperToolsCredential; -use azure_security_keyvault_certificates::CertificateClient; - -#[tokio::main] -async fn main() -> Result<(), Box> { - let credential = DeveloperToolsCredential::new(None)?; - let client = CertificateClient::new( - "https://.vault.azure.net/", - credential.clone(), - None, - )?; - - match client.get_certificate("certificate-name".into(), None).await { - Ok(response) => println!("Certificate: {:#?}", response.into_model()?.x509_thumbprint), - Err(err) => println!("Error: {:#?}", err.into_inner()?), - } - - Ok(()) +```rust ignore errors +match client.get_certificate("certificate-name".into(), None).await { + Ok(response) => println!("Certificate: {:#?}", response.into_model()?.x509_thumbprint), + Err(err) => println!("Error: {:#?}", err.into_inner()?), } ``` diff --git a/sdk/keyvault/azure_security_keyvault_certificates/tests/readme.rs b/sdk/keyvault/azure_security_keyvault_certificates/tests/readme.rs new file mode 100644 index 0000000000..f6b6193d7b --- /dev/null +++ b/sdk/keyvault/azure_security_keyvault_certificates/tests/readme.rs @@ -0,0 +1,78 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +use azure_core::{error::Result, http::StatusCode}; +use azure_core_test::{recorded, TestContext, TestMode}; +use azure_security_keyvault_certificates::{CertificateClient, CertificateClientOptions}; +use azure_security_keyvault_keys::{KeyClient, KeyClientOptions}; +use azure_security_keyvault_test::Retry; +use include_file::include_markdown; + +#[recorded::test] +async fn readme(ctx: TestContext) -> Result<()> { + let recording = ctx.recording(); + + let mut options = CertificateClientOptions::default(); + recording.instrument(&mut options.client_options); + + let client = CertificateClient::new( + recording.var("AZURE_KEYVAULT_URL", None).as_str(), + recording.credential(), + Some(options), + )?; + + let mut key_options = KeyClientOptions::default(); + recording.instrument(&mut key_options.client_options); + + let key_client = KeyClient::new( + client.endpoint().as_str(), + recording.credential(), + Some(key_options), + )?; + + // Each macro invocation is in its own block to prevent errors with duplicate imports. + println!("Create a certificate"); + include_markdown!("README.md", "create_certificate", scope); + + println!("Get a certificate"); + include_markdown!("README.md", "get_certificate", scope); + + println!("Update a certificate"); + include_markdown!("README.md", "update_certificate", scope); + + println!("List certificates"); + include_markdown!("README.md", "list_certificates", scope); + + println!("Key operations using certificates"); + include_markdown!("README.md", "key_operations", scope); + + println!("Handle errors"); + include_markdown!("README.md", "errors", scope); + + println!("Delete a certificate"); + include_markdown!("README.md", "delete_certificate", scope); + + println!("Purge a certificate"); + // Because deletes may not happen right away, try purging in a loop. + let mut retry = match recording.test_mode() { + TestMode::Playback => Retry::immediate(), + _ => Retry::progressive(None), + }; + + loop { + match client + .purge_deleted_certificate("certificate-name", None) + .await + { + Ok(_) => break, + Err(err) if matches!(err.http_status(), Some(StatusCode::Conflict)) => { + if retry.next().await.is_none() { + return Err(err); + } + } + Err(err) => return Err(err), + } + } + + Ok(()) +} diff --git a/sdk/keyvault/azure_security_keyvault_keys/Cargo.toml b/sdk/keyvault/azure_security_keyvault_keys/Cargo.toml index 58f5f251b0..b03250337c 100644 --- a/sdk/keyvault/azure_security_keyvault_keys/Cargo.toml +++ b/sdk/keyvault/azure_security_keyvault_keys/Cargo.toml @@ -30,7 +30,9 @@ azure_core_test = { workspace = true, features = [ azure_identity.workspace = true azure_security_keyvault_test = { path = "../azure_security_keyvault_test" } criterion.workspace = true +include-file.workspace = true rand.workspace = true +rand_chacha.workspace = true reqwest.workspace = true sha2.workspace = true tokio.workspace = true diff --git a/sdk/keyvault/azure_security_keyvault_keys/README.md b/sdk/keyvault/azure_security_keyvault_keys/README.md index ce2e0a9167..f3cebd22c1 100644 --- a/sdk/keyvault/azure_security_keyvault_keys/README.md +++ b/sdk/keyvault/azure_security_keyvault_keys/README.md @@ -77,6 +77,33 @@ Use the `az keyvault security-domain download` command to download the security az keyvault security-domain download --hsm-name --sd-wrapping-keys ./certs/cert_0.cer ./certs/cert_1.cer ./certs/cert_2.cer --sd-quorum 2 --security-domain-file ContosoHSM-SD.json ``` +### Instantiate a client + +```rust no_run +use azure_identity::DeveloperToolsCredential; +use azure_security_keyvault_keys::KeyClient; + +#[tokio::main] +async fn main() -> Result<(), Box> { + // Create a new key client + let credential = DeveloperToolsCredential::new(None)?; + let client = KeyClient::new( + "https://your-key-vault-name.vault.azure.net/", + credential.clone(), + None, + )?; + + // Get a key using the key client. + let key = client + .get_key("key-name", None) + .await? + .into_model()?; + println!("JWT: {:?}", key.key); + + Ok(()) +} +``` + ## Key concepts ### Key @@ -93,7 +120,7 @@ We guarantee that all client instance methods are thread-safe and independent of ## Examples -The following section provides several code snippets using the `KeyClient`, covering some of the most common Azure Key Vault keys service related tasks: +The following section provides several code snippets using a `KeyClient` like we [instantiated above](#instantiate-a-client): * [Create a key](#create-a-key) * [Retrieve a key](#retrieve-a-key) @@ -106,163 +133,94 @@ The following section provides several code snippets using the `KeyClient`, cove `create_key` creates a Key Vault key to be stored in the Azure Key Vault. If a key with the same name already exists, then a new version of the key is created. -```rust no_run -use azure_identity::DeveloperToolsCredential; +```rust ignore create_key use azure_security_keyvault_keys::{ - models::{Key, CreateKeyParameters, CurveName, KeyType}, - ResourceExt, KeyClient, + models::{CreateKeyParameters, CurveName, KeyType}, + ResourceExt, }; -#[tokio::main] -async fn main() -> Result<(), Box> { - let credential = DeveloperToolsCredential::new(None)?; - let client = KeyClient::new( - "https://your-key-vault-name.vault.azure.net/", - credential.clone(), - None, - )?; - - // Create an EC key. - let body = CreateKeyParameters { - kty: Some(KeyType::Ec), - curve: Some(CurveName::P256), - ..Default::default() - }; - - let key = client - .create_key("key-name", body.try_into()?, None) - .await? - .into_model()?; - - println!( - "Key Name: {:?}, Type: {:?}, Version: {:?}", - key.resource_id()?.name, - key.key.as_ref().map(|k| k.kty.as_ref()), - key.resource_id()?.version, - ); +// Create an EC key. +let body = CreateKeyParameters { + kty: Some(KeyType::Ec), + curve: Some(CurveName::P256), + ..Default::default() +}; - Ok(()) -} +let key = client + .create_key("key-name", body.try_into()?, None) + .await? + .into_model()?; + +println!( + "Key Name: {:?}, Type: {:?}, Version: {:?}", + key.resource_id()?.name, + key.key.as_ref().map(|k| k.kty.as_ref()), + key.resource_id()?.version, +); ``` ### Retrieve a key `get_key` retrieves a public key (or only metadata for symmetric keys) previously stored in the Azure Key Vault. Setting the `key-version` to an empty string will return the latest version. -```rust no_run -use azure_identity::DeveloperToolsCredential; -use azure_security_keyvault_keys::{KeyClient, models::KeyClientGetKeyOptions}; - -#[tokio::main] -async fn main() -> Result<(), Box> { - let credential = DeveloperToolsCredential::new(None)?; - let client = KeyClient::new( - "https://your-key-vault-name.vault.azure.net/", - credential.clone(), - None, - )?; - - // Retrieve a public key as a JWK using the key client. +```rust ignore get_key +use azure_security_keyvault_keys::models::KeyClientGetKeyOptions; - let key_options = KeyClientGetKeyOptions { - key_version: Some("key-version".to_string()), - ..Default::default() - }; - let key = client - .get_key("key-name", None) - .await? - .into_model()?; - - println!("Key: {:#?}", key.key); +// Retrieve a public key as a JWK using the key client. +let key_options = KeyClientGetKeyOptions { + key_version: Some("key-version".to_string()), + ..Default::default() +}; +let key = client + .get_key("key-name", None) + .await? + .into_model()?; - Ok(()) -} +println!("Key: {:#?}", key.key); ``` ### Update an existing key `update_key_properties` updates a key previously stored in the Azure Key Vault. Only the attributes of the key are updated. To update the value, call `KeyClient::create_key` on a key with the same name. -```rust no_run -use azure_identity::DeveloperToolsCredential; -use azure_security_keyvault_keys::{models::UpdateKeyPropertiesParameters, KeyClient}; +```rust ignore update_key +use azure_security_keyvault_keys::models::UpdateKeyPropertiesParameters; use std::collections::HashMap; -#[tokio::main] -async fn main() -> Result<(), Box> { - let credential = DeveloperToolsCredential::new(None)?; - let client = KeyClient::new( - "https://your-key-vault-name.vault.azure.net/", - credential.clone(), - None, - )?; - - // Update a key using the key client. - let key_update_parameters = UpdateKeyPropertiesParameters { - tags: Some(HashMap::from_iter(vec![("tag-name".into(), "tag-value".into())])), - ..Default::default() - }; - - client - .update_key_properties("key-name", key_update_parameters.try_into()?, None) - .await? - .into_model()?; +// Update a key using the key client. +let key_update_parameters = UpdateKeyPropertiesParameters { + tags: Some(HashMap::from_iter(vec![("tag-name".into(), "tag-value".into())])), + ..Default::default() +}; - Ok(()) -} +client + .update_key_properties("key-name", key_update_parameters.try_into()?, None) + .await? + .into_model()?; ``` ### Delete a key `delete_key` will tell Key Vault to delete a key but it is not deleted immediately. It will not be deleted until the service-configured data retention period - the default is 90 days - or until you call `purge_key` on the returned `DeletedKey.id`. -```rust no_run -use azure_identity::DeveloperToolsCredential; -use azure_security_keyvault_keys::KeyClient; - -#[tokio::main] -async fn main() -> Result<(), Box> { - let credential = DeveloperToolsCredential::new(None)?; - let client = KeyClient::new( - "https://your-key-vault-name.vault.azure.net/", - credential.clone(), - None, - )?; - - // Delete a key using the key client. - client.delete_key("key-name", None).await?; - - Ok(()) -} +```rust ignore delete_key +// Delete a key using the key client. +client.delete_key("key-name", None).await?; ``` ### List keys This example lists all the keys in the specified Azure Key Vault. -```rust no_run -use azure_identity::DeveloperToolsCredential; -use azure_security_keyvault_keys::{KeyClient, ResourceExt}; +```rust ignore list_keys +use azure_security_keyvault_keys::ResourceExt; use futures::TryStreamExt; -#[tokio::main] -async fn main() -> Result<(), Box> { - // Create a new key client - let credential = DeveloperToolsCredential::new(None)?; - let client = KeyClient::new( - "https://your-key-vault-name.vault.azure.net/", - credential.clone(), - None, - )?; - - let mut pager = client.list_key_properties(None)?.into_stream(); - while let Some(key) = pager.try_next().await? { - // Get the key name from the ID. - let name = key.resource_id()?.name; - println!("Found Key with Name: {}", name); - } - - Ok(()) +let mut pager = client.list_key_properties(None)?.into_stream(); +while let Some(key) = pager.try_next().await? { + // Get the key name from the ID. + let name = key.resource_id()?.name; + println!("Found Key with Name: {}", name); } ``` @@ -271,79 +229,67 @@ async fn main() -> Result<(), Box> { You can create an asymmetric key in Azure Key Vault (Managed HSM also supports AES symmetric key encryption) and encrypt or decrypt data without the private key ever leaving the HSM. -```rust no_run -use azure_identity::DeveloperToolsCredential; +```rust ignore encrypt_decrypt use azure_security_keyvault_keys::{ models::{ - CreateKeyParameters, KeyClientWrapKeyOptions, KeyClientUnwrapKeyOptions, - KeyOperationParameters, EncryptionAlgorithm, KeyType, + CreateKeyParameters, EncryptionAlgorithm, KeyClientUnwrapKeyOptions, + KeyClientWrapKeyOptions, KeyOperationParameters, KeyType, }, - ResourceExt, KeyClient, + ResourceExt, }; use rand::random; -#[tokio::main] -async fn main() -> Result<(), Box> { - let credential = DeveloperToolsCredential::new(None)?; - let client = KeyClient::new( - "https://your-key-vault-name.vault.azure.net/", - credential.clone(), - None, - )?; - - // Create a key encryption key (KEK) using RSA. - let body = CreateKeyParameters { - kty: Some(KeyType::Rsa), - key_size: Some(2048), - ..Default::default() - }; +// Create a key encryption key (KEK) using RSA. +let body = CreateKeyParameters { + kty: Some(KeyType::Rsa), + key_size: Some(2048), + ..Default::default() +}; - let key = client - .create_key("key-name", body.try_into()?, None) - .await? - .into_model()?; - let key_version = key.resource_id()?.version; - - // Generate a symmetric data encryption key (DEK). You'd encrypt your data using this DEK. - let dek = random::().to_le_bytes().to_vec(); - - // Wrap the DEK. You'd store the wrapped DEK along with your encrypted data. - let mut parameters = KeyOperationParameters { - algorithm: Some(EncryptionAlgorithm::RsaOaep256), - value: Some(dek.clone()), - ..Default::default() - }; - let wrapped = client - .wrap_key( - "key-name", - parameters.clone().try_into()?, - Some(KeyClientWrapKeyOptions { - key_version: key_version.clone(), - ..Default::default() - }), - ) - .await? - .into_model()?; - assert!(matches!(wrapped.result.as_ref(), Some(result) if !result.is_empty())); - - // Unwrap the DEK. - parameters.value = wrapped.result; - let unwrapped = client - .unwrap_key( - "key-name", - parameters.try_into()?, - Some(KeyClientUnwrapKeyOptions { - key_version, - ..Default::default() - }), - ) - .await? - .into_model()?; +let key = client + .create_key("key-name", body.try_into()?, None) + .await? + .into_model()?; +let key_version = key.resource_id()?.version; - assert!(matches!(unwrapped.result, Some(result) if result.eq(&dek))); +// Generate a symmetric data encryption key (DEK). You'd encrypt your data using this DEK. +let dek = random::().to_le_bytes().to_vec(); - Ok(()) -} +// Wrap the DEK. You'd store the wrapped DEK along with your encrypted data. +let mut parameters = KeyOperationParameters { + algorithm: Some(EncryptionAlgorithm::RsaOaep256), + value: Some(dek.clone()), + ..Default::default() +}; +let wrapped = client + .wrap_key( + "key-name", + parameters.clone().try_into()?, + Some(KeyClientWrapKeyOptions { + key_version: key_version.clone(), + ..Default::default() + }), + ) + .await? + .into_model()?; + +assert!(matches!(wrapped.result.as_ref(), Some(result) if !result.is_empty())); + +// Unwrap the DEK. +parameters.value = wrapped.result; +let unwrapped = client + .unwrap_key( + "key-name", + parameters.try_into()?, + Some(KeyClientUnwrapKeyOptions { + key_version, + ..Default::default() + }), + ) + .await? + .into_model()?; + +assert!(matches!(unwrapped.result, Some(result) if result.eq(&dek))); ``` ## Troubleshooting @@ -354,25 +300,10 @@ When you interact with the Azure Key Vault keys client library using the Rust SD For example, if you try to retrieve a key that doesn't exist in your Azure Key Vault, a `404` error is returned, indicating `Not Found`. -```rust no_run -use azure_identity::DeveloperToolsCredential; -use azure_security_keyvault_keys::KeyClient; - -#[tokio::main] -async fn main() -> Result<(), Box> { - let credential = DeveloperToolsCredential::new(None)?; - let client = KeyClient::new( - "https://.vault.azure.net/", - credential.clone(), - None, - )?; - - match client.get_key("key-name".into(), None).await { - Ok(response) => println!("Key: {:#?}", response.into_model()?.key), - Err(err) => println!("Error: {:#?}", err.into_inner()?), - } - - Ok(()) +```rust ignore errors +match client.get_key("key-name".into(), None).await { + Ok(response) => println!("Key: {:#?}", response.into_model()?.key), + Err(err) => println!("Error: {:#?}", err.into_inner()?), } ``` diff --git a/sdk/keyvault/azure_security_keyvault_keys/tests/readme.rs b/sdk/keyvault/azure_security_keyvault_keys/tests/readme.rs new file mode 100644 index 0000000000..53500a1c12 --- /dev/null +++ b/sdk/keyvault/azure_security_keyvault_keys/tests/readme.rs @@ -0,0 +1,94 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +use azure_core::{error::Result, http::StatusCode}; +use azure_core_test::{recorded, TestContext, TestMode}; +use azure_security_keyvault_keys::{KeyClient, KeyClientOptions}; +use include_file::include_markdown; + +#[recorded::test] +async fn readme(ctx: TestContext) -> Result<()> { + use azure_security_keyvault_test::Retry; + + let recording = ctx.recording(); + + let mut options = KeyClientOptions::default(); + recording.instrument(&mut options.client_options); + + let client = KeyClient::new( + recording.var("AZURE_KEYVAULT_URL", None).as_str(), + recording.credential(), + Some(options), + )?; + + // Each macro invocation is in its own block to prevent errors with duplicate imports. + println!("Create a key"); + include_markdown!("README.md", "create_key", scope); + + println!("Get a key"); + include_markdown!("README.md", "get_key", scope); + + println!("Update a key"); + include_markdown!("README.md", "update_key", scope); + + println!("List keys"); + include_markdown!("README.md", "list_keys", scope); + + println!("Encrypt and decrypt"); + rand::seed(recording.random()); + include_markdown!("README.md", "encrypt_decrypt", scope); + + println!("Handle errors"); + include_markdown!("README.md", "errors", scope); + + println!("Delete a key"); + include_markdown!("README.md", "delete_key", scope); + + println!("Purge a key"); + // Because deletes may not happen right away, try purging in a loop. + let mut retry = match recording.test_mode() { + TestMode::Playback => Retry::immediate(), + _ => Retry::progressive(None), + }; + + loop { + match client.purge_deleted_key("key-name", None).await { + Ok(_) => break, + Err(err) if matches!(err.http_status(), Some(StatusCode::Conflict)) => { + if retry.next().await.is_none() { + return Err(err); + } + } + Err(err) => return Err(err), + } + } + + Ok(()) +} + +/// Override `use rand::random` import in README.md to use recorded seed. +mod rand { + // cspell:ignore Seedable + #![allow(static_mut_refs)] + use rand::{ + distr::{Distribution, StandardUniform}, + Rng, SeedableRng, + }; + use rand_chacha::ChaCha20Rng; + use std::sync::OnceLock; + + static mut RNG: OnceLock = OnceLock::new(); + + pub fn random() -> T + where + StandardUniform: Distribution, + { + unsafe { RNG.get_mut().expect("expected ChaCha20 rng").random() } + } + + pub fn seed(seed: [u8; 32]) { + unsafe { + RNG.set(ChaCha20Rng::from_seed(seed)).expect("set seed"); + } + } +} diff --git a/sdk/keyvault/azure_security_keyvault_secrets/Cargo.toml b/sdk/keyvault/azure_security_keyvault_secrets/Cargo.toml index dc7d61654f..ddcc2804f6 100644 --- a/sdk/keyvault/azure_security_keyvault_secrets/Cargo.toml +++ b/sdk/keyvault/azure_security_keyvault_secrets/Cargo.toml @@ -31,6 +31,7 @@ azure_core_test = { workspace = true, features = [ ] } azure_identity.workspace = true azure_security_keyvault_test = { path = "../azure_security_keyvault_test" } +include-file.workspace = true rand.workspace = true tokio.workspace = true diff --git a/sdk/keyvault/azure_security_keyvault_secrets/README.md b/sdk/keyvault/azure_security_keyvault_secrets/README.md index 1045d9d6bc..86e3d4332e 100644 --- a/sdk/keyvault/azure_security_keyvault_secrets/README.md +++ b/sdk/keyvault/azure_security_keyvault_secrets/README.md @@ -40,7 +40,7 @@ cargo add azure_identity tokio In order to interact with the Azure Key Vault service, you'll need to create an instance of the `SecretClient`. You need a **vault url**, which you may see as "DNS Name" in the portal, and credentials to instantiate a client object. -The example shown below use a `DeveloperToolsCredential`, which is appropriate for most local development environments. Additionally, we recommend using a managed identity for authentication in production environments. You can find more information on different ways of authenticating and their corresponding credential types in the [Azure Identity] documentation. +The example shown below uses a `DeveloperToolsCredential`, which is appropriate for local development environments. We recommend using a managed identity for authentication in production environments. You can find more information on different ways of authenticating and their corresponding credential types in the [Azure Identity] documentation. The `DeveloperToolsCredential` will automatically pick up on an Azure CLI authentication. Ensure you are logged in with the Azure CLI: @@ -50,14 +50,11 @@ az login Instantiate a `DeveloperToolsCredential` to pass to the client. The same instance of a token credential can be used with multiple clients if they will be authenticating with the same identity. -### Set and Get a Secret +### Instantiate a client ```rust no_run use azure_identity::DeveloperToolsCredential; -use azure_security_keyvault_secrets::{ - models::{Secret, SecretClientGetSecretOptions, SetSecretParameters}, - ResourceExt, SecretClient, -}; +use azure_security_keyvault_secrets::SecretClient; #[tokio::main] async fn main() -> Result<(), Box> { @@ -69,29 +66,12 @@ async fn main() -> Result<(), Box> { None, )?; - // Create a new secret using the secret client. - let secret_set_parameters = SetSecretParameters { - value: Some("secret-value".into()), - ..Default::default() - }; - - let secret: Secret = client - .set_secret("secret-name", secret_set_parameters.try_into()?, None) - .await? - .into_model()?; - - // Get version of created secret. - let secret_version = secret.resource_id()?.version; - - // Retrieve a secret using the secret client. - let secret: Secret = client - .get_secret("secret-name", Some(SecretClientGetSecretOptions { - secret_version, - ..Default::default() - })) + // Get a secret using the secret client. + let secret = client + .get_secret("secret-name", None) .await? .into_model()?; - println!("{:?}", secret.value); + println!("Secret: {:?}", secret.value); Ok(()) } @@ -113,7 +93,7 @@ We guarantee that all client instance methods are thread-safe and independent of ## Examples -The following section provides several code snippets using the `SecretClient`, covering some of the most common Azure Key Vault secrets service related tasks: +The following section provides several code snippets using a `SecretClient` like we [instantiated above](#instantiate-a-client): * [Create a secret](#create-a-secret) * [Retrieve a secret](#retrieve-a-secret) @@ -125,166 +105,98 @@ The following section provides several code snippets using the `SecretClient`, c `set_secret` creates a Key Vault secret to be stored in the Azure Key Vault. If a secret with the same name already exists, then a new version of the secret is created. -```rust no_run -use azure_identity::DeveloperToolsCredential; -use azure_security_keyvault_secrets::{models::SetSecretParameters, ResourceExt, SecretClient}; +```rust ignore create_secret +use azure_security_keyvault_secrets::{models::SetSecretParameters, ResourceExt}; -#[tokio::main] -async fn main() -> Result<(), Box> { - let credential = DeveloperToolsCredential::new(None)?; - let client = SecretClient::new( - "https://your-key-vault-name.vault.azure.net/", - credential.clone(), - None, - )?; - - // Create a new secret using the secret client. - let secret_set_parameters = SetSecretParameters { - value: Some("secret-value".into()), - ..Default::default() - }; - - let secret = client - .set_secret("secret-name", secret_set_parameters.try_into()?, None) - .await? - .into_model()?; - - println!( - "Secret Name: {:?}, Value: {:?}, Version: {:?}", - secret.resource_id()?.name, - secret.value, - secret.resource_id()?.version - ); +// Create a new secret using the secret client. +let secret_set_parameters = SetSecretParameters { + value: Some("secret-value".into()), + ..Default::default() +}; - Ok(()) -} +let secret = client + .set_secret("secret-name", secret_set_parameters.try_into()?, None) + .await? + .into_model()?; + +println!( + "Secret Name: {:?}, Value: {:?}, Version: {:?}", + secret.resource_id()?.name, + secret.value, + secret.resource_id()?.version +); ``` ### Retrieve a secret `get_secret` retrieves a secret previously stored in the Azure Key Vault. Setting the `secret-version` to an empty string will return the latest version. -```rust no_run -use azure_identity::DeveloperToolsCredential; -use azure_security_keyvault_secrets::{SecretClient, models::SecretClientGetSecretOptions}; - -#[tokio::main] -async fn main() -> Result<(), Box> { - let credential = DeveloperToolsCredential::new(None)?; - let client = SecretClient::new( - "https://your-key-vault-name.vault.azure.net/", - credential.clone(), - None, - )?; +```rust ignore get_secret +use azure_security_keyvault_secrets::models::SecretClientGetSecretOptions; - // Retrieve a secret using the secret client. - let get_options = SecretClientGetSecretOptions { - secret_version: Some("secret-version".to_string()), - ..Default::default() - }; - let secret = client - .get_secret("secret-name", None) - .await? - .into_model()?; - - println!("Secret Value: {:?}", secret.value); +// Retrieve a secret using the secret client. +let get_options = SecretClientGetSecretOptions { + secret_version: Some("secret-version".to_string()), + ..Default::default() +}; +let secret = client + .get_secret("secret-name", None) + .await? + .into_model()?; - Ok(()) -} +println!("Secret Value: {:?}", secret.value); ``` ### Update an existing secret `update_secret_properties` updates a secret previously stored in the Azure Key Vault. Only the attributes of the secret are updated. To update the value, call `SecretClient::set_secret` on a secret with the same name. -```rust no_run -use azure_identity::DeveloperToolsCredential; -use azure_security_keyvault_secrets::{models::UpdateSecretPropertiesParameters, SecretClient}; +```rust ignore update_secret +use azure_security_keyvault_secrets::models::UpdateSecretPropertiesParameters; use std::collections::HashMap; -#[tokio::main] -async fn main() -> Result<(), Box> { - let credential = DeveloperToolsCredential::new(None)?; - let client = SecretClient::new( - "https://your-key-vault-name.vault.azure.net/", - credential.clone(), - None, - )?; - - // Update a secret using the secret client. - let secret_update_parameters = UpdateSecretPropertiesParameters { - content_type: Some("text/plain".into()), - tags: Some(HashMap::from_iter(vec![( - "tag-name".into(), - "tag-value".into(), - )])), - ..Default::default() - }; - - client - .update_secret_properties( - "secret-name", - secret_update_parameters.try_into()?, - None, - ) - .await? - .into_model()?; +// Update a secret using the secret client. +let secret_update_parameters = UpdateSecretPropertiesParameters { + content_type: Some("text/plain".into()), + tags: Some(HashMap::from_iter(vec![( + "tag-name".into(), + "tag-value".into(), + )])), + ..Default::default() +}; - Ok(()) -} +client + .update_secret_properties( + "secret-name", + secret_update_parameters.try_into()?, + None, + ) + .await? + .into_model()?; ``` ### Delete a secret `delete_secret` will tell Key Vault to delete a secret but it is not deleted immediately. It will not be deleted until the service-configured data retention period - the default is 90 days - or until you call `purge_secret` on the returned `DeletedSecret.id`. -```rust no_run -use azure_identity::DeveloperToolsCredential; -use azure_security_keyvault_secrets::SecretClient; - -#[tokio::main] -async fn main() -> Result<(), Box> { - let credential = DeveloperToolsCredential::new(None)?; - let client = SecretClient::new( - "https://your-key-vault-name.vault.azure.net/", - credential.clone(), - None, - )?; - - // Delete a secret using the secret client. - client.delete_secret("secret-name", None).await?; - - Ok(()) -} +```rust ignore delete_secret +// Delete a secret using the secret client. +client.delete_secret("secret-name", None).await?; ``` ### List secrets This example lists all the secrets in the specified Azure Key Vault. The value is not returned when listing all secrets. You will need to call `SecretClient::get_secret` to retrieve the value. -```rust no_run -use azure_identity::DeveloperToolsCredential; -use azure_security_keyvault_secrets::{ResourceExt, SecretClient}; +```rust ignore list_secrets +use azure_security_keyvault_secrets::ResourceExt; use futures::TryStreamExt; -#[tokio::main] -async fn main() -> Result<(), Box> { - // Create a new secret client - let credential = DeveloperToolsCredential::new(None)?; - let client = SecretClient::new( - "https://your-key-vault-name.vault.azure.net/", - credential.clone(), - None, - )?; - - let mut pager = client.list_secret_properties(None)?.into_stream(); - while let Some(secret) = pager.try_next().await? { - // Get the secret name from the ID. - let name = secret.resource_id()?.name; - println!("Found Secret with Name: {}", name); - } - - Ok(()) +let mut pager = client.list_secret_properties(None)?.into_stream(); +while let Some(secret) = pager.try_next().await? { + // Get the secret name from the ID. + let name = secret.resource_id()?.name; + println!("Found Secret with Name: {}", name); } ``` @@ -296,25 +208,10 @@ When you interact with the Azure Key Vault secrets client library using the Rust For example, if you try to retrieve a secret that doesn't exist in your Azure Key Vault, a `404` error is returned, indicating `Not Found`. -```rust no_run -use azure_identity::DeveloperToolsCredential; -use azure_security_keyvault_secrets::SecretClient; - -#[tokio::main] -async fn main() -> Result<(), Box> { - let credential = DeveloperToolsCredential::new(None)?; - let client = SecretClient::new( - "https://.vault.azure.net/", - credential.clone(), - None, - )?; - - match client.get_secret("secret-name", None).await { - Ok(response) => println!("Secret Value: {:?}", response.into_model()?.value), - Err(err) => println!("Error: {:#?}", err.into_inner()?), - } - - Ok(()) +```rust ignore errors +match client.get_secret("secret-name", None).await { + Ok(response) => println!("Secret Value: {:?}", response.into_model()?.value), + Err(err) => println!("Error: {:#?}", err.into_inner()?), } ``` diff --git a/sdk/keyvault/azure_security_keyvault_secrets/tests/readme.rs b/sdk/keyvault/azure_security_keyvault_secrets/tests/readme.rs new file mode 100644 index 0000000000..29dab74c38 --- /dev/null +++ b/sdk/keyvault/azure_security_keyvault_secrets/tests/readme.rs @@ -0,0 +1,63 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +use azure_core::{error::Result, http::StatusCode}; +use azure_core_test::{recorded, TestContext, TestMode}; +use azure_security_keyvault_secrets::{SecretClient, SecretClientOptions}; +use include_file::include_markdown; + +#[recorded::test] +async fn readme(ctx: TestContext) -> Result<()> { + use azure_security_keyvault_test::Retry; + + let recording = ctx.recording(); + + let mut options = SecretClientOptions::default(); + recording.instrument(&mut options.client_options); + + let client = SecretClient::new( + recording.var("AZURE_KEYVAULT_URL", None).as_str(), + recording.credential(), + Some(options), + )?; + + // Each macro invocation is in its own block to prevent errors with duplicate imports. + println!("Create a secret"); + include_markdown!("README.md", "create_secret", scope); + + println!("Get a secret"); + include_markdown!("README.md", "get_secret", scope); + + println!("Update a secret"); + include_markdown!("README.md", "update_secret", scope); + + println!("List secrets"); + include_markdown!("README.md", "list_secrets", scope); + + println!("Handle errors"); + include_markdown!("README.md", "errors", scope); + + println!("Delete a secret"); + include_markdown!("README.md", "delete_secret", scope); + + println!("Purge a secret"); + // Because deletes may not happen right away, try purging in a loop. + let mut retry = match recording.test_mode() { + TestMode::Playback => Retry::immediate(), + _ => Retry::progressive(None), + }; + + loop { + match client.purge_deleted_secret("secret-name", None).await { + Ok(_) => break, + Err(err) if matches!(err.http_status(), Some(StatusCode::Conflict)) => { + if retry.next().await.is_none() { + return Err(err); + } + } + Err(err) => return Err(err), + } + } + + Ok(()) +}