forked from loafle/openapi-generator-original
feat: Support selective ssl/tls backend in rust-server to optionally remove openssl (#22825)
* feat: Support selective ssl/tls backend in rust-server to avoid always requiring openssl * feat: Switch default features so a user must select SSL backend * Further tweaks to rust-server HTTPS feature flagging
This commit is contained in:
@@ -32,7 +32,7 @@ homepage = "{{.}}"
|
||||
{{/homePageUrl}}
|
||||
|
||||
[features]
|
||||
default = ["client", "server"]
|
||||
default = ["client", "server", "client-tls"]
|
||||
client = [
|
||||
{{#apiUsesMultipartFormData}}
|
||||
"multipart", "multipart/client", "swagger/multipart_form",
|
||||
@@ -47,7 +47,19 @@ client = [
|
||||
"serde_ignored", "percent-encoding", {{^apiUsesByteArray}}"lazy_static", "regex",{{/apiUsesByteArray}}
|
||||
{{/hasCallbacks}}
|
||||
{{! Anything added to the list below, should probably be added to the callbacks list below }}
|
||||
"hyper", "percent-encoding", "hyper-util/http1", "hyper-util/http2", "hyper-openssl", "hyper-tls", "native-tls", "openssl", "url"
|
||||
"hyper", "percent-encoding", "hyper-util/http1", "hyper-util/http2", "url"
|
||||
]
|
||||
# TLS support - automatically selects backend based on target OS:
|
||||
# - macOS/Windows/iOS: native-tls via hyper-tls
|
||||
# - Other platforms: OpenSSL via hyper-openssl
|
||||
# Dependencies are in target-specific sections below
|
||||
client-tls = [
|
||||
"client",
|
||||
"dep:native-tls",
|
||||
"dep:hyper-tls",
|
||||
"dep:openssl",
|
||||
"dep:hyper-openssl",
|
||||
"swagger/tls"
|
||||
]
|
||||
server = [
|
||||
{{#apiUsesMultipartFormData}}
|
||||
@@ -57,7 +69,7 @@ server = [
|
||||
"mime_multipart", "swagger/multipart_related",
|
||||
{{/apiUsesMultipartRelated}}
|
||||
{{#hasCallbacks}}
|
||||
"native-tls", "hyper-openssl", "hyper-tls", "openssl",
|
||||
"hyper-util/http1", "hyper-util/http2",
|
||||
{{/hasCallbacks}}
|
||||
{{! Anything added to the list below, should probably be added to the callbacks list above }}
|
||||
"serde_ignored", "hyper", "percent-encoding", "url",
|
||||
@@ -74,20 +86,12 @@ conversion = ["frunk", "frunk_derives", "frunk_core", "frunk-enum-core", "frunk-
|
||||
mock = ["mockall"]
|
||||
validate = [{{^apiUsesByteArray}}"regex",{{/apiUsesByteArray}} "serde_valid", "swagger/serdevalid"]
|
||||
|
||||
[target.'cfg(any(target_os = "macos", target_os = "windows", target_os = "ios"))'.dependencies]
|
||||
native-tls = { version = "0.2", optional = true }
|
||||
hyper-tls = { version = "0.6", optional = true }
|
||||
|
||||
[target.'cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))'.dependencies]
|
||||
hyper-openssl = { version = "0.10", optional = true }
|
||||
openssl = { version = "0.10", optional = true }
|
||||
|
||||
[dependencies]
|
||||
# Common
|
||||
async-trait = "0.1.88"
|
||||
chrono = { version = "0.4", features = ["serde"] }
|
||||
futures = "0.3"
|
||||
swagger = { version = "7.0.0", features = ["serdejson", "server", "client", "tls"] }
|
||||
swagger = { version = "7.0.0", features = ["serdejson", "server", "client"] }
|
||||
headers = "0.4.0"
|
||||
log = "0.4.27"
|
||||
|
||||
@@ -157,6 +161,17 @@ frunk_core = { version = "0.4.3", optional = true }
|
||||
frunk-enum-derive = { version = "0.3.0", optional = true }
|
||||
frunk-enum-core = { version = "0.3.0", optional = true }
|
||||
|
||||
# TLS dependencies - platform-specific backends
|
||||
# On macOS/Windows/iOS, use native-tls via hyper-tls
|
||||
[target.'cfg(any(target_os = "macos", target_os = "windows", target_os = "ios"))'.dependencies]
|
||||
native-tls = { version = "0.2", optional = true }
|
||||
hyper-tls = { version = "0.6", optional = true }
|
||||
|
||||
# On other platforms (Linux, etc.), use OpenSSL via hyper-openssl
|
||||
[target.'cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))'.dependencies]
|
||||
openssl = { version = "0.10", optional = true }
|
||||
hyper-openssl = { version = "0.10", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
always_send = "0.1.1"
|
||||
clap = "4.5"
|
||||
@@ -169,8 +184,8 @@ pin-project = "1.1.10"
|
||||
jsonwebtoken = {version = "10.0.0", features = ["rust_crypto"]}
|
||||
|
||||
[target.'cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))'.dev-dependencies]
|
||||
tokio-openssl = "0.6"
|
||||
openssl = "0.10"
|
||||
tokio-openssl = "0.6"
|
||||
|
||||
[[example]]
|
||||
name = "client"
|
||||
|
||||
@@ -126,6 +126,10 @@ The generated library has a few optional features that can be activated through
|
||||
* `client`
|
||||
* This defaults to enabled and creates the basic skeleton of a client implementation based on hyper
|
||||
* The constructed client implements the API trait by making remote API call.
|
||||
* `client-tls`
|
||||
* This default to enabled and provides HTTPS support with automatic TLS backend selection:
|
||||
- macOS/Windows/iOS: native-tls + hyper-tls
|
||||
- Linux/Unix/others: OpenSSL + hyper-openssl
|
||||
* `conversions`
|
||||
* This defaults to disabled and creates extra derives on models to allow "transmogrification" between objects of structurally similar types.
|
||||
* `cli`
|
||||
@@ -134,6 +138,25 @@ The generated library has a few optional features that can be activated through
|
||||
* This defaults to disabled and allows JSON Schema validation of received data using `MakeService::set_validation` or `Service::set_validation`.
|
||||
* Note, enabling validation will have a performance penalty, especially if the API heavily uses regex based checks.
|
||||
|
||||
### HTTPS/TLS Support
|
||||
|
||||
HTTPS support is included by default. To disable it (for example, to reduce dependencies), you can:
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
{{{packageName}}} = { version = "{{{packageVersion}}}", default-features = false, features = ["client", "server"] }
|
||||
```
|
||||
|
||||
**For server with callbacks that need HTTPS:**
|
||||
```toml
|
||||
[dependencies]
|
||||
{{{packageName}}} = { version = "{{{packageVersion}}}", features = ["server", "client-tls"] }
|
||||
```
|
||||
|
||||
The TLS backend is automatically selected based on your target platform:
|
||||
- **macOS, Windows, iOS**: Uses `native-tls` (system TLS libraries)
|
||||
- **Linux, Unix, other platforms**: Uses `openssl`
|
||||
|
||||
See https://doc.rust-lang.org/cargo/reference/manifest.html#the-features-section for how to use features in your `Cargo.toml`.
|
||||
|
||||
## Documentation for API Endpoints
|
||||
|
||||
@@ -45,17 +45,17 @@ struct Cli {
|
||||
server_address: String,
|
||||
|
||||
/// Path to the client private key if using client-side TLS authentication
|
||||
#[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))]
|
||||
#[cfg(all(feature = "client-tls", not(any(target_os = "macos", target_os = "windows", target_os = "ios"))))]
|
||||
#[clap(long, requires_all(&["client_certificate", "server_certificate"]))]
|
||||
client_key: Option<String>,
|
||||
|
||||
/// Path to the client's public certificate associated with the private key
|
||||
#[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))]
|
||||
#[cfg(all(feature = "client-tls", not(any(target_os = "macos", target_os = "windows", target_os = "ios"))))]
|
||||
#[clap(long, requires_all(&["client_key", "server_certificate"]))]
|
||||
client_certificate: Option<String>,
|
||||
|
||||
/// Path to CA certificate used to authenticate the server
|
||||
#[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))]
|
||||
#[cfg(all(feature = "client-tls", not(any(target_os = "macos", target_os = "windows", target_os = "ios"))))]
|
||||
#[clap(long)]
|
||||
server_certificate: Option<String>,
|
||||
|
||||
@@ -130,7 +130,8 @@ enum Operation {
|
||||
{{/apiInfo}}
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))]
|
||||
// On Linux/Unix with OpenSSL (client-tls feature), support certificate pinning and mutual TLS
|
||||
#[cfg(all(feature = "client-tls", not(any(target_os = "macos", target_os = "windows", target_os = "ios"))))]
|
||||
fn create_client(args: &Cli, context: ClientContext) -> Result<Box<dyn ApiNoContext<ClientContext>>> {
|
||||
if args.client_certificate.is_some() {
|
||||
debug!("Using mutual TLS");
|
||||
@@ -156,8 +157,15 @@ fn create_client(args: &Cli, context: ClientContext) -> Result<Box<dyn ApiNoCont
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "macos", target_os = "windows", target_os = "ios"))]
|
||||
// On macOS/Windows/iOS or without client-tls feature, use simple client (no cert pinning/mutual TLS)
|
||||
#[cfg(any(
|
||||
not(feature = "client-tls"),
|
||||
all(feature = "client-tls", any(target_os = "macos", target_os = "windows", target_os = "ios"))
|
||||
))]
|
||||
fn create_client(args: &Cli, context: ClientContext) -> Result<Box<dyn ApiNoContext<ClientContext>>> {
|
||||
// Client::try_new() automatically detects the URL scheme (http:// or https://)
|
||||
// and creates the appropriate client.
|
||||
// Note: Certificate pinning and mutual TLS are only available on Linux/Unix with OpenSSL
|
||||
let client =
|
||||
Client::try_new(&args.server_address).context("Failed to create HTTP(S) client")?;
|
||||
Ok(Box::new(client.with_context(context)))
|
||||
|
||||
@@ -118,10 +118,17 @@ impl<Connector, C> Client<
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "client-tls", any(target_os = "macos", target_os = "windows", target_os = "ios")))]
|
||||
type HyperHttpsConnector = hyper_tls::HttpsConnector<hyper_util::client::legacy::connect::HttpConnector>;
|
||||
|
||||
#[cfg(all(feature = "client-tls", not(any(target_os = "macos", target_os = "windows", target_os = "ios"))))]
|
||||
type HyperHttpsConnector = hyper_openssl::client::legacy::HttpsConnector<hyper_util::client::legacy::connect::HttpConnector>;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum HyperClient {
|
||||
Http(hyper_util::client::legacy::Client<hyper_util::client::legacy::connect::HttpConnector, BoxBody<Bytes, Infallible>>),
|
||||
Https(hyper_util::client::legacy::Client<HttpsConnector, BoxBody<Bytes, Infallible>>),
|
||||
#[cfg(feature = "client-tls")]
|
||||
Https(hyper_util::client::legacy::Client<HyperHttpsConnector, BoxBody<Bytes, Infallible>>),
|
||||
}
|
||||
|
||||
impl Service<Request<BoxBody<Bytes, Infallible>>> for HyperClient {
|
||||
@@ -132,7 +139,8 @@ impl Service<Request<BoxBody<Bytes, Infallible>>> for HyperClient {
|
||||
fn call(&self, req: Request<BoxBody<Bytes, Infallible>>) -> Self::Future {
|
||||
match self {
|
||||
HyperClient::Http(client) => client.request(req),
|
||||
HyperClient::Https(client) => client.request(req)
|
||||
#[cfg(feature = "client-tls")]
|
||||
HyperClient::Https(client) => client.request(req),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -158,11 +166,17 @@ impl<C> Client<DropContextService<HyperClient, C>, C> where
|
||||
"http" => {
|
||||
HyperClient::Http(hyper_util::client::legacy::Client::builder(hyper_util::rt::TokioExecutor::new()).build(connector.build()))
|
||||
},
|
||||
#[cfg(feature = "client-tls")]
|
||||
"https" => {
|
||||
let connector = connector.https()
|
||||
.build()
|
||||
.map_err(ClientInitError::SslError)?;
|
||||
HyperClient::Https(hyper_util::client::legacy::Client::builder(hyper_util::rt::TokioExecutor::new()).build(connector))
|
||||
let https_connector = connector
|
||||
.https()
|
||||
.build()
|
||||
.map_err(ClientInitError::SslError)?;
|
||||
HyperClient::Https(hyper_util::client::legacy::Client::builder(hyper_util::rt::TokioExecutor::new()).build(https_connector))
|
||||
},
|
||||
#[cfg(not(feature = "client-tls"))]
|
||||
"https" => {
|
||||
return Err(ClientInitError::TlsNotEnabled);
|
||||
},
|
||||
_ => {
|
||||
return Err(ClientInitError::InvalidScheme);
|
||||
@@ -206,12 +220,13 @@ impl<C> Client<
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "macos", target_os = "windows", target_os = "ios"))]
|
||||
#[cfg(all(feature = "client-tls", any(target_os = "macos", target_os = "windows", target_os = "ios")))]
|
||||
type HttpsConnector = hyper_tls::HttpsConnector<hyper_util::client::legacy::connect::HttpConnector>;
|
||||
|
||||
#[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))]
|
||||
#[cfg(all(feature = "client-tls", not(any(target_os = "macos", target_os = "windows", target_os = "ios"))))]
|
||||
type HttpsConnector = hyper_openssl::client::legacy::HttpsConnector<hyper_util::client::legacy::connect::HttpConnector>;
|
||||
|
||||
#[cfg(feature = "client-tls")]
|
||||
impl<C> Client<
|
||||
DropContextService<
|
||||
hyper_util::service::TowerToHyperService<
|
||||
@@ -226,10 +241,11 @@ impl<C> Client<
|
||||
> where
|
||||
C: Clone + Send + Sync + 'static
|
||||
{
|
||||
/// Create a client with a TLS connection to the server
|
||||
/// Create a client with a TLS connection to the server.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `base_path` - base path of the client API, i.e. "<http://www.my-api-implementation.com>"
|
||||
/// * `base_path` - base path of the client API, i.e. "<https://www.my-api-implementation.com>"
|
||||
#[cfg(any(target_os = "macos", target_os = "windows", target_os = "ios"))]
|
||||
pub fn try_new_https(base_path: &str) -> Result<Self, ClientInitError>
|
||||
{
|
||||
let https_connector = Connector::builder()
|
||||
@@ -239,10 +255,24 @@ impl<C> Client<
|
||||
Self::try_new_with_connector(base_path, Some("https"), https_connector)
|
||||
}
|
||||
|
||||
/// Create a client with a TLS connection to the server using a pinned certificate
|
||||
/// Create a client with a TLS connection to the server using OpenSSL via swagger.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `base_path` - base path of the client API, i.e. "<http://www.my-api-implementation.com>"
|
||||
/// * `base_path` - base path of the client API, i.e. "<https://www.my-api-implementation.com>"
|
||||
#[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))]
|
||||
pub fn try_new_https(base_path: &str) -> Result<Self, ClientInitError>
|
||||
{
|
||||
let https_connector = Connector::builder()
|
||||
.https()
|
||||
.build()
|
||||
.map_err(ClientInitError::SslError)?;
|
||||
Self::try_new_with_connector(base_path, Some("https"), https_connector)
|
||||
}
|
||||
|
||||
/// Create a client with a TLS connection to the server using a pinned certificate.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `base_path` - base path of the client API, i.e. "<https://www.my-api-implementation.com>"
|
||||
/// * `ca_certificate` - Path to CA certificate used to authenticate the server
|
||||
#[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))]
|
||||
pub fn try_new_https_pinned<CA>(
|
||||
@@ -263,7 +293,7 @@ impl<C> Client<
|
||||
/// Create a client with a mutually authenticated TLS connection to the server.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `base_path` - base path of the client API, i.e. "<http://www.my-api-implementation.com>"
|
||||
/// * `base_path` - base path of the client API, i.e. "<https://www.my-api-implementation.com>"
|
||||
/// * `ca_certificate` - Path to CA certificate used to authenticate the server
|
||||
/// * `client_key` - Path to the client private key
|
||||
/// * `client_certificate` - Path to the client's public certificate associated with the private key
|
||||
@@ -325,12 +355,15 @@ pub enum ClientInitError {
|
||||
/// Missing Hostname
|
||||
MissingHost,
|
||||
|
||||
/// HTTPS requested but TLS features not enabled
|
||||
TlsNotEnabled,
|
||||
|
||||
/// SSL Connection Error
|
||||
#[cfg(any(target_os = "macos", target_os = "windows", target_os = "ios"))]
|
||||
#[cfg(all(feature = "client-tls", any(target_os = "macos", target_os = "windows", target_os = "ios")))]
|
||||
SslError(native_tls::Error),
|
||||
|
||||
/// SSL Connection Error
|
||||
#[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))]
|
||||
#[cfg(all(feature = "client-tls", not(any(target_os = "macos", target_os = "windows", target_os = "ios"))))]
|
||||
SslError(openssl::error::ErrorStack),
|
||||
}
|
||||
|
||||
|
||||
@@ -115,17 +115,33 @@ fn main() {
|
||||
let context: ClientContext =
|
||||
swagger::make_context!(ContextBuilder, EmptyContext, auth_data, XSpanIdString::default());
|
||||
|
||||
let mut client : Box<dyn ApiNoContext<ClientContext>> = if is_https {
|
||||
// Using Simple HTTPS
|
||||
let client = Box::new(Client::try_new_https(&base_url)
|
||||
.expect("Failed to create HTTPS client"));
|
||||
Box::new(client.with_context(context))
|
||||
} else {
|
||||
// Using HTTP
|
||||
let client = Box::new(Client::try_new_http(
|
||||
&base_url)
|
||||
.expect("Failed to create HTTP client"));
|
||||
Box::new(client.with_context(context))
|
||||
let mut client : Box<dyn ApiNoContext<ClientContext>> = {
|
||||
#[cfg(feature = "client-tls")]
|
||||
{
|
||||
if is_https {
|
||||
// Using HTTPS with native-tls
|
||||
let client = Box::new(Client::try_new_https(&base_url)
|
||||
.expect("Failed to create HTTPS client"));
|
||||
Box::new(client.with_context(context))
|
||||
} else {
|
||||
// Using HTTP
|
||||
let client = Box::new(Client::try_new_http(&base_url)
|
||||
.expect("Failed to create HTTP client"));
|
||||
Box::new(client.with_context(context))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "client-tls"))]
|
||||
{
|
||||
if is_https {
|
||||
panic!("HTTPS requested but TLS support not enabled. \
|
||||
Enable the 'client-tls' feature to use HTTPS.");
|
||||
}
|
||||
// Using HTTP only
|
||||
let client = Box::new(Client::try_new_http(&base_url)
|
||||
.expect("Failed to create HTTP client"));
|
||||
Box::new(client.with_context(context))
|
||||
}
|
||||
};
|
||||
|
||||
let mut rt = tokio::runtime::Runtime::new().unwrap();
|
||||
|
||||
@@ -97,16 +97,17 @@ impl<C> Client<DropContextService<hyper_util::service::TowerToHyperService<hyper
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "macos", target_os = "windows", target_os = "ios"))]
|
||||
#[cfg(all(feature = "client-tls", any(target_os = "macos", target_os = "windows", target_os = "ios")))]
|
||||
type HttpsConnector = hyper_tls::HttpsConnector<hyper_util::client::legacy::connect::HttpConnector>;
|
||||
|
||||
#[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))]
|
||||
#[cfg(all(feature = "client-tls", not(any(target_os = "macos", target_os = "windows", target_os = "ios"))))]
|
||||
type HttpsConnector = hyper_openssl::client::legacy::HttpsConnector<hyper_util::client::legacy::connect::HttpConnector>;
|
||||
|
||||
#[cfg(feature = "client-tls")]
|
||||
impl<C> Client<DropContextService<hyper_util::service::TowerToHyperService<hyper_util::client::legacy::Client<HttpsConnector, BoxBody<Bytes, Infallible>>>, C>, C> where
|
||||
C: Clone + Send + Sync + 'static
|
||||
{
|
||||
/// Create a client with a TLS connection to the server.
|
||||
/// Create a client with a TLS connection to the server using native-tls.
|
||||
#[cfg(any(target_os = "macos", target_os = "windows", target_os = "ios"))]
|
||||
pub fn new_https() -> Result<Self, native_tls::Error>
|
||||
{
|
||||
@@ -114,7 +115,7 @@ impl<C> Client<DropContextService<hyper_util::service::TowerToHyperService<hyper
|
||||
Ok(Self::new_with_connector(https_connector))
|
||||
}
|
||||
|
||||
/// Create a client with a TLS connection to the server.
|
||||
/// Create a client with a TLS connection to the server using OpenSSL.
|
||||
#[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))]
|
||||
pub fn new_https() -> Result<Self, openssl::error::ErrorStack>
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user