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 38FF185B3 for ; Fri, 3 Mar 2023 18:30:34 +0100 (CET) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id 1A0D91A3F2 for ; Fri, 3 Mar 2023 18:30:04 +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 ; Fri, 3 Mar 2023 18:30:03 +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 BB34A41D6F for ; Fri, 3 Mar 2023 18:30:02 +0100 (CET) From: Max Carrara To: pve-devel@lists.proxmox.com Date: Fri, 3 Mar 2023 18:29:50 +0100 Message-Id: <20230303172951.197711-4-m.carrara@proxmox.com> X-Mailer: git-send-email 2.30.2 In-Reply-To: <20230303172951.197711-1-m.carrara@proxmox.com> References: <20230303172951.197711-1-m.carrara@proxmox.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-SPAM-LEVEL: Spam detection results: 0 AWL 0.041 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 v2 http-server 3/4] fix #4494: anyevent: 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: Fri, 03 Mar 2023 17:30:34 -0000 Allow HTTP connections up until the request's header has been parsed and processed. If no TLS handshake has been completed beforehand, the server now responds with either a '301 Moved Permanently' or a '308 Permanent Redirect' as noted in the MDN web docs[1]. This is done after the header was parsed; for the redirect to work, the `Host` header field of the request is used to create the `Location` field of the response. This makes redirections independent of how the server is accessed (e.g. via IP, localhost, FQDN, ...) possible. Upon redirection the client is immediately disconnected; otherwise, they would have to wait for the connection to time out until they may reconnect via TLS again. [1] https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/301 Signed-off-by: Max Carrara --- src/PVE/APIServer/AnyEvent.pm | 46 +++++++++++++++++++++++++++++++++-- 1 file changed, 44 insertions(+), 2 deletions(-) diff --git a/src/PVE/APIServer/AnyEvent.pm b/src/PVE/APIServer/AnyEvent.pm index 636502b..d1bba3c 100644 --- a/src/PVE/APIServer/AnyEvent.pm +++ b/src/PVE/APIServer/AnyEvent.pm @@ -1318,7 +1318,7 @@ sub unshift_read_header { if $state->{key}; $self->process_header($reqstate) or return; - # header processing complete - authenticate now + $self->ensure_tls_connection($reqstate) or return; $self->authenticate_and_handle_request($reqstate) or return; } elsif ($line =~ /^([^:\s]+)\s*:\s*(.*)/) { @@ -1388,6 +1388,43 @@ sub process_header { return 1; } +sub ensure_tls_connection { + my ($self, $reqstate) = @_; + + # Skip if server doesn't use TLS + if (!$self->{tls_ctx}) { + return 1; + } + + # TLS session exists, so the handshake has succeeded + if ($reqstate->{hdl}->{tls}) { + return 1; + } + + my $request = $reqstate->{request}; + my $method = $request->method(); + + my $h_host = $reqstate->{request}->header('Host'); + + die "Header field 'Host' not found in request\n" + if !$h_host; + + my $secure_host = "https://" . ($h_host =~ s/^http(s)?:\/\///r); + + my $header = HTTP::Headers->new('Location' => $secure_host . $request->uri()); + + if ($method eq 'GET' || $method eq 'HEAD') { + $self->error($reqstate, 301, 'Moved Permanently', $header); + } else { + $self->error($reqstate, 308, 'Permanent Redirect', $header); + } + + # disconnect the client so they may immediately connect again via HTTPS + $self->client_do_disconnect($reqstate); + + return; +} + sub authenticate_and_handle_request { my ($self, $reqstate) = @_; @@ -1795,11 +1832,16 @@ sub accept_connections { }; if (my $err = $@) { syslog('err', "$err"); } }, - ($self->{tls_ctx} ? (tls => "accept", tls_ctx => $self->{tls_ctx}) : ())); + ); $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->push_request_header($reqstate); } }; -- 2.30.2