diff --git a/g3fcgen/src/frontend/request.rs b/g3fcgen/src/frontend/request.rs index 0cc2918d..cec819ad 100644 --- a/g3fcgen/src/frontend/request.rs +++ b/g3fcgen/src/frontend/request.rs @@ -20,6 +20,7 @@ use anyhow::{anyhow, Context}; use openssl::x509::X509; use rmpv::ValueRef; +use g3_tls_cert::agent::{request_key, request_key_id, response_key_id}; use g3_types::net::TlsServiceType; use super::GeneratedData; @@ -30,69 +31,118 @@ pub(crate) struct Request { pub(crate) cert: Option, } +impl Default for Request { + fn default() -> Self { + Request { + host: Arc::from(""), + service: TlsServiceType::Http, + cert: None, + } + } +} + impl Request { + fn check(&self) -> anyhow::Result<()> { + if self.host.is_empty() { + return Err(anyhow!("no host value set")); + } + Ok(()) + } + + fn set(&mut self, k: ValueRef, v: ValueRef) -> anyhow::Result<()> { + match k { + ValueRef::String(s) => { + let key = s + .as_str() + .ok_or_else(|| anyhow!("invalid string key {k}"))?; + match g3_msgpack::key::normalize(key).as_str() { + request_key::HOST => self + .set_host_value(v) + .context(format!("invalid string value for key {key}")), + request_key::SERVICE => { + self.service = g3_msgpack::value::as_tls_service_type(&v) + .context(format!("invalid tls service type value for key {key}"))?; + Ok(()) + } + request_key::CERT => { + let cert = g3_msgpack::value::as_openssl_certificate(&v) + .context(format!("invalid mimic cert value for key {key}"))?; + self.cert = Some(cert); + Ok(()) + } + _ => Err(anyhow!("invalid key {key}")), + } + } + ValueRef::Integer(i) => { + let key_id = i.as_u64().ok_or_else(|| anyhow!("invalid u64 key {k}"))?; + match key_id { + request_key_id::HOST => self + .set_host_value(v) + .context(format!("invalid host string value for key id {key_id}")), + request_key_id::SERVICE => { + self.service = g3_msgpack::value::as_tls_service_type(&v).context( + format!("invalid tls service type value for key id {key_id}"), + )?; + Ok(()) + } + request_key_id::CERT => { + let cert = g3_msgpack::value::as_openssl_certificate(&v) + .context(format!("invalid mimic cert value for key id {key_id}"))?; + self.cert = Some(cert); + Ok(()) + } + _ => Err(anyhow!("invalid key id {key_id}")), + } + } + _ => Err(anyhow!("unsupported key type: {k}")), + } + } + + fn set_host_value(&mut self, v: ValueRef) -> anyhow::Result<()> { + let host = g3_msgpack::value::as_string(&v)?; + self.host = Arc::from(host); + Ok(()) + } + pub(crate) fn parse_req(mut data: &[u8]) -> anyhow::Result { let v = rmpv::decode::read_value_ref(&mut data) .map_err(|e| anyhow!("invalid req data: {e}"))?; + let mut request = Request::default(); if let ValueRef::Map(map) = v { - let mut host = String::default(); - let mut service = TlsServiceType::Http; - let mut cert = None; - for (k, v) in map { - let key = g3_msgpack::value::as_string(&k)?; - match g3_msgpack::key::normalize(key.as_str()).as_str() { - "host" => { - host = g3_msgpack::value::as_string(&v) - .context(format!("invalid string value for key {key}"))?; - } - "service" => { - service = g3_msgpack::value::as_tls_service_type(&v) - .context(format!("invalid tls service type value for key {key}"))?; - } - "cert" => { - let c = g3_msgpack::value::as_openssl_certificate(&v) - .context(format!("invalid mimic cert value for key {key}"))?; - cert = Some(c); - } - _ => return Err(anyhow!("invalid key {key}")), - } + request.set(k, v)?; } - - if host.is_empty() { - return Err(anyhow!("invalid host value")); - } - Ok(Request { - host: Arc::from(host), - service, - cert, - }) } else { - Err(anyhow!("the req root data type should be map")) + request + .set_host_value(v) + .context("invalid single host string value")?; } + + request.check()?; + Ok(request) } pub(crate) fn encode_rsp(&self, generated: &GeneratedData) -> anyhow::Result> { let map = vec![ ( - ValueRef::String("host".into()), + ValueRef::Integer(response_key_id::HOST.into()), ValueRef::String(self.host.as_ref().into()), ), ( - ValueRef::String("service".into()), + ValueRef::Integer(response_key_id::SERVICE.into()), ValueRef::String(self.service.as_str().into()), ), ( - ValueRef::String("cert".into()), + ValueRef::Integer(response_key_id::CERT_CHAIN.into()), ValueRef::String(generated.cert.as_str().into()), ), ( - ValueRef::String("key".into()), + ValueRef::Integer(response_key_id::PRIVATE_KEY.into()), ValueRef::Binary(&generated.key), ), ( - ValueRef::String("ttl".into()), + ValueRef::Integer(response_key_id::TTL.into()), ValueRef::Integer(generated.ttl.into()), ), ]; diff --git a/g3proxy/doc/configuration/index.rst b/g3proxy/doc/configuration/index.rst index 39007a17..6ffe9659 100644 --- a/g3proxy/doc/configuration/index.rst +++ b/g3proxy/doc/configuration/index.rst @@ -11,9 +11,6 @@ is make up of the following entries: +-----------+----------+-------+------------------------------------------------+ |Key |Type |Reload |Description | +===========+==========+=======+================================================+ -|group_name |Str |no |Process group name, default to be empty, can be | -| | | |overridden by the *-G* command line option. | -+-----------+----------+-------+------------------------------------------------+ |runtime |Map |no |Runtime config, see :doc:`runtime` | +-----------+----------+-------+------------------------------------------------+ |worker |Map [#w]_ |no |An unaided runtime will be started if present. | diff --git a/g3proxy/doc/protocol/helper/cert_generator.rst b/g3proxy/doc/protocol/helper/cert_generator.rst index 4be574c3..8f7aee86 100644 --- a/g3proxy/doc/protocol/helper/cert_generator.rst +++ b/g3proxy/doc/protocol/helper/cert_generator.rst @@ -16,7 +16,8 @@ Both the request and the response are structured data and should be encoded in ` .. _msgpack: https://msgpack.org/ -The root of the request and the response should be a map, we will describe the keys of them in the following. +The root of the request and the response should be a map, the key may be a `key str` or a `key id`, +we will describe the keys of them in the following. request ======= @@ -24,14 +25,14 @@ request host ---- -**required**, **type**: string +**required**, **id**: 1, **type**: string Set the hostname of the target tls server. May be a domain or an IP address. service ------- -**optional**, **type**: string +**optional**, **id**: 2, **type**: string Set the tls service type. It should be returned in response. @@ -42,7 +43,7 @@ Set the tls service type. It should be returned in response. cert ---- -**optional**, **type**: pem string or der binary +**optional**, **id**: 3, **type**: pem string or der binary The real upstream leaf cert in PEM string format or DER binary format. @@ -54,14 +55,14 @@ response host ---- -**required**, **type**: string +**required**, **id**: 1, **type**: string The hostname as specified in the request. service ------- -**optional**, **type**: string +**optional**, **id**: 2, **type**: string Set the tls service type. It should be the same value as in the request. @@ -72,21 +73,21 @@ Set the tls service type. It should be the same value as in the request. cert ---- -**required**, **type**: pem string +**required**, **id**: 3, **type**: pem string The generated fake certificate (chain) in PEM format. key --- -**required**, **type**: pem string or der binary +**required**, **id**: 4, **type**: pem string or der binary The generated fake private key in PEM string format or in DER binary format. ttl --- -**optional**, **type**: u32 +**optional**, **id**: 5, **type**: u32 Set the expire ttl of this response. diff --git a/lib/g3-tls-cert/src/agent/mod.rs b/lib/g3-tls-cert/src/agent/mod.rs index c06511d0..34a5206b 100644 --- a/lib/g3-tls-cert/src/agent/mod.rs +++ b/lib/g3-tls-cert/src/agent/mod.rs @@ -24,6 +24,12 @@ use openssl::x509::X509; use g3_types::net::TlsServiceType; +mod protocol; +pub use protocol::*; + +mod response; +use response::Response; + mod query; use query::QueryRuntime; diff --git a/lib/g3-tls-cert/src/agent/protocol.rs b/lib/g3-tls-cert/src/agent/protocol.rs new file mode 100644 index 00000000..8b5c967e --- /dev/null +++ b/lib/g3-tls-cert/src/agent/protocol.rs @@ -0,0 +1,43 @@ +/* + * Copyright 2024 ByteDance and/or its affiliates. + * + * 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. + */ + +pub mod request_key { + pub const HOST: &str = "host"; + pub const SERVICE: &str = "service"; + pub const CERT: &str = "cert"; +} + +pub mod request_key_id { + pub const HOST: u64 = 1; + pub const SERVICE: u64 = 2; + pub const CERT: u64 = 3; +} + +pub mod response_key { + pub const HOST: &str = "host"; + pub const SERVICE: &str = "service"; + pub const CERT_CHAIN: &str = "cert"; + pub const PRIVATE_KEY: &str = "key"; + pub const TTL: &str = "ttl"; +} + +pub mod response_key_id { + pub const HOST: u64 = 1; + pub const SERVICE: u64 = 2; + pub const CERT_CHAIN: u64 = 3; + pub const PRIVATE_KEY: u64 = 4; + pub const TTL: u64 = 5; +} diff --git a/lib/g3-tls-cert/src/agent/query.rs b/lib/g3-tls-cert/src/agent/query.rs index a0f36804..94387220 100644 --- a/lib/g3-tls-cert/src/agent/query.rs +++ b/lib/g3-tls-cert/src/agent/query.rs @@ -24,14 +24,12 @@ use std::time::Duration; use anyhow::anyhow; use log::warn; -use openssl::pkey::{PKey, Private}; use tokio::io::ReadBuf; use tokio::net::UdpSocket; use g3_io_ext::{EffectiveCacheData, EffectiveQueryHandle}; -use g3_types::net::TlsServiceType; -use super::{CacheQueryKey, CertAgentConfig, FakeCertPair}; +use super::{request_key_id, CacheQueryKey, CertAgentConfig, FakeCertPair, Response}; pub(super) struct QueryRuntime { socket: UdpSocket, @@ -76,16 +74,19 @@ impl QueryRuntime { { let mut map = Vec::with_capacity(3); map.push(( - ValueRef::String("host".into()), + ValueRef::Integer(request_key_id::HOST.into()), ValueRef::String(req.host().into()), )); map.push(( - ValueRef::String("service".into()), + ValueRef::Integer(request_key_id::SERVICE.into()), ValueRef::String(req.service().into()), )); if let Some(cert) = &req.mimic_cert { if let Ok(der) = cert.to_der() { - map.push((ValueRef::String("cert".into()), ValueRef::Binary(&der))); + map.push(( + ValueRef::Integer(request_key_id::CERT.into()), + ValueRef::Binary(&der), + )); let mut buf = Vec::with_capacity(320 + der.len()); if rmpv::encode::write_value_ref(&mut buf, &ValueRef::Map(map)).is_err() { self.send_empty_result(req, false); @@ -104,82 +105,26 @@ impl QueryRuntime { } } - fn parse_rsp( - map: Vec<(rmpv::ValueRef, rmpv::ValueRef)>, - ) -> anyhow::Result<(Arc, FakeCertPair, u32)> { - use anyhow::Context; - - let mut host = String::new(); - let mut certs = Vec::new(); - let mut pkey: Option> = None; - let mut ttl: u32 = 0; - let mut service = TlsServiceType::Http; - - for (k, v) in map { - let key = g3_msgpack::value::as_string(&k)?; - match g3_msgpack::key::normalize(key.as_str()).as_str() { - "host" => { - host = g3_msgpack::value::as_string(&v) - .context(format!("invalid string value for key {key}"))?; - } - "service" => { - service = g3_msgpack::value::as_tls_service_type(&v) - .context(format!("invalid tls service type value for key {key}"))?; - } - "cert" => { - certs = g3_msgpack::value::as_openssl_certificates(&v) - .context(format!("invalid tls certificate value for key {key}"))?; - } - "key" => { - let key = g3_msgpack::value::as_openssl_private_key(&v) - .context(format!("invalid tls private key value for key {key}"))?; - pkey = Some(key); - } - "ttl" => { - ttl = g3_msgpack::value::as_u32(&v) - .context(format!("invalid u32 value for key {key}"))?; - } - _ => {} // ignore unknown keys - } - } - - if host.is_empty() { - return Err(anyhow!("no required host key found")); - } - if certs.is_empty() { - return Err(anyhow!("no required cert key found")); - } - let Some(key) = pkey else { - return Err(anyhow!("no required pkey key found")); - }; - - let host = Arc::from(host); - Ok(( - Arc::new(CacheQueryKey::new(service, host)), - FakeCertPair { certs, key }, - ttl, - )) - } - fn handle_rsp(&mut self, len: usize) { - use rmpv::ValueRef; - let mut buf = &self.read_buffer[..len]; - if let Ok(ValueRef::Map(map)) = rmpv::decode::read_value_ref(&mut buf) { - match Self::parse_rsp(map) { - Ok((req_key, pair, mut ttl)) => { - if ttl == 0 { - ttl = self.protective_ttl; - } else if ttl > self.maximum_ttl { - ttl = self.maximum_ttl; - } + match rmpv::decode::read_value_ref(&mut buf) + .map_err(|e| anyhow!("invalid msgpack response data: {e}")) + .and_then(|v| Response::parse(v, self.protective_ttl)) + .and_then(|r| r.into_parts()) + { + Ok((req_key, pair, mut ttl)) => { + if ttl == 0 { + ttl = self.protective_ttl; + } else if ttl > self.maximum_ttl { + ttl = self.maximum_ttl; + } - let result = EffectiveCacheData::new(pair, ttl, self.vanish_wait); - self.query_handle.send_rsp_data(req_key, result, false); - } - Err(e) => { - warn!("parse cert generator rsp error: {e:?}"); - } + let result = EffectiveCacheData::new(pair, ttl, self.vanish_wait); + self.query_handle + .send_rsp_data(Arc::new(req_key), result, false); + } + Err(e) => { + warn!("parse cert generator rsp error: {e:?}"); } } } diff --git a/lib/g3-tls-cert/src/agent/response.rs b/lib/g3-tls-cert/src/agent/response.rs new file mode 100644 index 00000000..06189551 --- /dev/null +++ b/lib/g3-tls-cert/src/agent/response.rs @@ -0,0 +1,139 @@ +/* + * Copyright 2024 ByteDance and/or its affiliates. + * + * 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. + */ + +use std::sync::Arc; + +use anyhow::{anyhow, Context}; +use openssl::pkey::{PKey, Private}; +use openssl::x509::X509; +use rmpv::ValueRef; + +use g3_types::net::TlsServiceType; + +use super::{response_key, response_key_id, CacheQueryKey, FakeCertPair}; + +pub(super) struct Response { + host: String, + service: TlsServiceType, + certs: Vec, + key: Option>, + ttl: u32, +} + +impl Response { + fn new(protective_ttl: u32) -> Self { + Response { + host: String::default(), + service: TlsServiceType::Http, + certs: Vec::new(), + key: None, + ttl: protective_ttl, + } + } + + fn set(&mut self, k: ValueRef, v: ValueRef) -> anyhow::Result<()> { + match k { + ValueRef::String(s) => { + let key = s + .as_str() + .ok_or_else(|| anyhow!("invalid string key {k}"))?; + match g3_msgpack::key::normalize(key).as_str() { + response_key::HOST => { + self.host = g3_msgpack::value::as_string(&v) + .context(format!("invalid string value for key {key}"))?; + } + response_key::SERVICE => { + self.service = g3_msgpack::value::as_tls_service_type(&v) + .context(format!("invalid tls service type value for key {key}"))?; + } + response_key::CERT_CHAIN => { + self.certs = g3_msgpack::value::as_openssl_certificates(&v) + .context(format!("invalid tls certificate value for key {key}"))?; + } + response_key::PRIVATE_KEY => { + let key = g3_msgpack::value::as_openssl_private_key(&v) + .context(format!("invalid tls private key value for key {key}"))?; + self.key = Some(key); + } + response_key::TTL => { + self.ttl = g3_msgpack::value::as_u32(&v) + .context(format!("invalid u32 value for key {key}"))?; + } + _ => {} // ignore unknown keys + } + } + ValueRef::Integer(i) => { + let key_id = i.as_u64().ok_or_else(|| anyhow!("invalid u64 key {k}"))?; + match key_id { + response_key_id::HOST => { + self.host = g3_msgpack::value::as_string(&v) + .context(format!("invalid string value for key id {key_id}"))?; + } + response_key_id::SERVICE => { + self.service = g3_msgpack::value::as_tls_service_type(&v).context( + format!("invalid tls service type value for key id {key_id}"), + )?; + } + response_key_id::CERT_CHAIN => { + self.certs = g3_msgpack::value::as_openssl_certificates(&v).context( + format!("invalid tls certificate value for key id {key_id}"), + )?; + } + response_key_id::PRIVATE_KEY => { + let key = g3_msgpack::value::as_openssl_private_key(&v).context( + format!("invalid tls private key value for key id {key_id}"), + )?; + self.key = Some(key); + } + response_key_id::TTL => { + self.ttl = g3_msgpack::value::as_u32(&v) + .context(format!("invalid u32 value for key id {key_id}"))?; + } + _ => {} // ignore unknown keys + } + } + _ => return Err(anyhow!("unsupported key type: {k}")), + } + Ok(()) + } + + pub(super) fn parse(v: ValueRef, protective_ttl: u32) -> anyhow::Result { + if let ValueRef::Map(map) = v { + let mut response = Response::new(protective_ttl); + for (k, v) in map { + response.set(k, v)?; + } + Ok(response) + } else { + Err(anyhow!("the response data type should be 'map'")) + } + } + + pub(super) fn into_parts(self) -> anyhow::Result<(CacheQueryKey, FakeCertPair, u32)> { + if self.certs.is_empty() { + return Err(anyhow!("no cert chain set")); + } + let key = self.key.ok_or_else(|| anyhow!("no private key set"))?; + Ok(( + CacheQueryKey::new(self.service, Arc::from(self.host)), + FakeCertPair { + certs: self.certs, + key, + }, + self.ttl, + )) + } +}