Skip to content

Commit

Permalink
fix: Enable support for user:password@host in proxy connections
Browse files Browse the repository at this point in the history
Ticket: MEN-7402
Changelog: Basic authentication (https://user:password@host/) is now supported for proxy URLs and connections
Signed-off-by: Vratislav Podzimek <vratislav.podzimek@northern.tech>
  • Loading branch information
vpodzime committed Jul 18, 2024
1 parent 3fb2a64 commit 5cfc99d
Show file tree
Hide file tree
Showing 2 changed files with 184 additions and 9 deletions.
29 changes: 27 additions & 2 deletions src/common/http/platform/beast/http.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -439,6 +439,21 @@ error::Error Client::AsyncCall(
return error::NoError;
}

static inline error::Error AddProxyAuthHeader(OutgoingRequest &req, BrokenDownUrl &proxy_address) {
if (proxy_address.username == "") {
// nothing to do
return error::NoError;
}
auto creds = proxy_address.username + ":" + proxy_address.password;
auto ex_encoded_creds = crypto::EncodeBase64(common::ByteVectorFromString(creds));
if (!ex_encoded_creds) {
return ex_encoded_creds.error();
}
req.SetHeader("Proxy-Authorization", "Basic " + ex_encoded_creds.value());

return error::NoError;
}

error::Error Client::HandleProxySetup() {
secondary_req_.reset();

Expand All @@ -448,7 +463,7 @@ error::Error Client::HandleProxySetup() {
if (http_proxy_ != "" && !HostNameMatchesNoProxy(request_->address_.host, no_proxy_)) {
// Make a modified proxy request.
BrokenDownUrl proxy_address;
auto err = BreakDownUrl(http_proxy_, proxy_address);
auto err = BreakDownUrl(http_proxy_, proxy_address, true);
if (err != error::NoError) {
return err.WithContext("HTTP proxy URL is invalid");
}
Expand All @@ -464,6 +479,11 @@ error::Error Client::HandleProxySetup() {
request_->address_.port = proxy_address.port;
request_->address_.protocol = proxy_address.protocol;

err = AddProxyAuthHeader(*request_, proxy_address);
if (err != error::NoError) {
return err;
}

if (proxy_address.protocol == "https") {
socket_mode_ = SocketMode::Tls;
} else if (proxy_address.protocol == "http") {
Expand All @@ -484,7 +504,7 @@ error::Error Client::HandleProxySetup() {
request_ = make_shared<OutgoingRequest>();
request_->SetMethod(Method::CONNECT);
BrokenDownUrl proxy_address;
auto err = BreakDownUrl(https_proxy_, proxy_address);
auto err = BreakDownUrl(https_proxy_, proxy_address, true);
if (err != error::NoError) {
return err.WithContext("HTTPS proxy URL is invalid");
}
Expand All @@ -499,6 +519,11 @@ error::Error Client::HandleProxySetup() {
request_->address_.port = proxy_address.port;
request_->address_.protocol = proxy_address.protocol;

err = AddProxyAuthHeader(*request_, proxy_address);
if (err != error::NoError) {
return err;
}

if (proxy_address.protocol == "https") {
socket_mode_ = SocketMode::Tls;
} else if (proxy_address.protocol == "http") {
Expand Down
164 changes: 157 additions & 7 deletions tests/src/common/http_proxy_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ using namespace std;
#define TEST_PROXY_PORT "8003"
#define TEST_TLS_PROXY_PORT "8004"
#define TEST_CLOSED_PORT "8005"
#define TEST_PROXY_USER "testuser"
#define TEST_PROXY_PASSWORD "T3stP433w0rd"

namespace common = mender::common;
namespace error = mender::common::error;
Expand Down Expand Up @@ -93,7 +95,7 @@ class HttpProxyTest : public testing::Test {
ASSERT_EQ(error::NoError, err);
}

void StartProxy() {
void StartProxy(bool authenticating) {
const string tiny_proxy = "/usr/bin/tinyproxy";
const string nc = "/bin/nc";

Expand All @@ -113,6 +115,9 @@ Allow 127.0.0.1
MaxClients 10
StartServers 1
)";
if (authenticating) {
config << "BasicAuth " TEST_PROXY_USER " " TEST_PROXY_PASSWORD << endl;
}
ASSERT_TRUE(config.good());
config.close();

Expand Down Expand Up @@ -189,8 +194,8 @@ connect = localhost:)" + connect_port
EnsureUp(client);
}

void StartTlsProxy() {
StartProxy();
void StartTlsProxy(bool authenticating) {
StartProxy(authenticating);
StartTlsTunnel(TEST_TLS_PROXY_PORT, TEST_PROXY_PORT);
}

Expand Down Expand Up @@ -239,7 +244,7 @@ class HttpProxyHttpTest : public HttpProxyTest {
public:
void SetUp() override {
StartPlainServer();
StartProxy();
StartProxy(false);
}
};

Expand Down Expand Up @@ -420,7 +425,7 @@ TEST_F(HttpProxyHttpTest, WrongTarget) {
class HttpProxyHttpsTest : public HttpProxyTest {
public:
void SetUp() override {
StartProxy();
StartProxy(false);
StartTlsServer();
}
};
Expand Down Expand Up @@ -627,7 +632,7 @@ class HttpsProxyHttpTest : public HttpProxyTest {
public:
void SetUp() override {
StartPlainServer();
StartTlsProxy();
StartTlsProxy(false);
}
};

Expand Down Expand Up @@ -795,7 +800,7 @@ class HttpsProxyHttpsTest : public HttpProxyTest {
public:
void SetUp() override {
StartTlsServer();
StartTlsProxy();
StartTlsProxy(false);
}
};

Expand Down Expand Up @@ -984,3 +989,148 @@ TEST_F(HttpsProxyHttpsTest, WrongCertificate) {

EXPECT_TRUE(client_hit_header);
}

class HttpAuthProxyHttpTest : public HttpProxyTest {
public:
void SetUp() override {
StartPlainServer();
StartProxy(true);
}
};

TEST_F(HttpAuthProxyHttpTest, BasicRequestAndResponse) {
bool client_hit_header = false;
bool client_hit_body = false;

http::ClientConfig client_config {
.http_proxy =
"http://" TEST_PROXY_USER ":" TEST_PROXY_PASSWORD "@127.0.0.1:" TEST_PROXY_PORT,
};
http::Client client(client_config, loop);
auto req = make_shared<http::OutgoingRequest>();
req->SetMethod(http::Method::GET);
req->SetAddress("http://127.0.0.1:" TEST_PORT "/index.html");
vector<uint8_t> received;
auto err = client.AsyncCall(
req,
[&client_hit_header, &received](http::ExpectedIncomingResponsePtr exp_resp) {
ASSERT_TRUE(exp_resp) << exp_resp.error().String();
auto resp = exp_resp.value();
EXPECT_EQ(resp->GetStatusCode(), 200);
client_hit_header = true;

auto body_writer = make_shared<io::ByteWriter>(received);
body_writer->SetUnlimited(true);
resp->SetBodyWriter(body_writer);
},
[&client_hit_body, this](http::ExpectedIncomingResponsePtr exp_resp) {
ASSERT_TRUE(exp_resp) << exp_resp.error().String();
client_hit_body = true;
loop.Stop();
});
ASSERT_EQ(error::NoError, err);

loop.Run();

EXPECT_TRUE(plain_server_hit_header);
EXPECT_TRUE(plain_server_hit_body);
EXPECT_TRUE(client_hit_header);
EXPECT_TRUE(client_hit_body);
EXPECT_EQ(common::StringFromByteVector(received), "Test\r\n");
}

class HttpAuthProxyHttpsTest : public HttpProxyTest {
public:
void SetUp() override {
StartProxy(true);
StartTlsServer();
}
};

TEST_F(HttpAuthProxyHttpsTest, BasicRequestAndResponse) {
bool client_hit_header = false;
bool client_hit_body = false;

http::ClientConfig client_config {
.server_cert_path = "server.localhost.crt",
.https_proxy =
"http://" TEST_PROXY_USER ":" TEST_PROXY_PASSWORD "@localhost:" TEST_PROXY_PORT,
};
http::Client client(client_config, loop);
auto req = make_shared<http::OutgoingRequest>();
req->SetMethod(http::Method::GET);
req->SetAddress("https://localhost:" TEST_TLS_PORT "/index.html");
vector<uint8_t> received;
auto err = client.AsyncCall(
req,
[&client_hit_header, &received](http::ExpectedIncomingResponsePtr exp_resp) {
ASSERT_TRUE(exp_resp) << exp_resp.error().String();
auto resp = exp_resp.value();
EXPECT_EQ(resp->GetStatusCode(), 200);
client_hit_header = true;

auto body_writer = make_shared<io::ByteWriter>(received);
body_writer->SetUnlimited(true);
resp->SetBodyWriter(body_writer);
},
[&client_hit_body, this](http::ExpectedIncomingResponsePtr exp_resp) {
ASSERT_TRUE(exp_resp) << exp_resp.error().String();
client_hit_body = true;
loop.Stop();
});
ASSERT_EQ(error::NoError, err);

loop.Run();

EXPECT_TRUE(client_hit_header);
EXPECT_TRUE(client_hit_body);
EXPECT_EQ(common::StringFromByteVector(received), "Test\r\n");
}

class HttpsAuthProxyHttpsTest : public HttpProxyTest {
public:
void SetUp() override {
StartTlsServer();
StartTlsProxy(true);
}
};

TEST_F(HttpsAuthProxyHttpsTest, BasicRequestAndResponse) {
bool client_hit_header = false;
bool client_hit_body = false;

http::ClientConfig client_config {
.server_cert_path = "server.localhost.crt",
.https_proxy =
"https://" TEST_PROXY_USER ":" TEST_PROXY_PASSWORD "@localhost:" TEST_TLS_PROXY_PORT,
};
http::Client client(client_config, loop);
auto req = make_shared<http::OutgoingRequest>();
req->SetMethod(http::Method::GET);
req->SetAddress("https://localhost:" TEST_TLS_PORT "/index.html");
vector<uint8_t> received;
auto err = client.AsyncCall(
req,
[&client_hit_header, &received](http::ExpectedIncomingResponsePtr exp_resp) {
ASSERT_TRUE(exp_resp) << exp_resp.error().String();
auto resp = exp_resp.value();
EXPECT_EQ(resp->GetStatusCode(), 200);
client_hit_header = true;

auto body_writer = make_shared<io::ByteWriter>(received);
body_writer->SetUnlimited(true);
resp->SetBodyWriter(body_writer);
},
[&client_hit_body, this](http::ExpectedIncomingResponsePtr exp_resp) {
ASSERT_TRUE(exp_resp) << exp_resp.error().String();
client_hit_body = true;
loop.Stop();
});
ASSERT_EQ(error::NoError, err);

loop.Run();

EXPECT_TRUE(client_hit_header);
EXPECT_TRUE(client_hit_body);
EXPECT_EQ(common::StringFromByteVector(received), "Test\r\n");
}

0 comments on commit 5cfc99d

Please sign in to comment.