]> git.proxmox.com Git - pve-http-server.git/commitdiff
fix #4494: redirect HTTP to HTTPS
authorMax Carrara <m.carrara@proxmox.com>
Fri, 3 Mar 2023 17:29:50 +0000 (18:29 +0100)
committerFabian Grünbichler <f.gruenbichler@proxmox.com>
Tue, 7 Mar 2023 10:19:32 +0000 (11:19 +0100)
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 <m.carrara@proxmox.com>
src/PVE/APIServer/AnyEvent.pm

index 72c649d782900a7f7d997b4db22623d3be448457..ddab0b474debb1a1617ab71f44ee9a971e83cf97 100644 (file)
@@ -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);
        }
     };