Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions include/fluent-bit/flb_upstream.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@
#include <fluent-bit/flb_upstream_queue.h>
#include <fluent-bit/flb_stream.h>

#ifdef FLB_HAVE_TLS
#include <fluent-bit/tls/flb_tls.h>
#endif

#include <cmetrics/cmetrics.h>
#include <cmetrics/cmt_gauge.h>

Expand Down Expand Up @@ -57,6 +61,9 @@ struct flb_upstream {
int proxied_port;
char *proxy_username;
char *proxy_password;
#ifdef FLB_HAVE_TLS
struct flb_tls *proxy_tls_context; /* TLS context for the proxy (https proxy) */
#endif

/*
* If an upstream context has been created in HA mode, this flag is
Expand Down
8 changes: 8 additions & 0 deletions include/fluent-bit/tls/flb_tls.h
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,14 @@ struct flb_tls_backend {
void (*session_invalidate) (void *);
int (*session_destroy) (void *);
const char *(*session_alpn_get) (void *);
/*
* Chain an inner TLS session's I/O through an outer TLS session.
* Used for TLS-in-TLS when connecting through an HTTPS proxy: after
* HTTP CONNECT is established over the proxy TLS, the destination TLS
* handshake data must be sent through (and encrypted by) the proxy TLS.
* Optional: may be NULL if the backend does not support it.
*/
int (*session_set_outer) (void *inner, void *outer);

/* I/O */
int (*net_read) (struct flb_tls_session *, void *, size_t);
Expand Down
32 changes: 32 additions & 0 deletions src/flb_io.c
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,38 @@ int flb_io_net_connect(struct flb_connection *connection,
}

if (connection->upstream->proxied_host) {
#ifdef FLB_HAVE_TLS
/*
* When the proxy URL uses https://, the connection to the proxy
* itself must be TLS-wrapped before the HTTP CONNECT tunnel is
* established. Use the dedicated proxy TLS context which carries
* the proxy hostname as the SNI (vhost).
*/
if (connection->upstream->proxy_tls_context != NULL) {
ret = flb_tls_session_create(connection->upstream->proxy_tls_context,
connection,
coro);
if (ret != 0) {
flb_debug("[http_client] proxy TLS handshake failed for %s:%i",
connection->upstream->tcp_host,
connection->upstream->tcp_port);
flb_socket_close(fd);
connection->fd = -1;
connection->event.fd = -1;
return -1;
}
/*
* Ensure all I/O (the CONNECT request and any subsequent
* data) is routed through the proxy TLS session. This is
* necessary when the ultimate destination is plain HTTP:
* the stream's FLB_IO_TLS flag is not set for such upstreams,
* but flb_io_net_write/read check that flag to decide whether
* to use connection->tls_session. For HTTPS destinations the
* flag is already set so this is a no-op.
*/
flb_stream_enable_flags(connection->stream, FLB_IO_TLS);
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
#endif
ret = flb_http_client_proxy_connect(connection);

if (ret == -1) {
Expand Down
45 changes: 41 additions & 4 deletions src/flb_upstream.c
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,10 @@ struct flb_upstream *flb_upstream_create(struct flb_config *config,
config,
NULL);

/* Initialize queues early so all error paths can safely call
* flb_upstream_destroy(u) for centralised cleanup. */
flb_upstream_queue_init(&u->queue);

/* Set upstream to the http_proxy if it is specified. */
if (flb_upstream_needs_proxy(host, config->http_proxy, config->no_proxy) == FLB_TRUE) {
flb_debug("[upstream] config->http_proxy: %s", config->http_proxy);
Expand All @@ -336,6 +340,35 @@ struct flb_upstream *flb_upstream_create(struct flb_config *config,
u->proxy_password = flb_strdup(proxy_password);
}

#ifdef FLB_HAVE_TLS
if (strcmp(proxy_protocol, "https") == 0) {
/*
Comment thread
antoniomrfranco marked this conversation as resolved.
* The proxy connection itself is TLS. Create a dedicated TLS
* context using the proxy hostname as the SNI (vhost). This
* context is separate from the destination TLS context so that
* each handshake uses the correct hostname.
*/
u->proxy_tls_context = flb_tls_create(FLB_TLS_CLIENT_MODE,
FLB_TRUE, 0,
proxy_host,
NULL, NULL,
NULL, NULL, NULL);
if (!u->proxy_tls_context) {
Comment thread
coderabbitai[bot] marked this conversation as resolved.
flb_error("[upstream] could not create TLS context for HTTPS proxy %s",
proxy_host);
flb_free(proxy_protocol);
flb_free(proxy_host);
flb_free(proxy_port);
flb_free(proxy_username);
flb_free(proxy_password);
flb_upstream_destroy(u);
return NULL;
}

flb_tls_set_verify_hostname(u->proxy_tls_context, FLB_TRUE);
}
#endif

flb_free(proxy_protocol);
flb_free(proxy_host);
flb_free(proxy_port);
Expand All @@ -348,15 +381,12 @@ struct flb_upstream *flb_upstream_create(struct flb_config *config,
}

if (!u->tcp_host) {
flb_free(u);
flb_upstream_destroy(u);
return NULL;
}

flb_stream_enable_flags(&u->base, FLB_IO_ASYNC);

/* Initialize queues */
flb_upstream_queue_init(&u->queue);

mk_list_add(&u->base._head, &config->upstreams);

return u;
Expand Down Expand Up @@ -688,6 +718,13 @@ int flb_upstream_destroy(struct flb_upstream *u)
flb_free(u->proxy_username);
flb_free(u->proxy_password);

#ifdef FLB_HAVE_TLS
if (u->proxy_tls_context) {
flb_tls_destroy(u->proxy_tls_context);
u->proxy_tls_context = NULL;
}
#endif

if (mk_list_is_set(&u->base._head) == 0) {
mk_list_del(&u->base._head);
}
Expand Down
20 changes: 18 additions & 2 deletions src/flb_utils.c
Original file line number Diff line number Diff line change
Expand Up @@ -1869,9 +1869,17 @@ int flb_utils_proxy_url_split(const char *in_url, char **out_protocol,
return -1;
}

/* Only HTTP proxy is supported for now. */
if (strcmp(protocol, "http") != 0) {
/* Only HTTP proxy is supported without TLS support. */
if (strcmp(protocol, "http") != 0
#ifdef FLB_HAVE_TLS
&& strcmp(protocol, "https") != 0
#endif
) {
#ifdef FLB_HAVE_TLS
flb_error("only HTTP and HTTPS proxies are supported.");
#else
flb_error("only HTTP proxy is supported.");
#endif
goto error;
}

Expand Down Expand Up @@ -1949,7 +1957,11 @@ int flb_utils_proxy_url_split(const char *in_url, char **out_protocol,
}
}
else if (*(end + 1) == '\0') {
#ifdef FLB_HAVE_TLS
port = flb_strdup(strcmp(protocol, "https") == 0 ? "443" : "80");
#else
port = flb_strdup("80");
#endif
if (!port) {
flb_errno();
goto error;
Expand Down Expand Up @@ -1988,7 +2000,11 @@ int flb_utils_proxy_url_split(const char *in_url, char **out_protocol,
goto error;
}

#ifdef FLB_HAVE_TLS
port = flb_strdup(strcmp(protocol, "https") == 0 ? "443" : "80");
#else
port = flb_strdup("80");
#endif
if (!port) {
flb_errno();
goto error;
Expand Down
49 changes: 45 additions & 4 deletions src/tls/flb_tls.c
Original file line number Diff line number Diff line change
Expand Up @@ -617,13 +617,20 @@ int flb_tls_session_create(struct flb_tls *tls,
vhost = NULL;

if (connection->type == FLB_UPSTREAM_CONNECTION) {
if (connection->upstream->proxied_host != NULL) {
if (tls->vhost != NULL) {
/*
* An explicit vhost in the TLS context takes priority. This
* covers the HTTPS proxy case where the proxy TLS context has
* its own vhost (= tcp_host) and must not fall through to
* proxied_host which belongs to the inner destination.
* Leave vhost as NULL so net_handshake() picks up tls->vhost.
*/
}
else if (connection->upstream->proxied_host != NULL) {
vhost = flb_rtrim(connection->upstream->proxied_host, '.');
}
else {
if (tls->vhost == NULL) {
vhost = flb_rtrim(connection->upstream->tcp_host, '.');
}
vhost = flb_rtrim(connection->upstream->tcp_host, '.');
}
}

Expand All @@ -643,6 +650,40 @@ int flb_tls_session_create(struct flb_tls *tls,
return -1;
}

/*
* If an existing TLS session is already active on this connection
* (e.g. the proxy TLS session for an HTTPS proxy), chain the new
* session's I/O through it. The inner (destination) TLS handshake
* data must travel inside the outer (proxy) TLS tunnel rather than
* going directly to the raw socket.
*/
if (connection->tls_session != NULL &&
tls->api->session_set_outer != NULL) {
result = tls->api->session_set_outer(session->ptr,
connection->tls_session->ptr);
if (result != 0) {
flb_error("[tls] failed to chain TLS session over proxy tunnel for %s",
flb_connection_get_remote_address(connection));

if (vhost != NULL) {
flb_free(vhost);
}

tls->api->session_destroy(session->ptr);
flb_free(session);
return -1;
}

/*
* The outer backend session ptr is now owned by the inner session
* (via outer_session). Release the outer flb_tls_session wrapper
* without going through flb_tls_session_destroy, which would free
* the backend ptr we just transferred.
*/
flb_free(connection->tls_session);
connection->tls_session = NULL;
}

session->tls = tls;
session->connection = connection;

Expand Down
46 changes: 46 additions & 0 deletions src/tls/openssl.c
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ struct tls_session {
char alpn[FLB_TLS_ALPN_MAX_LENGTH];
int continuation_flag;
struct tls_context *parent; /* parent struct tls_context ref */
struct tls_session *outer_session; /* outer TLS session for TLS-in-TLS (HTTPS proxy) */
};

static int tls_init(void)
Expand Down Expand Up @@ -1279,10 +1280,40 @@ static void *tls_session_create(struct flb_tls *tls,
return session;
}

/*
* Chain inner TLS session I/O through an outer TLS session.
* Used for TLS-in-TLS when connecting through an HTTPS proxy: the inner
* (destination) SSL object's BIO is replaced with a BIO_f_ssl wrapper
* around the outer (proxy) SSL object, so all inner TLS bytes flow
* through the already-established outer TLS tunnel.
*/
static int tls_session_set_outer(void *inner_ptr, void *outer_ptr)
{
struct tls_session *inner = (struct tls_session *) inner_ptr;
struct tls_session *outer = (struct tls_session *) outer_ptr;
BIO *bio;

bio = BIO_new(BIO_f_ssl());
if (!bio) {
flb_error("[tls] could not create BIO for TLS-in-TLS tunnel");
return -1;
}

/*
* BIO_NOCLOSE: the outer SSL object must NOT be freed when this BIO
* is freed; we manage its lifecycle via inner->outer_session.
*/
BIO_set_ssl(bio, outer->ssl, BIO_NOCLOSE);
SSL_set_bio(inner->ssl, bio, bio);
inner->outer_session = outer;
return 0;
}

static int tls_session_destroy(void *session)
{
struct tls_session *ptr = session;
struct tls_context *ctx;
struct tls_context *outer_ctx;

if (!ptr) {
return 0;
Expand All @@ -1296,6 +1327,20 @@ static int tls_session_destroy(void *session)
}

SSL_free(ptr->ssl);

/*
* If this session was chained over an outer TLS session (HTTPS proxy),
* BIO_NOCLOSE ensured SSL_free above did not free the outer SSL object.
* Free it explicitly now, under its own context mutex.
*/
if (ptr->outer_session != NULL) {
outer_ctx = ptr->outer_session->parent;
pthread_mutex_lock(&outer_ctx->mutex);
SSL_free(ptr->outer_session->ssl);
flb_free(ptr->outer_session);
pthread_mutex_unlock(&outer_ctx->mutex);
}

flb_free(ptr);

pthread_mutex_unlock(&ctx->mutex);
Expand Down Expand Up @@ -1688,6 +1733,7 @@ static struct flb_tls_backend tls_openssl = {
.session_create = tls_session_create,
.session_invalidate = tls_session_invalidate,
.session_destroy = tls_session_destroy,
.session_set_outer = tls_session_set_outer,
.net_read = tls_net_read,
.net_write = tls_net_write,
.net_handshake = tls_net_handshake,
Expand Down
Loading
Loading