From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from firstgate.proxmox.com (firstgate.proxmox.com [212.224.123.68]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits)) (No client certificate requested) by lists.proxmox.com (Postfix) with ESMTPS id C4FDD935DA for ; Mon, 20 Feb 2023 10:59:57 +0100 (CET) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id A64A824F50 for ; Mon, 20 Feb 2023 10:59:57 +0100 (CET) Received: from proxmox-new.maurer-it.com (proxmox-new.maurer-it.com [94.136.29.106]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits)) (No client certificate requested) by firstgate.proxmox.com (Proxmox) with ESMTPS for ; Mon, 20 Feb 2023 10:59:56 +0100 (CET) Received: from proxmox-new.maurer-it.com (localhost.localdomain [127.0.0.1]) by proxmox-new.maurer-it.com (Proxmox) with ESMTP id 94B0247CBA for ; Mon, 20 Feb 2023 10:59:56 +0100 (CET) From: Max Carrara To: pve-devel@lists.proxmox.com Date: Mon, 20 Feb 2023 10:59:50 +0100 Message-Id: <20230220095950.16137-1-m.carrara@proxmox.com> X-Mailer: git-send-email 2.30.2 In-Reply-To: <4472e05c-bac9-17bc-6972-a8788bbef119@proxmox.com> References: <4472e05c-bac9-17bc-6972-a8788bbef119@proxmox.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-SPAM-LEVEL: Spam detection results: 0 AWL 0.017 Adjusted score from AWL reputation of From: address BAYES_00 -1.9 Bayes spam probability is 0 to 1% KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record Subject: [pve-devel] [PATCH] http-server: redirect HTTP to HTTPS X-BeenThere: pve-devel@lists.proxmox.com X-Mailman-Version: 2.1.29 Precedence: list List-Id: Proxmox VE development discussion List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Mon, 20 Feb 2023 09:59:57 -0000 Note: This change isn't final yet and shall only serve as an example. This change adds another subroutine in the request processing algorithm that verifies whether the TLS handshake has occurred after the HTTP header was read, redirecting the user to HTTPS if it hasn't. In order to let AnyEvent handle TLS handshakes automatically, the `tls_autostart` callback is added to the handle's read queue, replacing the `tls` and `tls_ctx` constructor attributes. Signed-off-by: Max Carrara --- src/PVE/APIServer/AnyEvent.pm | 115 ++++++++++++++++++++++++++++++---- 1 file changed, 102 insertions(+), 13 deletions(-) diff --git a/src/PVE/APIServer/AnyEvent.pm b/src/PVE/APIServer/AnyEvent.pm index 86f5e07..059d710 100644 --- a/src/PVE/APIServer/AnyEvent.pm +++ b/src/PVE/APIServer/AnyEvent.pm @@ -22,6 +22,7 @@ use Compress::Zlib; use Digest::MD5; use Digest::SHA; use Encode; +use Errno qw(EBADMSG); use Fcntl (); use Fcntl; use File::Find; @@ -1331,6 +1332,7 @@ sub begin_process_next_request { my @process_steps = \( &parse_request_header_method, &parse_request_header_fields, + &ensure_tls_connection, &postprocess_header_fields, &authenticate_and_handle_request, ); @@ -1513,6 +1515,36 @@ sub parse_request_header_fields { return { success => 1 }; } +sub ensure_tls_connection { + my ($self, $reqstate) = @_; + + $self->dprint("TLS session: " . $reqstate->{hdl}->{tls}); + $self->dprint("TLS handshake succeeded: " . $reqstate->{tls_handshake_succeeded}); + + if ($reqstate->{hdl}->{tls} && $reqstate->{tls_handshake_succeeded}) { + return { success => 1 }; + } + + my $request = $reqstate->{request}; + my $h_host = $reqstate->{request}->header('Host'); + + die "request object not found in reqstate\n" + if !$request; + + die "Header field 'Host' not found in request\n" + if !$h_host; + + my $secure_host = "https://" . ($h_host =~ s/^http(s)?:\/\///r); + my $h_location = $secure_host . $request->uri(); + + return { + success => 0, + http_code => 301, + http_message => 'Moved Permanently', + http_header => HTTP::Headers->new('Location' => $h_location), + }; +} + sub postprocess_header_fields { my ($self, $reqstate) = @_; @@ -1950,26 +1982,27 @@ sub accept_connections { timeout => $self->{timeout}, linger => 0, # avoid problems with ssh - really needed ? on_eof => sub { - my ($hdl) = @_; - eval { - $self->log_aborted_request($reqstate); - $self->client_do_disconnect($reqstate); - }; - if (my $err = $@) { syslog('err', $err); } + $self->handle_on_eof($reqstate, @_); }, on_error => sub { - my ($hdl, $fatal, $message) = @_; - eval { - $self->log_aborted_request($reqstate, $message); - $self->client_do_disconnect($reqstate); - }; - if (my $err = $@) { syslog('err', "$err"); } + $self->handle_on_error($reqstate, @_); }, - ($self->{tls_ctx} ? (tls => "accept", tls_ctx => $self->{tls_ctx}) : ())); + on_starttls => sub { + $self->handle_on_starttls($reqstate, @_); + }, + on_stoptls => sub { + $self->handle_on_stoptls($reqstate, @_); + } + ); $handle_creation = 0; $self->dprint("ACCEPT FH" . $clientfh->fileno() . " CONN$self->{conn_count}"); + if ($self->{tls_ctx}) { + $self->dprint("Setting TLS to autostart"); + $reqstate->{hdl}->unshift_read(tls_autostart => $self->{tls_ctx}, "accept"); + } + $self->begin_process_next_request($reqstate); } }; @@ -1991,6 +2024,62 @@ sub accept_connections { $self->wait_end_loop() if $self->{end_loop}; } +sub handle_on_eof { + my ($self, $reqstate, $handle) = @_; + + eval { + $self->log_aborted_request($reqstate); + $self->client_do_disconnect($reqstate); + }; + + if (my $err = $@) { syslog('err', "$err"); } +} + +sub handle_on_error { + my ($self, $reqstate, $handle, $fatal, $message) = @_; + + $fatal ||= 0; + $message ||= ''; + + $self->dprint("Encountered error '$!' - fatal: $fatal; handle: $handle;"); + $self->dprint("message: $message;"); + + eval { + if ($!{EBADMSG}) { + $self->error($reqstate, 400, 'bad request'); + } else { + $self->dprint("Error '$!' not handled!"); + } + + $self->log_aborted_request($reqstate, $message); + $self->client_do_disconnect($reqstate); + }; + + if (my $err = $@) { syslog('err', "$err"); } +} + +sub handle_on_starttls { + my ($self, $reqstate, $handle, $success, $message) = @_; + + eval { + $reqstate->{tls_handshake_succeeded} = $success; + + if ($success) { + $self->dprint("TLS handshake for handle $handle successful ($message); session started"); + } else { + $self->dprint("TLS handshake for handle $handle failed"); + } + }; + + if (my $err = $@) { syslog('err', "$err"); } +} + +sub handle_on_stoptls { + my ($self, $reqstate, $handle) = @_; + + $self->dprint("TLS session for handle $handle stopped"); +} + # Note: We can't open log file in non-blocking mode and use AnyEvent::Handle, # because we write from multiple processes, and that would arbitrarily mix output # of all processes. -- 2.30.2