Skip to content

Commit

Permalink
Add support for gRPC TLS
Browse files Browse the repository at this point in the history
  • Loading branch information
zdz committed Feb 15, 2024
1 parent 4267f6b commit 198fabd
Show file tree
Hide file tree
Showing 8 changed files with 148 additions and 46 deletions.
52 changes: 32 additions & 20 deletions client/src/grpc.rs
Original file line number Diff line number Diff line change
@@ -1,34 +1,19 @@
// #![allow(unused)]
use std::net::ToSocketAddrs;
use std::str::FromStr;
use std::thread;
use std::time::Duration;
use tonic::transport::Channel;
use tonic::transport::{Certificate, Channel, ClientTlsConfig, Identity};
use tonic::{metadata::MetadataValue, Request};
use tower::timeout::Timeout;
use url::Url;

use stat_common::server_status::server_status_client::ServerStatusClient;
use stat_common::server_status::StatRequest;

use crate::sample_all;
use crate::Args;

// TODO mTLS

pub async fn report(args: &Args, stat_base: &mut StatRequest) -> anyhow::Result<()> {
if ![stat_base.online4, stat_base.online6].iter().any(|&x| x) {
eprintln!("try get target network...");
let addr = args.addr.replace("grpc://", "");
let sock_addr = addr.to_socket_addrs()?.next().unwrap();

stat_base.online4 = sock_addr.is_ipv4();
stat_base.online6 = sock_addr.is_ipv6();

eprintln!(
"get target network (ipv4, ipv6) => ({}, {})",
stat_base.online4, stat_base.online6
);
}

let auth_user: String;
let ssr_auth: &[u8];
if args.gid.is_empty() {
Expand All @@ -40,9 +25,35 @@ pub async fn report(args: &Args, stat_base: &mut StatRequest) -> anyhow::Result<
}
let token = MetadataValue::try_from(format!("{}@_@{}", auth_user, args.pass))?;

let channel = Channel::from_shared(args.addr.to_string())?.connect().await?;
let timeout_channel = Timeout::new(channel, Duration::from_millis(3000));
let addr = args.addr.replace("grpcs://", "https://");
let channel: Channel;
// mTLS
if args.mtls {
let u = Url::parse(addr.as_str())?;

let tls_dir = std::path::PathBuf::from_str(&args.tls_dir)?;
let ca = std::fs::read_to_string(tls_dir.join("ca.pem"))?;
let client_cert = std::fs::read_to_string(tls_dir.join("client.pem"))?;
let client_key = std::fs::read_to_string(tls_dir.join("client.key"))?;
let client_identity = Identity::from_pem(client_cert, client_key);
let ca = Certificate::from_pem(ca);

let tls = ClientTlsConfig::new()
.domain_name(u.host_str().expect("invalid domain"))
.ca_certificate(ca)
.identity(client_identity);
channel = Channel::from_shared(addr)?.tls_config(tls)?.connect().await?;
} else {
// TLS
if addr.starts_with("https://") {
let tls = ClientTlsConfig::new();
channel = Channel::from_shared(addr)?.tls_config(tls)?.connect().await?;
} else {
channel = Channel::from_shared(addr)?.connect().await?;
}
}

let timeout_channel = Timeout::new(channel, Duration::from_millis(3000));
let grpc_client = ServerStatusClient::with_interceptor(timeout_channel, move |mut req: Request<()>| {
req.metadata_mut().insert("authorization", token.clone());
req.metadata_mut()
Expand All @@ -54,6 +65,7 @@ pub async fn report(args: &Args, stat_base: &mut StatRequest) -> anyhow::Result<
loop {
let stat_rt = sample_all(args, stat_base);
let mut client = grpc_client.clone();

tokio::spawn(async move {
let request = tonic::Request::new(stat_rt);

Expand Down
24 changes: 14 additions & 10 deletions client/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,14 @@ pub struct Args {
help = "ipv6 check address"
)]
ipv6_address: String,
#[arg(
short = 'o',
long,
env = "SSR_ONLINE",
default_value = "0",
help = "online 1:ipv4, 2:ipv6, 3:both"
)]
online: u8,
#[arg(long = "json", help = "use json protocol, default:false")]
json: bool,
#[arg(short = '6', long = "ipv6", help = "ipv6 only, default:false")]
Expand All @@ -134,18 +142,14 @@ pub struct Args {
disable_notify: bool,
#[arg(short = 't', long = "type", env = "SSR_TYPE", default_value = "", help = "host type")]
host_type: String,
#[arg(long = "mtls", env = "SSR_MTLS", help = "enable mTLS, default:false")]
mtls: bool,
#[arg(long, env = "SSR_TLS_DIR", default_value = "tls", help = "tls certs dir")]
tls_dir: String,
#[arg(long, env = "SSR_LOC", default_value = "", help = "location")]
location: String,
#[arg(short = 'd', long = "debug", env = "SSR_DEBUG", help = "debug mode, default:false")]
debug: bool,
#[arg(
short = 'o',
long,
env = "SSR_ONLINE",
default_value = "0",
help = "online 1:ipv4, 2:ipv6, 3:both"
)]
online: u8,
#[arg(
short = 'i',
long = "iface",
Expand Down Expand Up @@ -280,7 +284,7 @@ fn http_report(args: &Args, stat_base: &mut StatRequest) -> Result<()> {
.post(&url)
.basic_auth(auth_user, Some(auth_pass))
.timeout(Duration::from_secs(3))
.header(header::CONTENT_TYPE, content_type)
.header(header::CONTENT_TYPE.as_str(), content_type)
.header("ssr-auth", ssr_auth)
.body(body_data.unwrap())
.send()
Expand Down Expand Up @@ -419,7 +423,7 @@ async fn main() -> Result<()> {
let result = http_report(&args, &mut stat_base);
dbg!(&result);
} else if args.addr.starts_with("grpc") {
let result = grpc::report(&args, &mut stat_base).await;
let result = { grpc::report(&args, &mut stat_base).await };
dbg!(&result);
} else {
eprint!("invalid addr scheme!");
Expand Down
10 changes: 8 additions & 2 deletions config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ http_addr = "0.0.0.0:8080"
# 默认30s无上报判定下线
offline_threshold = 30

# 开启 grpc TLS, 0:关闭 1: TLS 2: mTLS
grpc_tls = 0
# 证书最终路径 ${workspace}/${tls_dir}, 包含 server.pem, server.key 文件
tls_dir = "tls"

# 管理员账号,不设置默认随机生成,用于查看 /detail, /map
jwt_secret = "" # 修改这个, 使用 openssl rand -base64 16 生成 secret
admin_user = ""
Expand All @@ -18,9 +23,9 @@ admin_pass = ""
# 或国家缩写,如 cn us 等等,所有国家见目录 web/static/flags
# 自定义标签 labels = "os=centos;ndd=2022/11/25;spec=2C/4G/60G;"
# os 标签可选,不填则使用上报数据,ndd(next due date) 下次续费时间, spec 为主机规格
# os 可用值 centos debian ubuntu alpine pi arch windows linux macos android
# os 可用值 centos debian ubuntu alpine pi arch windows linux macos android freebsd
hosts = [
{name = "h1", password = "p1", alias = "n1", location = "🏠", type = "kvm", labels = "os=arch;ndd=2022/11/25;spec=2C/4G/60G;"},
{name = "h1", password = "p1", alias = "n1", location = "🏠", type = "kvm", labels = "os=freebsd;ndd=2022/11/25;spec=2C/4G/60G;"},
{name = "h2", password = "p2", alias = "n2", location = "🏢", type = "kvm", disabled = false},
{name = "h3", password = "p3", alias = "n3", location = "🏡", type = "kvm", monthstart = 1},
{name = "h4", password = "p4", alias = "n4", location = "cn", type = "kvm", notify = true, labels = "ndd=2022/11/25;spec=2C/4G/60G;"},
Expand All @@ -43,6 +48,7 @@ hosts_group = [
{gid = "silent", password = "pp", location = "🏡", type = "kvm", notify = false},
]
# 动态注册模式下,无效数据清理间隔,默认 30s
# 这个设置要比较通知间隔 notify_interval 大,不然收不到告警通知
group_gc = 30

# !!! 一键部署如果没问题则不需要动,Server 会自行根据你的域名生成 server_url
Expand Down
12 changes: 12 additions & 0 deletions example.env
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,15 @@ SSR_WEIGHT=0
SSR_CM_ADDR=cm.tz.cloudcpp.com:80
SSR_CT_ADDR=ct.tz.cloudcpp.com:80
SSR_CU_ADDR=cu.tz.cloudcpp.com:80

SSR_IPV4_ADDRESS=ipv4.google.com:80
SSR_IPV6_ADDRESS=ipv6.google.com:80

# ip-api.com / ip.sb / ipapi.co / myip.la
SSR_IP_SOURCE=ip-api.com

# 1:ipv4, 2:ipv6, 3:both
SSR_ONLINE=3

SSR_MTLS=false
SSR_TLS_DIR=tls
33 changes: 33 additions & 0 deletions scripts/gen_certs.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#!/bin/bash
set -ex

\rm -rf *.pem *.key *.srl *.csr ssr.ext

cat <<'EOF' >> ssr.ext
authorityKeyIdentifier=keyid,issuer
basicConstraints=CA:FALSE
subjectAltName = @alt_names
[alt_names]
DNS.1 = *.ssr.rs
DNS.2 = ssr.rs
DNS.3 = *.localhost
DNS.4 = localhost
DNS.5 = ssr.server
IP.1 = 127.0.0.1
EOF


openssl req -x509 -sha256 -nodes -subj "/C=CN/CN=ServerStatusRust" -days 3650 -newkey rsa:4096 \
-keyout ca.key -out ca.pem

openssl req -newkey rsa:4096 -nodes -subj "/C=CN/CN=ServerStatusRust" -keyout client.key -out client.csr
openssl x509 -signkey client.key -in client.csr -req -days 3650 -out client.pem
openssl x509 -req -CA ca.pem -CAkey ca.key -in client.csr -out client.pem -days 3650 \
-CAcreateserial -extfile ssr.ext

openssl req -newkey rsa:4096 -nodes -subj "/C=CN/CN=ServerStatusRust" -keyout server.key -out server.csr
openssl x509 -signkey server.key -in server.csr -req -days 3650 -out server.pem
openssl x509 -req -CA ca.pem -CAkey ca.key -in server.csr -out server.pem -days 3650 \
-CAcreateserial -extfile ssr.ext

\rm -rf *.srl *.csr ssr.ext
7 changes: 7 additions & 0 deletions server/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ fn default_http_addr() -> String {
fn default_workspace() -> String {
"/opt/ServerStatus".to_string()
}
fn default_tls_dir() -> String {
"tls".to_string()
}

#[derive(Debug, Clone, Default, Deserialize, Serialize)]
pub struct Host {
Expand Down Expand Up @@ -104,6 +107,10 @@ pub struct Config {
pub notify_interval: u64,
#[serde(default = "Default::default")]
pub offline_threshold: u64,
#[serde(default = "Default::default")]
pub grpc_tls: u32,
#[serde(default = "default_tls_dir")]
pub tls_dir: String,
// admin user & pass
pub admin_user: Option<String>,
pub admin_pass: Option<String>,
Expand Down
49 changes: 40 additions & 9 deletions server/src/grpc.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
// #![allow(unused)]
use tonic::{transport::Server, Request, Response, Status};
use anyhow::Result;
use std::str::FromStr;
use tonic::{
transport::{Certificate, Identity, Server, ServerTlsConfig},
Request, Response, Status,
};

use stat_common::server_status;
use stat_common::server_status::server_status_server::{ServerStatus, ServerStatusServer};
use stat_common::server_status::StatRequest;

use crate::config::Config;
use crate::G_CONFIG;
use crate::G_STATS_MGR;

Expand Down Expand Up @@ -63,14 +69,39 @@ fn check_auth(req: Request<()>) -> Result<Request<()>, Status> {
}
}

pub async fn serv_grpc(addr: &str) -> anyhow::Result<()> {
let sock_addr = addr.parse().unwrap();
pub async fn serv_grpc(cfg: &Config) -> anyhow::Result<()> {
let sock_addr = cfg.grpc_addr.parse().unwrap();
let sss = ServerStatusSrv::default();
eprintln!("🚀 listening on grpc://{sock_addr}");
let svc = ServerStatusServer::with_interceptor(sss, check_auth);
Server::builder()
.add_service(svc)
.serve(sock_addr)
.await
.map_err(anyhow::Error::new)

if cfg.grpc_tls > 0 {
let mut proto = " + TLS";
let tls_dir = std::path::PathBuf::from_str(&cfg.tls_dir)?;
let cert = std::fs::read_to_string(tls_dir.join("server.pem"))?;
let key = std::fs::read_to_string(tls_dir.join("server.key"))?;
let identity = Identity::from_pem(cert, key);

let mut tls = ServerTlsConfig::new().identity(identity);
if cfg.grpc_tls > 1 {
let ca = Certificate::from_pem(std::fs::read_to_string(tls_dir.join("ca.pem"))?);
tls = tls.client_ca_root(ca);
proto = " + mTLS";
}

eprintln!("🚀 listening on grpc://{sock_addr}{proto}");
Server::builder()
.tls_config(tls)?
.add_service(svc)
.serve(sock_addr)
.await
.map_err(anyhow::Error::new)
} else {
eprintln!("🚀 listening on grpc://{sock_addr}");
Server::builder()
.accept_http1(true)
.add_service(svc)
.serve(sock_addr)
.await
.map_err(anyhow::Error::new)
}
}
7 changes: 2 additions & 5 deletions server/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -181,12 +181,9 @@ async fn main() -> Result<(), anyhow::Error> {
}

// serv grpc
tokio::spawn(async move {
let addr = &*G_CONFIG.get().unwrap().grpc_addr;
grpc::serv_grpc(addr).await
});
tokio::spawn(async move { grpc::serv_grpc(cfg).await });

let http_addr = G_CONFIG.get().unwrap().http_addr.to_string();
let http_addr = cfg.http_addr.to_string();
eprintln!("🚀 listening on http://{http_addr}");

let listener = TcpListener::bind(&http_addr).await.unwrap();
Expand Down

0 comments on commit 198fabd

Please sign in to comment.