From: Max Carrara Date: Fri, 3 Mar 2023 17:29:50 +0000 (+0100) Subject: fix #4494: redirect HTTP to HTTPS X-Git-Url: https://git.proxmox.com/?p=pve-http-server.git;a=commitdiff_plain;h=933a4dbbaff5ccc43ec8b30c1e852b1b36f92497 fix #4494: redirect HTTP to HTTPS 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 --- diff --git a/src/PVE/APIServer/AnyEvent.pm b/src/PVE/APIServer/AnyEvent.pm index 72c649d..ddab0b4 100644 --- a/src/PVE/APIServer/AnyEvent.pm +++ b/src/PVE/APIServer/AnyEvent.pm @@ -1314,7 +1314,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*(.*)/) { @@ -1384,6 +1384,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) = @_; @@ -1791,11 +1828,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); } };