fix(transport): harden QUIC admission boundaries

This commit is contained in:
sechmachine
2026-08-12 20:04:51 +07:00
parent 111092becb
commit 09f40eb9c7
7 changed files with 508 additions and 78 deletions
+212 -1
View File
@@ -536,6 +536,164 @@ func TestStableErrorFramePrecedesConnectionTeardown(t *testing.T) {
}
}
func TestStableErrorDrainOutlivesExpiredHandlerContext(t *testing.T) {
serverTLS, clientTLS := testTLS(t)
clientTLS.NextProtos = []string{"versevdi-gateway-v1"}
fake := NewFakeApollo(FakeApolloConfig{Now: time.Now()})
authority := protocol.SessionAuthority{Version: "1", SessionID: "session-1", GatewayID: "gateway-1", Audience: "versevdi-gateway", ExpiresAt: time.Now().Add(time.Minute).UTC().Format(time.RFC3339Nano), Capabilities: DefaultCapabilities(), ProviderProfile: ProviderProfileApollo, ProviderIdentity: fake.config.Identity.Key()}
admission := AdmissionFunc(func(ctx context.Context, _ protocol.TunnelAdmissionRequest) (protocol.SessionAuthority, error) {
<-ctx.Done()
return protocol.SessionAuthority{}, ctx.Err()
})
server, err := NewServer(ServerConfig{ListenAddress: "127.0.0.1:0", TLSConfig: serverTLS, GatewayID: authority.GatewayID, Admission: admission, Provider: fake})
if err != nil {
t.Fatal(err)
}
server.helloTimeout = 20 * time.Millisecond
ctx, cancel := context.WithCancel(context.Background())
go func() { _ = server.Serve(ctx) }()
connection, err := quic.DialAddr(context.Background(), server.Addr().String(), clientTLS, &quic.Config{EnableDatagrams: true})
if err != nil {
t.Fatal(err)
}
stream, err := connection.OpenStreamSync(context.Background())
if err != nil {
t.Fatal(err)
}
request := protocol.TunnelAdmissionRequest{Version: "1", SessionID: authority.SessionID, GatewayID: authority.GatewayID, Audience: authority.Audience, Grant: strings.Repeat("g", 64), ClientNonce: "nonce-0000000001", DeviceSignature: strings.Repeat("s", 86), Capabilities: DefaultCapabilities()}
payload, err := protocol.EncodeTunnelAdmissionRequest(request)
if err != nil {
t.Fatal(err)
}
if err := writeWire(stream, payload, defaultHelloLimit); err != nil {
t.Fatal(err)
}
response, err := readWire(stream, defaultHelloLimit)
if err != nil {
t.Fatalf("stable response lost with expired handler context: %v", err)
}
if stable, err := protocol.DecodeStableError(response); err != nil || stable.Code != "admission_rejected" {
t.Fatalf("stable response = %#v, %v", stable, err)
}
_ = connection.CloseWithError(applicationError, "done")
cancel()
_ = server.Close()
}
func TestServerCloseInterruptsPartialHelloClient(t *testing.T) {
serverTLS, clientTLS := testTLS(t)
clientTLS.NextProtos = []string{"versevdi-gateway-v1"}
fake := NewFakeApollo(FakeApolloConfig{Now: time.Now()})
authority := protocol.SessionAuthority{Version: "1", SessionID: "session-1", GatewayID: "gateway-1", Audience: "versevdi-gateway", ExpiresAt: time.Now().Add(time.Minute).UTC().Format(time.RFC3339Nano), Capabilities: DefaultCapabilities(), ProviderProfile: ProviderProfileApollo, ProviderIdentity: fake.config.Identity.Key()}
server, err := NewServer(ServerConfig{ListenAddress: "127.0.0.1:0", TLSConfig: serverTLS, GatewayID: authority.GatewayID, Admission: &oneTimeAdmission{authority: authority, released: make(chan struct{})}, Provider: fake})
if err != nil {
t.Fatal(err)
}
ctx, cancel := context.WithCancel(context.Background())
serveDone := make(chan error, 1)
go func() { serveDone <- server.Serve(ctx) }()
connection, err := quic.DialAddr(context.Background(), server.Addr().String(), clientTLS, &quic.Config{EnableDatagrams: true})
if err != nil {
t.Fatal(err)
}
stream, err := connection.OpenStreamSync(context.Background())
if err != nil {
t.Fatal(err)
}
if _, err := stream.Write([]byte{0, 0}); err != nil {
t.Fatal(err)
}
closed := make(chan error, 1)
go func() { closed <- server.Close() }()
select {
case err := <-closed:
if err != nil {
t.Fatal(err)
}
case <-time.After(time.Second):
t.Fatal("Server.Close blocked on partial hello")
}
select {
case <-connection.Context().Done():
case <-time.After(time.Second):
t.Fatal("partial hello connection remained open")
}
cancel()
if err := <-serveDone; err != nil {
t.Fatal(err)
}
}
func TestHelloUsesOneAbsoluteDeadlineAgainstTrickle(t *testing.T) {
serverTLS, clientTLS := testTLS(t)
clientTLS.NextProtos = []string{"versevdi-gateway-v1"}
fake := NewFakeApollo(FakeApolloConfig{Now: time.Now()})
authority := protocol.SessionAuthority{Version: "1", SessionID: "session-1", GatewayID: "gateway-1", Audience: "versevdi-gateway", ExpiresAt: time.Now().Add(time.Minute).UTC().Format(time.RFC3339Nano), Capabilities: DefaultCapabilities(), ProviderProfile: ProviderProfileApollo, ProviderIdentity: fake.config.Identity.Key()}
server, err := NewServer(ServerConfig{ListenAddress: "127.0.0.1:0", TLSConfig: serverTLS, GatewayID: authority.GatewayID, Admission: &oneTimeAdmission{authority: authority, released: make(chan struct{})}, Provider: fake})
if err != nil {
t.Fatal(err)
}
server.helloTimeout = 40 * time.Millisecond
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
go func() { _ = server.Serve(ctx) }()
connection, err := quic.DialAddr(context.Background(), server.Addr().String(), clientTLS, &quic.Config{EnableDatagrams: true})
if err != nil {
t.Fatal(err)
}
stream, err := connection.OpenStreamSync(context.Background())
if err != nil {
t.Fatal(err)
}
if _, err := stream.Write([]byte{0}); err != nil {
t.Fatal(err)
}
started := time.Now()
time.Sleep(25 * time.Millisecond)
_, _ = stream.Write([]byte{0})
response, err := readWire(stream, defaultHelloLimit)
if err != nil {
t.Fatalf("absolute hello deadline did not produce a stable error: %v", err)
}
stable, err := protocol.DecodeStableError(response)
if err != nil || stable.Code != "invalid_hello" {
t.Fatalf("stable response = %#v, %v", stable, err)
}
if time.Since(started) > 250*time.Millisecond {
t.Fatal("trickling hello reset or escaped its absolute deadline")
}
_ = connection.CloseWithError(applicationError, "done")
_ = server.Close()
}
func TestHelloDeadlineIsClearedAfterAdmission(t *testing.T) {
serverTLS, clientTLS := testTLS(t)
fake := NewFakeApollo(FakeApolloConfig{Now: time.Now()})
authority := protocol.SessionAuthority{Version: "1", SessionID: "session-1", GatewayID: "gateway-1", Audience: "versevdi-gateway", ExpiresAt: time.Now().Add(time.Minute).UTC().Format(time.RFC3339Nano), Capabilities: DefaultCapabilities(), ProviderProfile: ProviderProfileApollo, ProviderIdentity: fake.config.Identity.Key()}
admission := &oneTimeAdmission{authority: authority, released: make(chan struct{}), disableClipboard: true}
server, err := NewServer(ServerConfig{ListenAddress: "127.0.0.1:0", TLSConfig: serverTLS, GatewayID: authority.GatewayID, Admission: admission, Provider: fake})
if err != nil {
t.Fatal(err)
}
server.helloTimeout = 40 * time.Millisecond
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
go func() { _ = server.Serve(ctx) }()
request := protocol.TunnelAdmissionRequest{Version: "1", SessionID: authority.SessionID, GatewayID: authority.GatewayID, Audience: authority.Audience, Grant: strings.Repeat("g", 64), ClientNonce: "nonce-0000000001", DeviceSignature: strings.Repeat("s", 86), Capabilities: DefaultCapabilities()}
client, err := Dial(context.Background(), server.Addr().String(), clientTLS, request)
if err != nil {
t.Fatal(err)
}
time.Sleep(80 * time.Millisecond)
select {
case <-client.connection.Context().Done():
t.Fatal("hello deadline leaked into the admitted session")
default:
}
_ = client.Close()
_ = server.Close()
}
func TestStableErrorNeverLeaksInternalProviderDetails(t *testing.T) {
var wire bytes.Buffer
if err := writeStableError(&wire, "provider_unavailable", errors.New("https://provider.invalid/launch?rikey=secret-sentinel"), true); err != nil {
@@ -554,6 +712,58 @@ func TestStableErrorNeverLeaksInternalProviderDetails(t *testing.T) {
}
}
func TestPostAdmissionProviderFailureIsTerminalEvenWhenReleaseFails(t *testing.T) {
serverTLS, clientTLS := testTLS(t)
clientTLS.NextProtos = []string{"versevdi-gateway-v1"}
fake := NewFakeApollo(FakeApolloConfig{Now: time.Now()})
authority := protocol.SessionAuthority{Version: "1", SessionID: "session-1", GatewayID: "gateway-1", Audience: "versevdi-gateway", ExpiresAt: time.Now().Add(time.Minute).UTC().Format(time.RFC3339Nano), Capabilities: DefaultCapabilities(), ProviderProfile: ProviderProfileApollo, ProviderIdentity: fake.config.Identity.Key()}
admission := &oneTimeAdmission{authority: authority, released: make(chan struct{}), disableClipboard: true, releaseErr: errors.New("release failed")}
provider := providerStartFunc(func(context.Context, LaunchRequest) (ProviderSession, error) {
return nil, context.DeadlineExceeded
})
server, err := NewServer(ServerConfig{ListenAddress: "127.0.0.1:0", TLSConfig: serverTLS, GatewayID: authority.GatewayID, Admission: admission, Provider: provider})
if err != nil {
t.Fatal(err)
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
go func() { _ = server.Serve(ctx) }()
connection, err := quic.DialAddr(context.Background(), server.Addr().String(), clientTLS, &quic.Config{EnableDatagrams: true})
if err != nil {
t.Fatal(err)
}
stream, err := connection.OpenStreamSync(context.Background())
if err != nil {
t.Fatal(err)
}
request := protocol.TunnelAdmissionRequest{Version: "1", SessionID: authority.SessionID, GatewayID: authority.GatewayID, Audience: authority.Audience, Grant: strings.Repeat("g", 64), ClientNonce: "nonce-0000000001", DeviceSignature: strings.Repeat("s", 86), Capabilities: DefaultCapabilities()}
payload, err := protocol.EncodeTunnelAdmissionRequest(request)
if err != nil {
t.Fatal(err)
}
if err := writeWire(stream, payload, defaultHelloLimit); err != nil {
t.Fatal(err)
}
response, err := readWire(stream, defaultHelloLimit)
if err != nil {
t.Fatal(err)
}
stable, err := protocol.DecodeStableError(response)
if err != nil || stable.Code != "provider_timeout" || stable.Retryable {
t.Fatalf("post-admission stable response = %#v, %v", stable, err)
}
select {
case <-admission.released:
case <-time.After(time.Second):
t.Fatal("post-admission failure did not attempt release")
}
if got := admission.releases.Load(); got != 1 {
t.Fatalf("release attempts = %d, want 1", got)
}
_ = connection.CloseWithError(applicationError, "done")
_ = server.Close()
}
func TestGatewayTelemetrySeparatesQueueProcessingAndPacing(t *testing.T) {
serverTLS, clientTLS := testTLS(t)
session := &fakeSession{
@@ -1668,6 +1878,7 @@ type oneTimeAdmission struct {
streamPolicy protocol.ProviderStreamPolicy
providerWork *protocol.ProviderSessionWork
disableClipboard bool
releaseErr error
}
type recordingProviderStateReporter struct {
@@ -1742,7 +1953,7 @@ func (a *oneTimeAdmission) Release(_ context.Context, authority protocol.Session
a.releaseAuthority = authority
close(a.released)
}
return nil
return a.releaseErr
}
func mustRead(t *testing.T, path string) []byte {