0

I'm trying to do the equivalent in Rust of a simple Python requests.get to an "https" domain. I should stress that my knowledge of authentication, certificates, proxies, etc. is virtually nil.

I found this question, but I'm not sure what this is doing: there doesn't seem to be a password involved, for example, in the answer there, which I tried to adapt to my case (as shown below), unsuccessfully.

The aim is to get a desired response from a locally executing Elasticsearch server (NB Elasticsearch v8 became "https" when previously it had been "http". For me this is a challenge). The Python looks like this:

# NB these are valid username and password for an account created in the server setup
username = 'mike12'
password = 'mike12' 
from requests.auth import HTTPBasicAuth
kwargs['auth'] = HTTPBasicAuth(username, password)
# set kwargs['verify'] to the CA (certification authority) certificate path
os.environ['ES_PATH_CONF'] = r'D:\apps\ElasticSearch\elasticsearch-8.6.2\config'
es_path_conf = os.getenv('ES_PATH_CONF')
kwargs['verify'] = fr'{es_path_conf}\certs\http_ca.crt'
try:
    response = requests.request('get', "https://localhost:9500", **kwargs)
except BaseException as e:
    ...

As you can see, for this to work, I found when I was doing the above that environment variable ES_PATH_CONF had to be set by the code. The value for key "verify" is a path to a valid (since it works in Python) certificate of some kind, previously set up. The above runs and gets the expected 200 response from the local server, with the right response message.

This is what I tried in Rust:

let login_fields = [
    ("user", "mike12"),
    ("script", "true"),
    ("token", "[TOKEN]"),
    ("password", "mike12"), # is this the right way to add a password???
];
let login_response = client
    .get("https://localhost:9500")
    .form(&login_fields)
    .send()
    .await?
    .text()
    .await?;
println!("{}", login_response);

Output:

Error: reqwest::Error { kind: Request, url: Url { scheme: "https", cannot_be_a_base: false, username: 
"", password: None, host: Some(Domain("localhost")), port: Some(9500), path: "/", query: None, fragment: 
None }, source: hyper::Error(Connect, Os { code: -2146762487, kind: Uncategorized, message: "A certificate 
chain processed, but terminated in a root certificate which is not trusted by the trust provider." }) }

I understand the gist. But I have no idea what the solution is. Seeing as it says "password: None" I assume the way to submit a password is not what I've done above. But it also says "username: """... so yes, I am floundering. If there's somewhere which documents this sort of stuff (other than the reqwest documentation) in Rust that would help.

later
Some progress, perhaps: taking inspiration from here, among others. I got to this:

use reqwest::Client;
use std::io::Read;
use std::fs::File;
use tmp_env::set_var;
...
    let login_fields = [
        ("user", "mike12"),
        ("script", "true"),
        ("token", "[TOKEN]"),
        ("password", "mike12"), // is this how to add a password???
    ];
    
    let es_path = r#"D:\apps\ElasticSearch\elasticsearch-8.6.2\config"#;
    set_var("ES_PATH_CONF", &es_path);
    
    let mut buf = Vec::new();
    println!("buf {}", str_type_of(&buf));
    let cert_path = format!(r#"{es_path}\certs\http_ca.crt"#);
    println!("cert_path |{}|", cert_path);
    let mut cert_file = File::open(cert_path)?;
    println!("cert_file type {}", str_type_of(&cert_file));
    cert_file.read_to_end(&mut buf);
    println!("buf len now {}", buf.len());

    // let cert = reqwest::Certificate::from_der(&buf)?; // threw error   
    let cert = reqwest::Certificate::from_pem(&buf)?; // delivers cert OK
    println!("cert {:?} type {}", cert, str_type_of(&cert));
    
    let client = Client::builder()
        .add_root_certificate(cert)
        .build()?;
    
    let login_response = client
        .get("https://localhost:9500")
        .form(&login_fields)
        .send()
        .await?
        .text()
        .await?;

    println!("{}", login_response);

Output now:

{"error":"Content-Type header [application/x-www-form-urlencoded] is not supported","status":406}

It occurs to me that what's happening now may be specific to Elasticsearch ... ? This particular 406 error crops up quite a bit in the ES forums. From the message, could it mean that the ES server expects to receive the username and password in a different way, not using a form? NB the way Python delivers them, as a value (HTTPBasicAuth) for key "auth".

mike rodent
  • 14,126
  • 11
  • 103
  • 157
  • Ignore the `username: ""` and `password: None` in the error. Those reflect values if specified *in the url*, like `https://username:password@example.com:1111`. The real error is at the end: the certificate could not be verified. – kmdreko Sep 02 '23 at 20:07

1 Answers1

0

Got it. basic_auth and drop form:

let login_response = client
    .get("https://localhost:9500")
    .basic_auth("mike12", Some("mike12"))
    .send()
    .await?
    .text()
    .await?;
mike rodent
  • 14,126
  • 11
  • 103
  • 157