Skip to content
Merged
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
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.

1 change: 1 addition & 0 deletions src/storage/examples/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ categories.workspace = true

[dependencies]
anyhow.workspace = true
base64.workspace = true
bytes.workspace = true
clap = { workspace = true, features = ["derive", "std"] }
futures.workspace = true
Expand Down
7 changes: 7 additions & 0 deletions src/storage/examples/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,13 @@ pub async fn run_object_examples(buckets: &mut Vec<String>) -> anyhow::Result<()
tracing::info!("running stream_file_download example");
objects::stream_file_download::sample(&client, &id).await?;

tracing::info!("running generate_encryption_key example");
let csek_key = objects::generate_encryption_key::sample()?;
tracing::info!("running upload_encrypted_file example");
objects::upload_encrypted_file::sample(&client, &id, "csek_file.txt", csek_key.clone()).await?;
tracing::info!("running download_encrypted_file example");
objects::download_encrypted_file::sample(&client, &id, "csek_file.txt", csek_key).await?;

tracing::info!("running list_files example");
objects::list_files::sample(&control, &id).await?;
tracing::info!("running list_files_with_prefix example");
Expand Down
3 changes: 3 additions & 0 deletions src/storage/examples/src/objects.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,11 @@
// limitations under the License.

pub mod delete_file;
pub mod download_encrypted_file;
pub mod generate_encryption_key;
pub mod list_files;
pub mod list_files_with_prefix;
pub mod set_metadata;
pub mod stream_file_download;
pub mod stream_file_upload;
pub mod upload_encrypted_file;
39 changes: 39 additions & 0 deletions src/storage/examples/src/objects/download_encrypted_file.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// [START storage_download_encrypted_file]
use google_cloud_storage::client::Storage;
use google_cloud_storage::model_ext::KeyAes256;
use google_cloud_storage::read_object::ReadObjectResponse;

pub async fn sample(
client: &Storage,
bucket: &str,
object: &str,
encryption_key: KeyAes256,
) -> Result<(), anyhow::Error> {
let mut reader = client
.read_object(format!("projects/_/buckets/{bucket}"), object)
.set_key(encryption_key.clone())
.send()
.await?;

while let Some(data) = reader.next().await.transpose()? {
println!("downloaded {} bytes", data.len())
}

println!("Downloaded {object} in bucket {bucket} with key={encryption_key}.");
Ok(())
}
// [END storage_download_encrypted_file]
30 changes: 30 additions & 0 deletions src/storage/examples/src/objects/generate_encryption_key.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// [START storage_generate_encryption_key]
use google_cloud_storage::{error::KeyAes256Error, model_ext::KeyAes256};
use rand::RngCore;

pub fn sample() -> Result<KeyAes256, KeyAes256Error> {
// Generates a 256 bit (32 byte) AES encryption key and prints the base64 representation.
//
// This is included for demonstration purposes. You should generate your own key.
// Please remember that encryption keys should be handled with a comprehensive security policy.
let mut raw_key_bytes = [0u8; 32];
rand::rng().fill_bytes(&mut raw_key_bytes);
let key = KeyAes256::new(&raw_key_bytes)?;
println!("Sample encryption key: {key}");
Ok(key)
}
// [END storage_generate_encryption_key]
34 changes: 34 additions & 0 deletions src/storage/examples/src/objects/upload_encrypted_file.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// [START storage_upload_encrypted_file]
use google_cloud_storage::client::Storage;
use google_cloud_storage::model_ext::KeyAes256;

pub async fn sample(
client: &Storage,
bucket: &str,
object: &str,
encryption_key: KeyAes256,
) -> Result<(), anyhow::Error> {
let _result = client
.write_object(format!("projects/_/buckets/{bucket}"), object, "top secret")
.set_key(encryption_key.clone())
.send_unbuffered()
.await?;

println!("Uploaded to {object} in bucket {bucket} with key={encryption_key}.",);
Ok(())
}
// [END storage_upload_encrypted_file]
17 changes: 16 additions & 1 deletion src/storage/src/model_ext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
//! ergonomics.

use crate::error::KeyAes256Error;
use base64::{Engine, prelude::BASE64_STANDARD};
use sha2::{Digest, Sha256};

/// ObjectHighlights contains select metadata from a [crate::model::Object].
Expand Down Expand Up @@ -71,7 +72,7 @@ pub struct ObjectHighlights {
pub etag: String,
}

#[derive(Debug)]
#[derive(Debug, Clone)]
/// KeyAes256 represents an AES-256 encryption key used with the
/// Customer-Supplied Encryption Keys (CSEK) feature.
///
Expand Down Expand Up @@ -131,6 +132,12 @@ impl std::convert::From<KeyAes256> for crate::model::CommonObjectRequestParams {
}
}

impl std::fmt::Display for KeyAes256 {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", BASE64_STANDARD.encode(self.key))
}
}

/// Define read ranges for use with [ReadObject].
///
/// # Example: read the first 100 bytes of an object
Expand Down Expand Up @@ -397,4 +404,12 @@ pub(crate) mod tests {
assert_eq!(request.read_offset, 1000);
assert_eq!(request.read_limit, want);
}

#[test]
fn test_key_aes_256_display() -> Result {
let (key, key_base64, _, _) = create_key_helper();
let key_aes_256 = KeyAes256::new(&key)?;
assert_eq!(key_aes_256.to_string(), key_base64);
Ok(())
}
}
Loading