added mRATLS to tonic examples
This commit is contained in:
		
							parent
							
								
									e36329c4be
								
							
						
					
					
						commit
						f2f2d545b3
					
				
							
								
								
									
										28
									
								
								Cargo.toml
									
									
									
									
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										28
									
								
								Cargo.toml
									
									
									
									
									
								
							@ -19,6 +19,18 @@ ring = "0.17" # hash256
 | 
			
		||||
rcgen = "0.13"
 | 
			
		||||
log = "0.4"
 | 
			
		||||
hex = "0.4"
 | 
			
		||||
tokio-rustls = "0.26"
 | 
			
		||||
tower = { version = "0.5", features = ["full"] }
 | 
			
		||||
tower-http = { version = "0.5", features = ["full"] }
 | 
			
		||||
hyper = "1.4.1"
 | 
			
		||||
hyper-util = "0.1.7"
 | 
			
		||||
hyper-rustls = { version = "0.27", features = ["http2"] }
 | 
			
		||||
prost = "0.13"
 | 
			
		||||
 | 
			
		||||
[dependencies.tonic]
 | 
			
		||||
version = "0.12"
 | 
			
		||||
#features = ["rustls-0_23"]
 | 
			
		||||
optional = true
 | 
			
		||||
 | 
			
		||||
[dependencies.actix-web]
 | 
			
		||||
version = "4.3"
 | 
			
		||||
@ -52,17 +64,29 @@ version = "1"
 | 
			
		||||
default-features = false
 | 
			
		||||
features = ["precommit-hook", "run-cargo-test", "run-cargo-clippy"]
 | 
			
		||||
 | 
			
		||||
[build-dependencies]
 | 
			
		||||
tonic-build = "0.12"
 | 
			
		||||
 | 
			
		||||
[features]
 | 
			
		||||
default = []
 | 
			
		||||
occlum = []
 | 
			
		||||
reqwest = ["dep:reqwest"]
 | 
			
		||||
actix-web = ["dep:actix-web", "actix-service", "actix-http"]
 | 
			
		||||
tonic = ["dep:tonic"]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
[[example]]
 | 
			
		||||
name = "server"
 | 
			
		||||
name = "mratls_https_server"
 | 
			
		||||
required-features = ["actix-web"]
 | 
			
		||||
 | 
			
		||||
[[example]]
 | 
			
		||||
name = "client"
 | 
			
		||||
name = "mratls_https_client"
 | 
			
		||||
required-features = ["reqwest"]
 | 
			
		||||
 | 
			
		||||
[[example]]
 | 
			
		||||
name = "mratls_grpcs_server"
 | 
			
		||||
required-features = ["tonic"]
 | 
			
		||||
 | 
			
		||||
[[example]]
 | 
			
		||||
name = "mratls_grpcs_client"
 | 
			
		||||
required-features = ["tonic"]
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										21
									
								
								README.md
									
									
									
									
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										21
									
								
								README.md
									
									
									
									
									
								
							@ -1,6 +1,7 @@
 | 
			
		||||
# Occlum SGX Remote Attestation integrated in TLS connection
 | 
			
		||||
 | 
			
		||||
Steps to test the project:
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
occlum-cargo build --example server --features="occlum,actix-web"
 | 
			
		||||
strip -s target/x86_64-unknown-linux-musl/debug/examples/server
 | 
			
		||||
@ -10,3 +11,23 @@ occlum-cargo build --example client --features="occlum,reqwest"
 | 
			
		||||
strip -s target/x86_64-unknown-linux-musl/debug/examples/client
 | 
			
		||||
./build_client.sh
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## Mutual RATLS examples
 | 
			
		||||
 | 
			
		||||
Examples show how to use the mRATLS (Mutual Remote Attestation TLS) in different situations:
 | 
			
		||||
 | 
			
		||||
* The first example shows how to create mRATLS HTTPS server and client
 | 
			
		||||
* The second example shows how to create mRATLS GRPCs server and client
 | 
			
		||||
 | 
			
		||||
Both the server and the client must be running inside the enclave.
 | 
			
		||||
So during the remote attestation peers, acquire their RA certificates.
 | 
			
		||||
And during the TLS handshake, they verify each other's RA certificates.
 | 
			
		||||
The config allows to whitelist MRENCLAVE, MRSIGNER, PRODID, SVN of the peer.
 | 
			
		||||
 | 
			
		||||
## RATLS examples
 | 
			
		||||
 | 
			
		||||
Example shows how to create RATLS HTTPS server and client.
 | 
			
		||||
The server must be running inside the enclave.
 | 
			
		||||
The client can be running anywhere.
 | 
			
		||||
The server config allows to whitelist the public ec25519 key of the client.
 | 
			
		||||
The client config allows to whitelist MRENCLAVE, MRSIGNER, PRODID, SVN of the server.
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										6
									
								
								build.rs
									
									
									
									
									
										Normal file
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										6
									
								
								build.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,6 @@
 | 
			
		||||
fn main() {
 | 
			
		||||
    tonic_build::configure()
 | 
			
		||||
        .build_server(true)
 | 
			
		||||
        .compile(&["examples/echo.proto"], &["examples"])
 | 
			
		||||
        .unwrap_or_else(|e| panic!("Failed to compile protos {:?}", e));
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										37
									
								
								examples/echo.proto
									
									
									
									
									
										Normal file
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										37
									
								
								examples/echo.proto
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,37 @@
 | 
			
		||||
/*
 | 
			
		||||
 *
 | 
			
		||||
 * Copyright 2018 gRPC authors.
 | 
			
		||||
 *
 | 
			
		||||
 * 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
 | 
			
		||||
 *
 | 
			
		||||
 *     http://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.
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
syntax = "proto3";
 | 
			
		||||
 | 
			
		||||
package grpc.examples.unaryecho;
 | 
			
		||||
 | 
			
		||||
// EchoRequest is the request for echo.
 | 
			
		||||
message EchoRequest {
 | 
			
		||||
  string message = 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// EchoResponse is the response for echo.
 | 
			
		||||
message EchoResponse {
 | 
			
		||||
  string message = 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Echo is the echo service.
 | 
			
		||||
service Echo {
 | 
			
		||||
  // UnaryEcho is unary echo.
 | 
			
		||||
  rpc UnaryEcho(EchoRequest) returns (EchoResponse) {}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										64
									
								
								examples/mratls_grpcs_client.rs
									
									
									
									
									
										Normal file
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										64
									
								
								examples/mratls_grpcs_client.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,64 @@
 | 
			
		||||
pub mod pb {
 | 
			
		||||
    tonic::include_proto!("/grpc.examples.unaryecho");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
use hyper::Uri;
 | 
			
		||||
use hyper_util::{client::legacy::connect::HttpConnector, rt::TokioExecutor};
 | 
			
		||||
use occlum_ratls::prelude::*;
 | 
			
		||||
use occlum_ratls::RaTlsConfigBuilder;
 | 
			
		||||
use occlum_sgx::SGXMeasurement;
 | 
			
		||||
use pb::{echo_client::EchoClient, EchoRequest};
 | 
			
		||||
use tokio_rustls::rustls::ClientConfig;
 | 
			
		||||
 | 
			
		||||
#[tokio::main]
 | 
			
		||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
 | 
			
		||||
    let mrsigner_hex = "83D719E77DEACA1470F6BAF62A4D774303C899DB69020F9C70EE1DFC08C7CE9E";
 | 
			
		||||
    let mut mrsigner = [0u8; 32];
 | 
			
		||||
    hex::decode_to_slice(mrsigner_hex, &mut mrsigner).expect("mrsigner decoding failed");
 | 
			
		||||
 | 
			
		||||
    let config = RaTlsConfig::new().allow_instance_measurement(
 | 
			
		||||
        InstanceMeasurement::new().with_mrsigners(vec![SGXMeasurement::new(mrsigner)]),
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    let tls = ClientConfig::from_ratls_config(config)
 | 
			
		||||
        .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, format!("{}", e)))?;
 | 
			
		||||
 | 
			
		||||
    let mut http = HttpConnector::new();
 | 
			
		||||
    http.enforce_http(false);
 | 
			
		||||
 | 
			
		||||
    // We have to do some wrapping here to map the request type from
 | 
			
		||||
    // `https://example.com` -> `https://[::1]:50051` because `rustls`
 | 
			
		||||
    // doesn't accept ip's as `ServerName`.
 | 
			
		||||
    let connector = tower::ServiceBuilder::new()
 | 
			
		||||
        .layer_fn(move |s| {
 | 
			
		||||
            let tls = tls.clone();
 | 
			
		||||
 | 
			
		||||
            hyper_rustls::HttpsConnectorBuilder::new()
 | 
			
		||||
                .with_tls_config(tls)
 | 
			
		||||
                .https_or_http()
 | 
			
		||||
                .enable_http2()
 | 
			
		||||
                .wrap_connector(s)
 | 
			
		||||
        })
 | 
			
		||||
        // Since our cert is signed with `example.com` but we actually want to connect
 | 
			
		||||
        // to a local server we will override the Uri passed from the `HttpsConnector`
 | 
			
		||||
        // and map it to the correct `Uri` that will connect us directly to the local server.
 | 
			
		||||
        .map_request(|_| Uri::from_static("https://[::1]:50051"))
 | 
			
		||||
        .service(http);
 | 
			
		||||
 | 
			
		||||
    let client = hyper_util::client::legacy::Client::builder(TokioExecutor::new()).build(connector);
 | 
			
		||||
 | 
			
		||||
    // Using `with_origin` will let the codegenerated client set the `scheme` and
 | 
			
		||||
    // `authority` from the provided `Uri`.
 | 
			
		||||
    let uri = Uri::from_static("https://example.com");
 | 
			
		||||
    let mut client = EchoClient::with_origin(client, uri);
 | 
			
		||||
 | 
			
		||||
    let request = tonic::Request::new(EchoRequest {
 | 
			
		||||
        message: "hello".into(),
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    let response = client.unary_echo(request).await?;
 | 
			
		||||
 | 
			
		||||
    println!("RESPONSE={:?}", response);
 | 
			
		||||
 | 
			
		||||
    Ok(())
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										114
									
								
								examples/mratls_grpcs_server.rs
									
									
									
									
									
										Normal file
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										114
									
								
								examples/mratls_grpcs_server.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,114 @@
 | 
			
		||||
pub mod pb {
 | 
			
		||||
    tonic::include_proto!("/grpc.examples.unaryecho");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
use hyper::server::conn::http2::Builder;
 | 
			
		||||
use hyper_util::{
 | 
			
		||||
    rt::{TokioExecutor, TokioIo},
 | 
			
		||||
    service::TowerToHyperService,
 | 
			
		||||
};
 | 
			
		||||
use pb::{EchoRequest, EchoResponse};
 | 
			
		||||
use std::sync::Arc;
 | 
			
		||||
use tokio::net::TcpListener;
 | 
			
		||||
use tokio_rustls::{
 | 
			
		||||
    rustls::{pki_types::CertificateDer, ServerConfig},
 | 
			
		||||
    TlsAcceptor,
 | 
			
		||||
};
 | 
			
		||||
use tonic::{body::boxed, service::Routes, Request, Response, Status};
 | 
			
		||||
use tower::ServiceBuilder;
 | 
			
		||||
use tower::ServiceExt;
 | 
			
		||||
 | 
			
		||||
use occlum_ratls::prelude::*;
 | 
			
		||||
use occlum_ratls::RaTlsConfigBuilder;
 | 
			
		||||
 | 
			
		||||
#[tokio::main]
 | 
			
		||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
 | 
			
		||||
    let mrsigner_hex = "83D719E77DEACA1470F6BAF62A4D774303C899DB69020F9C70EE1DFC08C7CE9E";
 | 
			
		||||
    let mut mrsigner = [0u8; 32];
 | 
			
		||||
    hex::decode_to_slice(mrsigner_hex, &mut mrsigner).expect("mrsigner decoding failed");
 | 
			
		||||
 | 
			
		||||
    let config = RaTlsConfig::new().allow_instance_measurement(
 | 
			
		||||
        InstanceMeasurement::new().with_mrsigners(vec![SGXMeasurement::new(mrsigner)]),
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    let mut tls = ServerConfig::from_ratls_config(config)
 | 
			
		||||
        .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, format!("{}", e)))?;
 | 
			
		||||
    tls.alpn_protocols = vec![b"h2".to_vec()];
 | 
			
		||||
 | 
			
		||||
    let server = EchoServer::default();
 | 
			
		||||
 | 
			
		||||
    let svc = Routes::new(pb::echo_server::EchoServer::new(server)).prepare();
 | 
			
		||||
 | 
			
		||||
    let http = Builder::new(TokioExecutor::new());
 | 
			
		||||
 | 
			
		||||
    let listener = TcpListener::bind("[::1]:50051").await?;
 | 
			
		||||
    let tls_acceptor = TlsAcceptor::from(Arc::new(tls));
 | 
			
		||||
 | 
			
		||||
    loop {
 | 
			
		||||
        let (conn, addr) = match listener.accept().await {
 | 
			
		||||
            Ok(incoming) => incoming,
 | 
			
		||||
            Err(e) => {
 | 
			
		||||
                eprintln!("Error accepting connection: {}", e);
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        let http = http.clone();
 | 
			
		||||
        let tls_acceptor = tls_acceptor.clone();
 | 
			
		||||
        let svc = svc.clone();
 | 
			
		||||
 | 
			
		||||
        tokio::spawn(async move {
 | 
			
		||||
            let mut certificates = Vec::new();
 | 
			
		||||
 | 
			
		||||
            let conn = tls_acceptor
 | 
			
		||||
                .accept_with(conn, |info| {
 | 
			
		||||
                    if let Some(certs) = info.peer_certificates() {
 | 
			
		||||
                        for cert in certs {
 | 
			
		||||
                            certificates.push(cert.clone());
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                })
 | 
			
		||||
                .await
 | 
			
		||||
                .unwrap();
 | 
			
		||||
 | 
			
		||||
            let extension_layer =
 | 
			
		||||
                tower_http::add_extension::AddExtensionLayer::new(Arc::new(ConnInfo {
 | 
			
		||||
                    addr,
 | 
			
		||||
                    certificates,
 | 
			
		||||
                }));
 | 
			
		||||
            let svc = ServiceBuilder::new().layer(extension_layer).service(svc);
 | 
			
		||||
 | 
			
		||||
            http.serve_connection(
 | 
			
		||||
                TokioIo::new(conn),
 | 
			
		||||
                TowerToHyperService::new(svc.map_request(|req: hyper::Request<_>| req.map(boxed))),
 | 
			
		||||
            )
 | 
			
		||||
            .await
 | 
			
		||||
            .unwrap();
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug)]
 | 
			
		||||
struct ConnInfo {
 | 
			
		||||
    addr: std::net::SocketAddr,
 | 
			
		||||
    certificates: Vec<CertificateDer<'static>>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type EchoResult<T> = Result<Response<T>, Status>;
 | 
			
		||||
 | 
			
		||||
#[derive(Default)]
 | 
			
		||||
pub struct EchoServer {}
 | 
			
		||||
 | 
			
		||||
#[tonic::async_trait]
 | 
			
		||||
impl pb::echo_server::Echo for EchoServer {
 | 
			
		||||
    async fn unary_echo(&self, request: Request<EchoRequest>) -> EchoResult<EchoResponse> {
 | 
			
		||||
        let conn_info = request.extensions().get::<Arc<ConnInfo>>().unwrap();
 | 
			
		||||
        println!(
 | 
			
		||||
            "Got a request from: {:?} with certs: {:?}",
 | 
			
		||||
            conn_info.addr, conn_info.certificates
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        let message = request.into_inner().message;
 | 
			
		||||
        Ok(Response::new(EchoResponse { message }))
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -2,3 +2,4 @@
 | 
			
		||||
pub mod actix_web;
 | 
			
		||||
#[cfg(feature = "reqwest")]
 | 
			
		||||
pub mod reqwest;
 | 
			
		||||
//mod tonic_server;
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										97
									
								
								src/http/tonic_server.rs
									
									
									
									
									
										Normal file
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										97
									
								
								src/http/tonic_server.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,97 @@
 | 
			
		||||
use hyper::server::conn::http2::Builder;
 | 
			
		||||
use hyper_util::{
 | 
			
		||||
    rt::{TokioExecutor, TokioIo},
 | 
			
		||||
    service::TowerToHyperService,
 | 
			
		||||
};
 | 
			
		||||
use std::sync::Arc;
 | 
			
		||||
use tokio::net::TcpListener;
 | 
			
		||||
use tokio_rustls::{
 | 
			
		||||
    rustls::{pki_types::CertificateDer, ServerConfig},
 | 
			
		||||
    TlsAcceptor,
 | 
			
		||||
};
 | 
			
		||||
use tonic::transport::server::TcpIncoming;
 | 
			
		||||
use tonic::{body::boxed, service::Routes, Request, Response, Status};
 | 
			
		||||
use tower::ServiceExt;
 | 
			
		||||
use tower_http::ServiceBuilderExt;
 | 
			
		||||
 | 
			
		||||
use crate::{config::RaTlsConfig, RaTlsConfigBuilder};
 | 
			
		||||
 | 
			
		||||
fn bind_ratls(config: RaTlsConfig) -> Result<(), std::io::Error> {
 | 
			
		||||
    let config = ServerConfig::from_ratls_config(config)
 | 
			
		||||
        .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, format!("{}", e)))?;
 | 
			
		||||
    let tls_acceptor = TlsAcceptor::from(Arc::new(config));
 | 
			
		||||
 | 
			
		||||
    // add tls_acceptor to the server
 | 
			
		||||
    let incoming = TcpIncoming::new("[::1]:50051".parse().unwrap(), true, None)?;
 | 
			
		||||
 | 
			
		||||
    let svc = tower::ServiceBuilder::new()
 | 
			
		||||
        .add_extension(Arc::new(ConnInfo { addr, certificates }))
 | 
			
		||||
        .service(svc);
 | 
			
		||||
 | 
			
		||||
    self.bind_rustls_0_23(addr, config)
 | 
			
		||||
}
 | 
			
		||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
 | 
			
		||||
    let listener = TcpListener::bind("[::1]:50051").await?;
 | 
			
		||||
    let tls_acceptor = TlsAcceptor::from(Arc::new(tls));
 | 
			
		||||
 | 
			
		||||
    loop {
 | 
			
		||||
        let (conn, addr) = match listener.accept().await {
 | 
			
		||||
            Ok(incoming) => incoming,
 | 
			
		||||
            Err(e) => {
 | 
			
		||||
                eprintln!("Error accepting connection: {}", e);
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        let http = http.clone();
 | 
			
		||||
        let tls_acceptor = tls_acceptor.clone();
 | 
			
		||||
        let svc = svc.clone();
 | 
			
		||||
 | 
			
		||||
        tokio::spawn(async move {
 | 
			
		||||
            let mut certificates = Vec::new();
 | 
			
		||||
 | 
			
		||||
            let conn = tls_acceptor
 | 
			
		||||
                .accept_with(conn, |info| {
 | 
			
		||||
                    if let Some(certs) = info.peer_certificates() {
 | 
			
		||||
                        for cert in certs {
 | 
			
		||||
                            certificates.push(cert.clone());
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                })
 | 
			
		||||
                .await
 | 
			
		||||
                .unwrap();
 | 
			
		||||
 | 
			
		||||
            http.serve_connection(
 | 
			
		||||
                TokioIo::new(conn),
 | 
			
		||||
                TowerToHyperService::new(svc.map_request(|req: http::Request<_>| req.map(boxed))),
 | 
			
		||||
            )
 | 
			
		||||
            .await
 | 
			
		||||
            .unwrap();
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug)]
 | 
			
		||||
struct ConnInfo {
 | 
			
		||||
    addr: std::net::SocketAddr,
 | 
			
		||||
    certificates: Vec<CertificateDer<'static>>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type EchoResult<T> = Result<Response<T>, Status>;
 | 
			
		||||
 | 
			
		||||
#[derive(Default)]
 | 
			
		||||
pub struct EchoServer {}
 | 
			
		||||
 | 
			
		||||
#[tonic::async_trait]
 | 
			
		||||
impl pb::echo_server::Echo for EchoServer {
 | 
			
		||||
    async fn unary_echo(&self, request: Request<EchoRequest>) -> EchoResult<EchoResponse> {
 | 
			
		||||
        let conn_info = request.extensions().get::<Arc<ConnInfo>>().unwrap();
 | 
			
		||||
        println!(
 | 
			
		||||
            "Got a request from: {:?} with certs: {:?}",
 | 
			
		||||
            conn_info.addr, conn_info.certificates
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        let message = request.into_inner().message;
 | 
			
		||||
        Ok(Response::new(EchoResponse { message }))
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user