Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[guide section]: How to customize TLS #469

Open
jdisanti opened this issue Feb 23, 2022 · 10 comments
Open

[guide section]: How to customize TLS #469

jdisanti opened this issue Feb 23, 2022 · 10 comments
Labels
documentation This is a problem with documentation feature-request A feature should be added or improved. p2 This is a standard priority issue

Comments

@jdisanti
Copy link
Contributor

A note for the community

Community Note

  • Please vote on this issue by adding a 👍 reaction to the original issue to help the community and maintainers prioritize this request
  • Please do not leave "+1" or "me too" comments, they generate extra noise for issue followers and do not help prioritize the request
  • If you are interested in working on this issue, please leave a comment

Tell us about your request

Tell us about the problem you're trying to solve.

N/A

Are you currently working around this issue?

N/A

Additional context

No response

@jdisanti jdisanti added feature-request A feature should be added or improved. needs-triage This issue or PR still needs to be triaged. documentation This is a problem with documentation and removed needs-triage This issue or PR still needs to be triaged. labels Feb 23, 2022
@oxlade39
Copy link

oxlade39 commented Mar 4, 2022

I previously raised #334 and later ca-certs was added which I thought I could follow.

I need to do this behind a proxy and I have tried to add hyper_proxy::ProxyConnector. Rather unfortunately my corporate proxy is just an IP address and rustls does not support this.

This means I'm back to attempting to configure native-tls with a custom root cert. I just can't work out the right combination of types. The closest I've gotten is below but this doesn't compile. Some better documentation would really be appreciated here.

use std::{fs::File, env};
use std::io::BufReader;

use aws_config::provider_config::ProviderConfig;
use aws_config::profile::ProfileFileCredentialsProvider;
use aws_sdk_s3::Region;
use aws_smithy_client::hyper_ext;
use hyper::Client;
use hyper_proxy::{Proxy, Intercept, ProxyConnector};
use hyper_tls::HttpsConnector;
use native_tls::Certificate;
use std::path::Path;

fn load_ca_cert(pem_file: &Path) -> Result<Certificate, CertLoadError> {
    use std::fs;

    let bytes =
        fs::read(pem_file).map_err(|e| CertLoadError::Io(format!("Loading {:?}", pem_file), e))?;

    Certificate::from_pem(&bytes).map_err(CertLoadError::TlsError)
}

#[derive(Debug)]
enum CertLoadError {
    TlsError(native_tls::Error),
    Io(String, std::io::Error),
}


#[tokio::main]
async fn main() {
    env_logger::init();
    
    // insert your root CAs
    let certificate: native_tls::Certificate =
        load_ca_cert(&Path::new("/etc/ssl/certs/ca-certificates.crt")).expect("Failed to load your CA cert");

    let native_tls_connector = native_tls::TlsConnector::builder()
        .add_root_certificate(certificate)
        .build()
        .expect("Building native_tls::TlsConnector");

    let tokio_tls_tls_connector = tokio_native_tls::TlsConnector::from(native_tls_connector);


    let mut hyper_http_connector = hyper::client::HttpConnector::new();
    hyper_http_connector.enforce_http(false);
 
    let hyper_tls_https_connector = hyper_tls::HttpsConnector::from((
        hyper_http_connector,
        tokio_tls_tls_connector,
    ));


    let client_main = Client::builder().build::<_, hyper::Body>(hyper_tls_https_connector);


    let https_proxy = env::var("https_proxy").unwrap();
    let proxy_uri = https_proxy.replace("http", "https").parse().unwrap();
    let proxy = Proxy::new(Intercept::All, proxy_uri);
    let pc = ProxyConnector::from_proxy(tokio_tls_tls_connector, proxy).unwrap();
    

    let profile_creds = ProfileFileCredentialsProvider::builder()
        .profile_name("profile-name")
        .build();

    // Currently, aws_config connectors are buildable directly from something that implements `hyper::Connect`.
    // This enables different providers to construct clients with different timeouts.
    let provider_config = ProviderConfig::default()
        .with_tcp_connector(pc.clone());
        
    let shared_conf = aws_config::from_env()
        .region(Region::new("us-east-1"))
        .credentials_provider(profile_creds)
        .configure(provider_config)
        .load()
        .await;
    let s3_config = aws_sdk_s3::Config::from(&shared_conf);
    // however, for generated clients, they are constructred from a Hyper adapter directly:
    let s3_client = aws_sdk_s3::Client::from_conf_conn(
        s3_config,
        hyper_ext::Adapter::builder().build(pc),
    );
    let buckets = s3_client.list_buckets().send().await.unwrap();
    let items = buckets.buckets().unwrap();
    print!("buckets: {}", items[0].name.clone().unwrap());
}

@rcoh
Copy link
Contributor

rcoh commented Mar 6, 2022

is it possible to add a DNS name to /etc/hosts so that you can refer to the proxy by a DNS name? You could also do the same thing by using a custom DNS resolver in Hyper 💭

@oxlade39
Copy link

oxlade39 commented Mar 6, 2022

is it possible to add a DNS name to /etc/hosts so that you can refer to the proxy by a DNS name? You could also do the same thing by using a custom DNS resolver in Hyper 💭

Unfortunately I don't have write access to /etc/hosts. I'll try and work out how to add a custom DNS revolver in Hyper, that sounds promising.

@oxlade39
Copy link

@rcoh sorry I'm still having trouble working this all out. I'm trying to find an example of specifying a custom DNS resolver for Hyper but I've not managed to find much documentation on this. I don't suppose you have a link to an example or pointer in the right direction?

@rcoh
Copy link
Contributor

rcoh commented Mar 29, 2022

@oxlade39
Copy link

Sorry, I'm afraid not. I was aware of the above documentation and I really appreciate you patience but it's not clear to me how one would go about combining the below code with the custom DNS resolver example.

The hyper client itself appears to be hidden behind layers of other abstraction for aws_smithy, Proxy and rust_tls.

use std::{fs, io};
use std::{fs::File, env};
use std::io::BufReader;

use aws_config::provider_config::ProviderConfig;
use aws_config::profile::ProfileFileCredentialsProvider;
use aws_sdk_s3::Region;
use aws_smithy_client::hyper_ext;
use hyper_proxy::{Proxy, Intercept, ProxyConnector};
use rustls::{RootCertStore, Certificate, internal::msgs::codec::Codec};
use rustls_pemfile::certs;


#[tokio::main]
async fn main() {
    env_logger::init();
    
    // insert your root CAs
    let f = File::open("/etc/ssl/certs/ca-certificates.crt").unwrap();
    let mut reader = BufReader::new(f);    
    let mut root_store = RootCertStore::empty();

    for cert in certs(&mut reader).unwrap() {
        root_store.add(&Certificate(cert));
    }

    let config = rustls::ClientConfig::builder()
        .with_safe_defaults()
        .with_root_certificates(root_store)
        .with_no_client_auth();
    let rustls_connector = hyper_rustls::HttpsConnectorBuilder::new()
        .with_tls_config(config.clone())
        .https_only()
        .enable_http1()
        .enable_http2()
        .build();

    let https_proxy = env::var("https_proxy").unwrap();
    let proxy_uri = https_proxy.replace("http", "https").parse().unwrap();
    let proxy = Proxy::new(Intercept::All, proxy_uri);
    let pc = ProxyConnector::from_proxy(rustls_connector, proxy).unwrap();
    

    let profile_creds = ProfileFileCredentialsProvider::builder()
        .profile_name("fiaim-deployer-dev")
        .build();

    // Currently, aws_config connectors are buildable directly from something that implements `hyper::Connect`.
    // This enables different providers to construct clients with different timeouts.
    // let provider_config = ProviderConfig::default()
    //     .with_tcp_connector(pc.clone());
    let provider_config = ProviderConfig::default();
    let shared_conf = aws_config::from_env()
        .region(Region::new("us-east-1"))
        .credentials_provider(profile_creds)
        .configure(provider_config)
        .load()
        .await;
    let s3_config = aws_sdk_s3::Config::from(&shared_conf);
    // however, for generated clients, they are constructred from a Hyper adapter directly:
    let s3_client = aws_sdk_s3::Client::from_conf_conn(
        s3_config,
        hyper_ext::Adapter::builder().build(pc),
    );
    let buckets = s3_client.list_buckets().send().await.unwrap();
    let items = buckets.buckets().unwrap();
    print!("buckets: {}", items[0].name.clone().unwrap());
}

I fear I may be fundamentally misunderstanding.

@rcoh
Copy link
Contributor

rcoh commented Mar 29, 2022

I'll see if I can whip up an example, but I think you want this: https://docs.rs/hyper-rustls/latest/hyper_rustls/struct.HttpsConnectorBuilder.html#method.wrap_connector-1

Which will enable you to drop in your own custom HttpConnector with DNS stubbed out

@rcoh
Copy link
Contributor

rcoh commented Mar 29, 2022

here's a snippet that compiles for me in the context of the larger example you posted above.

use std::net::SocketAddr;
use std::iter;
use hyper::client::HttpConnector
// ... snip ...

    let config = rustls::ClientConfig::builder()
        .with_safe_defaults()
        .with_root_certificates(root_store)
        .with_no_client_auth();

    let resolver = tower::service_fn(|_name| async {
        // update to _always_ return the corp IP address. This will enable you to pass in a DNS
        // name but will always return your corp IP address
        Ok::<_, Infallible>(iter::once(SocketAddr::from(([127, 0, 0, 1], 8080))))
    });
    let http_conector = HttpConnector::new_with_resolver(resolver);

    let rustls_connector = hyper_rustls::HttpsConnectorBuilder::new()
        .with_tls_config(config.clone())
        .https_only()
        .enable_http1()
        .wrap_connector(http_conector);

@oxlade39
Copy link

that's great, thanks. It makes sense now.

It's compiling fine but panicking now "invalid URL, scheme is not http" That's probably something else in my setup that's wrong although I do wonder if it's because I'm passing an HttpConnector to wrap_connector.

I'll keep digging. Thanks again.

@oxlade39
Copy link

I just had to turn off enforce_http:
hyperium/hyper#1009 (comment)

@jmklix jmklix added p2 This is a standard priority issue and removed p2 This is a standard priority issue labels Nov 28, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
documentation This is a problem with documentation feature-request A feature should be added or improved. p2 This is a standard priority issue
Projects
None yet
Development

No branches or pull requests

4 participants