Skip to content

Commit ac71bb7

Browse files
authored
Merge pull request dapr#144 from zedgell/feature/crypto
Implement Cryptography API
2 parents 372522a + 02dd850 commit ac71bb7

File tree

9 files changed

+318
-17
lines changed

9 files changed

+318
-17
lines changed

.github/workflows/validate-examples.yml

+2-2
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ on:
2222
required: false
2323
default: ""
2424
repository_dispatch:
25-
types: [validate-examples]
25+
types: [ validate-examples ]
2626
merge_group:
2727
jobs:
2828
setup:
@@ -144,7 +144,7 @@ jobs:
144144
fail-fast: false
145145
matrix:
146146
examples:
147-
["actors", "client", "configuration", "invoke/grpc", "invoke/grpc-proxying", "pubsub", "secrets-bulk"]
147+
[ "actors", "client", "configuration", "crypto", "invoke/grpc", "invoke/grpc-proxying", "pubsub", "secrets-bulk" ]
148148
steps:
149149
- name: Check out code
150150
uses: actions/checkout@v4

Cargo.toml

+7-2
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ serde = { version = "1.0", features = ["derive"] }
2323
serde_json = "1.0"
2424
axum = "0.7.4"
2525
tokio = { version = "1.29", features = ["sync"] }
26+
tokio-util = { version = "0.7.10", features = ["io"] }
2627
chrono = "0.4.24"
2728

2829
[build-dependencies]
@@ -45,13 +46,17 @@ path = "examples/actors/client.rs"
4546
name = "actor-server"
4647
path = "examples/actors/server.rs"
4748

49+
[[example]]
50+
name = "client"
51+
path = "examples/client/client.rs"
52+
4853
[[example]]
4954
name = "configuration"
5055
path = "examples/configuration/main.rs"
5156

5257
[[example]]
53-
name = "client"
54-
path = "examples/client/client.rs"
58+
name = "crypto"
59+
path = "examples/crypto/main.rs"
5560

5661
[[example]]
5762
name = "invoke-grpc-client"

examples/crypto/README.md

+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# Crypto Example
2+
3+
This is a simple example that demonstrates Dapr's Cryptography capabilities.
4+
5+
> **Note:** Make sure to use latest version of proto bindings.
6+
7+
## Running
8+
9+
To run this example:
10+
11+
1. Generate keys in examples/crypto/keys directory:
12+
<!-- STEP
13+
name: Generate keys
14+
background: false
15+
sleep: 5
16+
timeout_seconds: 30
17+
-->
18+
```bash
19+
mkdir -p keys
20+
# Generate a private RSA key, 4096-bit keys
21+
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:4096 -out keys/rsa-private-key.pem
22+
# Generate a 256-bit key for AES
23+
openssl rand -out keys/symmetric-key-256 32
24+
```
25+
26+
<!-- END_STEP -->
27+
28+
2. Run the multi-app run template:
29+
30+
<!-- STEP
31+
name: Run multi-app
32+
output_match_mode: substring
33+
match_order: none
34+
expected_stdout_lines:
35+
- '== APP - crypto-example == Successfully Decrypted String'
36+
- '== APP - crypto-example == Successfully Decrypted Image'
37+
background: true
38+
sleep: 30
39+
timeout_seconds: 90
40+
-->
41+
42+
```bash
43+
dapr run -f .
44+
```
45+
46+
<!-- END_STEP -->
47+
48+
2. Stop with `ctrl + c`
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
apiVersion: dapr.io/v1alpha1
2+
kind: Component
3+
metadata:
4+
name: localstorage
5+
spec:
6+
type: crypto.dapr.localstorage
7+
version: v1
8+
metadata:
9+
- name: path
10+
# Path is relative to the folder where the example is located
11+
value: ./keys

examples/crypto/dapr.yaml

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
version: 1
2+
common:
3+
daprdLogDestination: console
4+
apps:
5+
- appID: crypto-example
6+
appDirPath: ./
7+
daprGRPCPort: 35002
8+
logLevel: debug
9+
command: [ "cargo", "run", "--example", "crypto" ]
10+
resourcesPath: ./components

examples/crypto/image.png

3.87 KB
Loading

examples/crypto/main.rs

+81
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
use std::fs;
2+
3+
use tokio::fs::File;
4+
use tokio::time::sleep;
5+
6+
use dapr::client::ReaderStream;
7+
8+
#[tokio::main]
9+
async fn main() -> Result<(), Box<dyn std::error::Error>> {
10+
sleep(std::time::Duration::new(2, 0)).await;
11+
let port: u16 = std::env::var("DAPR_GRPC_PORT")?.parse()?;
12+
let addr = format!("https://127.0.0.1:{}", port);
13+
14+
let mut client = dapr::Client::<dapr::client::TonicClient>::connect(addr).await?;
15+
16+
let encrypted = client
17+
.encrypt(
18+
ReaderStream::new("Test".as_bytes()),
19+
dapr::client::EncryptRequestOptions {
20+
component_name: "localstorage".to_string(),
21+
key_name: "rsa-private-key.pem".to_string(),
22+
key_wrap_algorithm: "RSA".to_string(),
23+
data_encryption_cipher: "aes-gcm".to_string(),
24+
omit_decryption_key_name: false,
25+
decryption_key_name: "rsa-private-key.pem".to_string(),
26+
},
27+
)
28+
.await
29+
.unwrap();
30+
31+
let decrypted = client
32+
.decrypt(
33+
encrypted,
34+
dapr::client::DecryptRequestOptions {
35+
component_name: "localstorage".to_string(),
36+
key_name: "rsa-private-key.pem".to_string(),
37+
},
38+
)
39+
.await
40+
.unwrap();
41+
42+
assert_eq!(String::from_utf8(decrypted).unwrap().as_str(), "Test");
43+
44+
println!("Successfully Decrypted String");
45+
46+
let image = File::open("./image.png").await.unwrap();
47+
48+
let encrypted = client
49+
.encrypt(
50+
ReaderStream::new(image),
51+
dapr::client::EncryptRequestOptions {
52+
component_name: "localstorage".to_string(),
53+
key_name: "rsa-private-key.pem".to_string(),
54+
key_wrap_algorithm: "RSA".to_string(),
55+
data_encryption_cipher: "aes-gcm".to_string(),
56+
omit_decryption_key_name: false,
57+
decryption_key_name: "rsa-private-key.pem".to_string(),
58+
},
59+
)
60+
.await
61+
.unwrap();
62+
63+
let decrypted = client
64+
.decrypt(
65+
encrypted,
66+
dapr::client::DecryptRequestOptions {
67+
component_name: "localstorage".to_string(),
68+
key_name: "rsa-private-key.pem".to_string(),
69+
},
70+
)
71+
.await
72+
.unwrap();
73+
74+
let image = fs::read("./image.png").unwrap();
75+
76+
assert_eq!(decrypted, image);
77+
78+
println!("Successfully Decrypted Image");
79+
80+
Ok(())
81+
}

src/client.rs

+152-5
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
1-
use crate::dapr::dapr::proto::{common::v1 as common_v1, runtime::v1 as dapr_v1};
2-
use prost_types::Any;
31
use std::collections::HashMap;
4-
use tonic::Streaming;
5-
use tonic::{transport::Channel as TonicChannel, Request};
62

7-
use crate::error::Error;
83
use async_trait::async_trait;
4+
use futures::StreamExt;
5+
use prost_types::Any;
96
use serde::{Deserialize, Serialize};
7+
use tokio::io::AsyncRead;
8+
use tonic::codegen::tokio_stream;
9+
use tonic::{transport::Channel as TonicChannel, Request};
10+
use tonic::{Status, Streaming};
11+
12+
use crate::dapr::dapr::proto::{common::v1 as common_v1, runtime::v1 as dapr_v1};
13+
use crate::error::Error;
1014

1115
#[derive(Clone)]
1216
pub struct Client<T>(T);
@@ -379,6 +383,78 @@ impl<T: DaprInterface> Client<T> {
379383
};
380384
self.0.unsubscribe_configuration(request).await
381385
}
386+
387+
/// Encrypt binary data using Dapr. returns Vec<StreamPayload> to be used in decrypt method
388+
///
389+
/// # Arguments
390+
///
391+
/// * `payload` - ReaderStream to the data to encrypt
392+
/// * `request_option` - Encryption request options.
393+
pub async fn encrypt<R>(
394+
&mut self,
395+
payload: ReaderStream<R>,
396+
request_options: EncryptRequestOptions,
397+
) -> Result<Vec<StreamPayload>, Status>
398+
where
399+
R: AsyncRead + Send,
400+
{
401+
// have to have it as a reference for the async move below
402+
let request_options = &Some(request_options);
403+
let requested_items: Vec<EncryptRequest> = payload
404+
.0
405+
.enumerate()
406+
.fold(vec![], |mut init, (i, bytes)| async move {
407+
let stream_payload = StreamPayload {
408+
data: bytes.unwrap().to_vec(),
409+
seq: 0,
410+
};
411+
if i == 0 {
412+
init.push(EncryptRequest {
413+
options: request_options.clone(),
414+
payload: Some(stream_payload),
415+
});
416+
} else {
417+
init.push(EncryptRequest {
418+
options: None,
419+
payload: Some(stream_payload),
420+
});
421+
}
422+
init
423+
})
424+
.await;
425+
self.0.encrypt(requested_items).await
426+
}
427+
428+
/// Decrypt binary data using Dapr. returns Vec<u8>.
429+
///
430+
/// # Arguments
431+
///
432+
/// * `encrypted` - Encrypted data usually returned from encrypted, Vec<StreamPayload>
433+
/// * `options` - Decryption request options.
434+
pub async fn decrypt(
435+
&mut self,
436+
encrypted: Vec<StreamPayload>,
437+
options: DecryptRequestOptions,
438+
) -> Result<Vec<u8>, Status> {
439+
let requested_items: Vec<DecryptRequest> = encrypted
440+
.iter()
441+
.enumerate()
442+
.map(|(i, item)| {
443+
if i == 0 {
444+
DecryptRequest {
445+
options: Some(options.clone()),
446+
payload: Some(item.clone()),
447+
}
448+
} else {
449+
DecryptRequest {
450+
options: None,
451+
payload: Some(item.clone()),
452+
}
453+
}
454+
})
455+
.collect();
456+
self.0.decrypt(requested_items).await
457+
}
382458
}
383459

384460
#[async_trait]
@@ -420,6 +496,11 @@ pub trait DaprInterface: Sized {
420496
&mut self,
421497
request: UnsubscribeConfigurationRequest,
422498
) -> Result<UnsubscribeConfigurationResponse, Error>;
499+
500+
async fn encrypt(&mut self, payload: Vec<EncryptRequest>)
501+
-> Result<Vec<StreamPayload>, Status>;
502+
503+
async fn decrypt(&mut self, payload: Vec<DecryptRequest>) -> Result<Vec<u8>, Status>;
423504
}
424505

425506
#[async_trait]
@@ -535,6 +616,51 @@ impl DaprInterface for dapr_v1::dapr_client::DaprClient<TonicChannel> {
535616
.await?
536617
.into_inner())
537618
}
619+
620+
/// Encrypt binary data using Dapr. returns Vec<StreamPayload> to be used in decrypt method
621+
///
622+
/// # Arguments
623+
///
624+
/// * `payload` - ReaderStream to the data to encrypt
625+
/// * `request_option` - Encryption request options.
626+
async fn encrypt(
627+
&mut self,
628+
request: Vec<EncryptRequest>,
629+
) -> Result<Vec<StreamPayload>, Status> {
630+
let request = Request::new(tokio_stream::iter(request));
631+
let stream = self.encrypt_alpha1(request).await?;
632+
let mut stream = stream.into_inner();
633+
let mut return_data = vec![];
634+
while let Some(resp) = stream.next().await {
635+
if let Ok(resp) = resp {
636+
if let Some(data) = resp.payload {
637+
return_data.push(data)
638+
}
639+
}
640+
}
641+
Ok(return_data)
642+
}
643+
644+
/// Decrypt binary data using Dapr. returns Vec<u8>.
645+
///
646+
/// # Arguments
647+
///
648+
/// * `encrypted` - Encrypted data usually returned from encrypted, Vec<StreamPayload>
649+
/// * `options` - Decryption request options.
650+
async fn decrypt(&mut self, request: Vec<DecryptRequest>) -> Result<Vec<u8>, Status> {
651+
let request = Request::new(tokio_stream::iter(request));
652+
let stream = self.decrypt_alpha1(request).await?;
653+
let mut stream = stream.into_inner();
654+
let mut data = vec![];
655+
while let Some(resp) = stream.next().await {
656+
if let Ok(resp) = resp {
657+
if let Some(mut payload) = resp.payload {
658+
data.append(payload.data.as_mut())
659+
}
660+
}
661+
}
662+
Ok(data)
663+
}
538664
}
539665

540666
/// A request from invoking a service
@@ -614,6 +740,19 @@ pub type UnsubscribeConfigurationResponse = dapr_v1::UnsubscribeConfigurationRes
614740
/// A tonic based gRPC client
615741
pub type TonicClient = dapr_v1::dapr_client::DaprClient<TonicChannel>;
616742

743+
/// Encryption gRPC request
744+
pub type EncryptRequest = crate::dapr::dapr::proto::runtime::v1::EncryptRequest;
745+
746+
/// Decrypt gRPC request
747+
pub type DecryptRequest = crate::dapr::dapr::proto::runtime::v1::DecryptRequest;
748+
749+
/// Encryption request options
750+
pub type EncryptRequestOptions = crate::dapr::dapr::proto::runtime::v1::EncryptRequestOptions;
751+
752+
/// Decryption request options
753+
pub type DecryptRequestOptions = crate::dapr::dapr::proto::runtime::v1::DecryptRequestOptions;
754+
755+
type StreamPayload = crate::dapr::dapr::proto::common::v1::StreamPayload;
617756
impl<K> From<(K, Vec<u8>)> for common_v1::StateItem
618757
where
619758
K: Into<String>,
@@ -626,3 +765,11 @@ where
626765
}
627766
}
628767
}
768+
769+
pub struct ReaderStream<T>(tokio_util::io::ReaderStream<T>);
770+
771+
impl<T: AsyncRead> ReaderStream<T> {
772+
pub fn new(data: T) -> Self {
773+
ReaderStream(tokio_util::io::ReaderStream::new(data))
774+
}
775+
}

0 commit comments

Comments
 (0)